Golang 源碼分析: Golang 的啓動原理
導讀
隨着技術的發展,大多的 PHPer 都開始轉型 golang。這個也是 golang 因爲 go 的一些特別深受大家喜愛。我們團隊也在試水,在內部的一些小項目上做了試探。在此同時我作爲試探的一員身先士卒,沙場練兵。從中也琢磨到一些技巧希望能和大家一起學習共勉。
一、前期準備:
go 版本:go1.13.4
由於是做源碼分析調試,我們必須找到適合自己的工具。咱們調試的話可以考慮幾種工具
1)gdb linux 使用比較合適
2)lldb mac 自帶,不過建議 dlv
3)dlv go 開發的調試工具
本文中主要以 dlv 爲主,接下來我們一起來安裝一下 dlv。
第一步下載
#go get -u github.com/derekparker/delve/cmd/dlv
第二步編譯:
#git clone https://github.com/go-delve/delve.git $GOPATH/src/github.com/go-delve/delve
#cd $GOPATH/src/github.com/go-delve/
#make install
$GOPATH 是 golang 的環境變量,我使用的是 Linux,建議大家在 / etc/profile 設置,mac 相同。
windows 設置 gopath
https://jingyan.baidu.com/article/5d368d1eb616133f60c057bf.html
第三步設置環境變量:
#vim /etc/profile
export PATH=$PATH:/data/gopath/bin
/data/gopath/bin 爲你自己的 go 的 bin 目錄
初始化環境變量
#source /etc/profile
dlv 參數介紹(常用)
二. 調試:
1. 編譯調試文件
代碼內容
package main
import (
"fmt"
)
func main() {
var p **int
var i int = 10
var p1 *int = &i
fmt.Println("p1=", p1)
p = &p1
fmt.Println("p=", p)
}
編譯
#go build -gcflags=all="-N -l" pointer.go
必須這樣編譯,待能用 dlv 打印導出變量信息;
2. 載入文件
#dlv exec ./pointer
設置 rt0_go 斷點,程序入口
(dlv) break runtime.rt0_go
是不是很好奇爲什麼入口在 runtime.rt0_go
可以打開程序後輸入 r,在輸入 list,然後在輸入 si;si 是單步 cpu 指令;
其實 go 在運行時會根據系統和 CPU 的不同,找到底層代碼下的,rt0_**.s 的彙編代碼,彙編代碼中再去調轉到 runtime.rt0_go 。
當然由於系統的不同可執行程序的形式不同。
常見的可執行程序可以分爲三大類:
1)PE 文件
PE 文件主要是 Windows 系列系統,可執行文件索引;
2)ELF 文件
ELF 文件是 linux 系列系統,可執行文件索引;
3)mach-o 文件
mach-o 是 mac 系列系統的可執行文件格式,蘋果系統是基於 FreeBSD 的,屬於 unix-like 操作系統;
有一篇比較不錯的文章可以推薦給大家:
https://blog.csdn.net/abc_12366/article/details/88205670
好的我們繼續往下說, 我們進入
我們按住 n 執行停留到 212 行
我們可以看到 runtime.args 、runtime.osinit 和 runtime.schedinit 三個函數調用。我們可以依次輸入 si 進入函數體內查看
(div)si
args 函數主要用途整理命令行參數;
osint 函數是確定 CPU Core 數量
schedinit 源碼如下:
schedinit 主要作用是所有運行時環境初始化; 我們繼續下一個斷點,然後往下執行:
(dlv)b runtime.main
(dlv)n
runtime/proc.go 部分源碼
func main() {
g := getg()
g.m.g0.racectx = 0
// 執行棧最大限制 64位系統1GB,32位系統250MB
if sys.PtrSize == 8 {
maxstacksize = 1000000000
} else {
maxstacksize = 250000000
}
// 允許newproc啓動新Ms。
mainStarted = true
if GOARCH != "wasm" { // wasm上還沒有線程,所以沒有sysmon
// 啓動系統後監控(併發任務調度相關)
systemstack(func() {
newm(sysmon, nil)
})
}
...
//啓動垃圾回收器後臺操作
gcenable()
...
//進行間接調用,因爲鏈接器在放置運行時不知道主包的地址。這個就是我們程序文件入口
//其實就是用戶main.main函數
fn := main_main
fn()
//執行結束
exit(0)
}
我們執行到 fn() 出可以按 s
(dlv)s
這樣就來到了我們程序的代碼;這時我們可以看一下我們的調度棧
執行完成程序完成之後又回到 runtime/proc.go 中 if atomic.Load(&runningPanicDefers) 處
繼續往下執行,一直到 exit(0) 處,按 si 執行。
程序回到 sys_linux_amd64.s 彙編代碼中的 runtime.exit
最終程序結束;
三. 調用流程:
非 goroutine 退出情況
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/c6txkGyafTlLayRURd7KeA