Go内核源码剖析 一 程序执行启动过程
go內核源碼剖析 一
這篇是看雨痕大佬的書所做練習的筆記,(其實后面部分基本都是抄的,但是都實踐了)
由于電腦抽風,使用的是win10的Linux子系統,功能不完善,很多跟蹤支持性不好(可以算是抄的原因)。
要想看內核源碼剖析的可以到雨痕大神的github。
- 1.新建hello.go編譯得到hello
2.gdb調試,輸入 gdb hello 命令行得到:((gdb) 后都表示輸入的))
先是Linux系統下的:
(gdb) info files
Symbols from "C:\Users\WnagoiYy\hello".
Local exec file:
'C:\Users\WnagoiYy\hello', file type elf64-x86-64.
Entry point: 0x41bc50
0x0000000000400c00 - 0x000000000041c0ba is .text
0x000000000041d000 - 0x0000000000435a00 is .rodata
0x0000000000435a00 - 0x0000000000435af8 is .typelink
0x0000000000435b00 - 0x0000000000440c91 is .gosymtab
0x0000000000440ca0 - 0x0000000000454c46 is .gopclntab
0x0000000000455000 - 0x0000000000455030 is .noptrdata
0x0000000000455040 - 0x0000000000459688 is .data
0x00000000004596a0 - 0x0000000000461698 is .bss
0x00000000004616a0 - 0x0000000000476bf8 is .noptrbss
接下來是windows下的編譯后文件gdb:
(gdb) info files
Symbols from "C:\Users\WnagoiYy\go\hello".
Local exec file:
'C:\Users\WnagoiYy\go\hello', file type pei-x86-64.
Entry point: 0x44d730
0x0000000000401000 - 0x00000000004953ad is .text
0x0000000000496000 - 0x0000000000496c00 is .data
0x0000000000506000 - 0x00000000005064fc is .idata
Entry point: 0x41bc50 入口地址在.text段file type elf64-x86-64. 文件類型為elf
.text
代碼段(codesegment/textsegment)通常是指用來存放程序執行代碼的一塊內存區域。這部分區域的大小在程序運行前就已經確定,并且內存區域通常屬于只讀,某些架構也允許代碼段為可寫,即允許修改程序。在代碼段中,也有可能包含一些只讀的常數變量,例如字符串常量等。.rodata
存放字符串和#define定義的常量.data
數據段(datasegment)通常是指用來存放程序中已初始化的全局變量的一塊內存區域。數據段屬于靜態內存分配。.bss
BSS段(bsssegment)通常是指用來存放程序中未初始化的全局變量的一塊內存區域。BSS是英文BlockStarted by Symbol的簡稱。BSS段屬于靜態內存分配。
(gdb) b *0x41bc50
Breakpoint 1 at 0x41bc50: file /usr/lib/go/src/pkg/runtime/rt0_linux_amd64.s, line 8.入口打上一個斷點,因為是win10下的linux子系統,所以文件路徑不對
hejing@DESKTOP-EP9L18K:/mnt/c/Go/src/runtime$ ls rt0_*
...
rt0_darwin_arm64.s rt0_linux_amd64.s rt0_nacl_386.s
rt0_openbsd_amd64.s rt0_windows_amd64.s先cd到/mnt/c/Go/src/runtime,然后輸入ls rt0_*,會找到許多文件,我們需要上面斷點的rt0_linux_amd64.s
打開rt0_linux_amd64.s,跳到具體行查看代碼:
TEXT _rt0_amd64_linux(SB),NOSPLIT,$-8
LEAQ 8(SP), SI // argv
MOVQ 0(SP), DI // argc
MOVQ $main(SB), AX
JMP AX
...
TEXT main(SB),NOSPLIT,$-8
MOVQ $runtime·rt0_go(SB), AX
JMP AX
查看MOVQ $runtime·rt0_go(SB), AX里具體的runtime.rt0_go
(gdb) b runtime.rt0_go
Breakpoint 2 at 0x44a780: file /usr/local/go/src/runtime/asm_amd64.s, line 12.正是asm_amd64.s完成了初始化和運行時啟動:
TEXT runtime·rt0_go(SB),NOSPLIT,$0
...
//調用初始化函數
CALL runtime·args(SB)
CALL runtime·osinit(SB)
CALL runtime·schedinit(SB)
//創建 main goroutine 用于執行 runtime.main
MOVQ $runtime·mainPC(SB), AX
PUSHQ AX
PUSHQ $0
CALL runtime·newproc(SB)
POPQ AX
POPQ AX
//讓當前線程開始執行 main goroutine
CALL runtime·mstart(SB)
RET
DATA runtime·mainPC+0(SB)/8,$runtime·main(SB)
GLOBL runtime·mainPC(SB),RODATA,$8
到此,匯編引導全部完成,剩下的由golang實現
(gdb) b runtime.main
Breakpoint 3 at 0x423250: file /usr/local/go/src/runtime/proc.go, line 28.
接下來查看初始化的相關內容:
由上得知調用了以下初始化函數:
CALL runtime·args(SB)
CALL runtime·osinit(SB)
CALL runtime·schedinit(SB)
現在查看args函數的位置和內容:
(gdb) b runtime.args
Breakpoint 7 at 0x42ebf0: file /usr/local/go/src/runtime/runtime1.go, line 48.
在runtime1.go可以看見初始化參數函數:
func args(c int32, v **byte) {//整理命令行參數argc = cargv = vsysargs(c, v) } 現在查看osinit函數的位置和內容: Breakpoint 8 at 0x41e9d0: file /usr/local/go/src/runtime/os1_linux.go, line 172. 在**os1_linux.go**中可以看見: func osinit() { //確定CPU Core的數量ncpu = getproccount() }最為關鍵的就是**schedinit**,所有運行環境的初始化都在這里: Breakpoint 9 at 0x424590: file /usr/local/go/src/runtime/proc1.go, line 40. 跟進**proc1.go**: // The bootstrap sequence is: // // call osinit // call schedinit // make & queue new G // call runtime·mstart // // The new G calls runtime·main. func schedinit() {// raceinit must be the first call to race detector.// In particular, it must be done before mallocinit below calls racemapshadow._g_ := getg()if raceenabled {_g_.racectx, raceprocctx0 = raceinit()}//最大系統線程數量限制,具體查看 runtime/debug.SetMaxThreads// maximum number of m's allowed (or die)sched.maxmcount = 10000 //sched結構體位于rutime2.go中//棧,內存分配器,調度器相關初始化tracebackinit()moduledataverify()stackinit()mallocinit()mcommoninit(_g_.m)alginit() // maps must not be used before this calltypelinksinit() // uses mapsitabsinit()msigsave(_g_.m)initSigmask = _g_.m.sigmask//處理命令行參數和環境變量goargs()goenvs()//處理 GODEBUG?GOTRACEBACK 調試相關的環境變量設置parsedebugvars()//垃圾回收初始化gcinit()//通過CPU Core 和 GOMAXPROCS 環境變量確定 P 數量sched.lastpoll = uint64(nanotime())procs := int(ncpu)if procs > _MaxGomaxprocs {procs = _MaxGomaxprocs}if n := atoi(gogetenv("GOMAXPROCS")); n > 0 {if n > _MaxGomaxprocs {n = _MaxGomaxprocs}procs = n}//調整p的數量if procresize(int32(procs)) != nil {throw("unknown runnable goroutine during bootstrap")}if buildVersion == "" {// Condition should never trigger. This code just serves// to ensure runtime·buildVersion is kept in the resulting binary.buildVersion = "unknown"} }
初始化操作到此并未結束,因為接下來要執?的是 runtime.main,?不是?戶邏 輯??函數 main.main。 (gdb) b runtime.main Breakpoint 10 at 0x423250: file /usr/local/go/src/runtime/proc.go, line 28. 查看**proc.go**里的main函數: func main() {g := getg()g.m.g0.racectx = 0//執行棧的最大限制:1GB-on 64bit, 250MB-on 32bitif sys.PtrSize == 8 {maxstacksize = 1000000000} else {maxstacksize = 250000000}runtimeInitTime = nanotime()//啟動系統后臺監控(定期垃圾回收,以及并發任務相關)systemstack(func() {newm(sysmon, nil)})lockOSThread()if g.m != &m0 {throw("runtime.main not on m0")}//執行runtime里面的所有init函數runtime_init() // must be before deferneedUnlock := truedefer func() {if needUnlock {unlockOSThread()}}()//啟動垃圾回收器后臺操作gcenable()main_init_done = make(chan bool)if iscgo {if _cgo_thread_start == nil {throw("_cgo_thread_start missing")}if GOOS != "windows" {if _cgo_setenv == nil {throw("_cgo_setenv missing")}if _cgo_unsetenv == nil {throw("_cgo_unsetenv missing")}}if _cgo_notify_runtime_init_done == nil {throw("_cgo_notify_runtime_init_done missing")}cgocall(_cgo_notify_runtime_init_done, nil)}//用戶main包的init函數初始化調用main_init()close(main_init_done)needUnlock = falseunlockOSThread()if isarchive || islibrary {return}//執行用戶的main函數main_main()if raceenabled {racefini()}if panicking != 0 {gopark(nil, nil, "panicwait", traceEvGoStop, 1)}//執行結束,返回函數狀態碼exit(0)for {var x *int32*x = 0} }
與之相關的就是 runtime_init 和 main_init 這兩個函數,它們都是由編譯器動態?成
//go:linkname runtime_init runtime.init
func runtime_init()
//go:linkname main_init main.init
func main_init()
//go:linkname main_main main.main
func main_main()
注意鏈接后符號名的變化:runtime_init > runtime.init。
我們準備?個稍微復雜點的?例,看看編譯器究竟?了什么。
src
|+- main.go, test.go
|+- lib
|+- sum.go
lib/sum.go
package lib func init(){println("sum.init") } func Sum(x ...int)int{n:=0for _,i:=range x{n+=i} return n } test.go package main import ( "lib") func init() {println("test.init") } func test() {println(lib.Sum(1, 2, 3)) } main.go package main import (_ "net/http" ) func init() {println("main.init.2") } func main() {test() } func init() {println("main.init.1") } 編譯,執?輸出。 $ go build -gcflags "-N -l" -o test $ ./test sum.init main.init.2 main.init.1 test.init 6 接下來我們?反匯編?具,看看最終動態?成代碼的真實??: TEXT runtime.init.1(SB) c:/go/src/runtime/mstats.go ... mstats.go:175 0x41fc00 CALL runtime.printlock(SB) ... mstats.go:175 0x41fc10 CALL runtime.printint(SB) mstats.go:175 0x41fc15 CALL runtime.printsp(SB) mstats.go:175 0x41fc1a MOVQ $0x1690, 0(SP) mstats.go:175 0x41fc22 CALL runtime.printint(SB) mstats.go:175 0x41fc27 CALL runtime.printnl(SB) mstats.go:175 0x41fc2c CALL runtime.printunlock(SB) mstats.go:176 0x41fc31 LEAQ 0x49ffd(IP), AX mstats.go:176 0x41fc38 MOVQ AX, 0(SP) mstats.go:176 0x41fc3c MOVQ $0x24, 0x8(SP) mstats.go:176 0x41fc45 CALL runtime.throw(SB) mstats.go:176 0x41fc4a UD2 mstats.go:172 0x41fc4c CALL runtime.morestack_noctxt(SB) mstats.go:172 0x41fc51 JMP runtime.init.1(SB) :-1 0x41fc56 INT $0x3 ...TEXT runtime.init.2(SB) c:/go/src/runtime/panic.go ... panic.go:177 0x423966 CALL runtime.writebarrierptr(SB) panic.go:178 0x42396b JMP 0x42394c panic.go:174 0x42396d CALL runtime.morestack_noctxt(SB) panic.go:174 0x423972 JMP runtime.init.2(SB) :-1 0x423977 INT $0x3 ....TEXT runtime.init.3(SB) c:/go/src/runtime/proc.go ... proc.go:213 0x426857 CALL runtime.newproc(SB) proc.go:214 0x42685c MOVQ 0x10(SP), BP proc.go:214 0x426861 ADDQ $0x18, SP proc.go:214 0x426865 RET proc.go:212 0x426866 CALL runtime.morestack_noctxt(SB) proc.go:212 0x42686b JMP runtime.init.3(SB) :-1 0x42686d INT $0x3 ...TEXT runtime.init(SB) c:/go/src/runtime/zcallback_windows.go ... zcallback_windows.go:6 0x445d53 CALL runtime.throwinit(SB) ... panic.go:23 0x445d95 CALL runtime.convT2I(SB) ... select.go:48 0x445fa9 CALL runtime.funcPC(SB) ... select.go:49 0x445fd1 CALL runtime.funcPC(SB) ... zcallback_windows.go:6 0x445fe2 CALL runtime.init.1(SB) zcallback_windows.go:6 0x445fe7 CALL runtime.init.2(SB) zcallback_windows.go:6 0x445fec CALL runtime.init.3(SB) ... panic.go:23 0x44609a CALL runtime.writebarrierptr(SB) panic.go:30 0x44609f JMP 0x445dc0 zcallback_windows.go:6 0x4460a4 CALL runtime.morestack_noctxt(SB) zcallback_windows.go:6 0x4460a9 JMP runtime.init(SB) :-1 0x4460ae INT $0x3runtime里面的多個init被賦予唯一的函數名,再由 runtime.init(SB)統一調用,?于 main.init,情況基本?致。區別在于它負責調?? runtime 包的初始化函數:
$ go tool objdump -s "main\.init\b" test TEXT main.init.1(SB) src/main.go main.go:7 ... TEXT main.init.2(SB) src/main.go main.go:15 ... TEXT main.init.3(SB) src/test.go test.go:7 ... TEXT main.init(SB) src/test.go test.go:13 ... test.go:13 CALL net/http.init(SB) test.go:13 CALL test/lib.init(SB) test.go:13 CALL main.init.1(SB) test.go:13 CALL main.init.2(SB) test.go:13 CALL main.init.3(SB) test.go:13 MOVL $0x2, 0x48d543(IP) test.go:13 RET被引?的包,包括 lib 和標準庫 net/http ?的 init 函數都被 main.init 調?。
*雖然從當前版本的編譯器?度來說,init 的執?順序和依賴關系、?件名以及定義順序有關。但這種次序?常不便于維護和理解,極易造成潛在錯誤,所以強烈要求讓 init 只做該做的事情:局部初始化。
最后需要記住:
-所有 init 函數都在同?個 goroutine 內執?。
-所有 init 函數結束后才會執? main.main 函數。
總結
以上是生活随笔為你收集整理的Go内核源码剖析 一 程序执行启动过程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 原生js实现红球碰撞篮球效果
- 下一篇: 微软明年关闭诺基亚功能手机应用商店