1、入口
1.1 準備go程式
package main func test() int { return 1 } func main() { go test() }
編譯 go build main.go
1.2 使用readelf 查詢入口
root@xxx:/data# readelf -h main ELF 頭: ... 入口點地址: 0x453860 程式頭起點: 64 (bytes into file) ... root@xxx:/data# addr2line -e main -a 0x453860 0x0000000000453860 /usr/lib/go-1.18/src/runtime/rt0_linux_amd64.s:8
2、引導
2.1 檢視引導程式碼
src/runtime/rt0_linux_amd64.s
TEXT _rt0_amd64_linux(SB),NOSPLIT,$-8 JMP _rt0_amd64(SB)
查詢_rt0_amd64
root@xxx:/data# readelf -s main | grep -P "_rt0_amd64\b" 912: 0000000000451ba0 14 FUNC GLOBAL DEFAULT 1 _rt0_amd64 root@xxx:/data# addr2line -e main -a 0451ba0 0x0000000000451ba0 /usr/lib/go-1.18/src/runtime/asm_amd64.s:16
src/runtime/asm_amd64.s
EXT _rt0_amd64(SB),NOSPLIT,$-8 MOVQ 0(SP), DI // argc LEAQ 8(SP), SI // argv JMP runtime·rt0_go(SB)
TEXT runtime·rt0_go(SB),NOSPLIT|NOFRAME|TOPFRAME,$0 //標誌 NOSPLIT 表示函式不會引發棧分裂,NOFRAME 表示函式沒有棧幀,TOPFRAME 表示這是棧上的頂層幀 // copy arguments forward on an even stack MOVQ DI, AX // argc 將暫存器 DI 中的值(argc,即引數計數)移動到暫存器 AX 中。 MOVQ SI, BX // argv 將暫存器 SI 中的值(argv,即引數陣列)移動到暫存器 BX 中。 SUBQ $(5*8), SP // 3args 2auto 將棧指標 SP 減少 40 位元組(5*8),為 3 個引數和 2 個自動變數留出空間。 ANDQ $~15, SP MOVQ AX, 24(SP) // 將引數計數 AX 儲存到棧上的偏移 24 處 MOVQ BX, 32(SP) // 同理 // create istack out of the given (operating system) stack. // _cgo_init may update stackguard. MOVQ $runtime·g0(SB), DI // 將全域性變數 runtime.g0 的地址載入到暫存器 DI 中 LEAQ (-64*1024)(SP), BX // 將棧指標 SP 減少 64KB,結果儲存在暫存器 BX 中 MOVQ BX, g_stackguard0(DI) // 將 BX(新的棧指標值)儲存到 g0 結構的 stackguard0 欄位中 MOVQ BX, g_stackguard1(DI) MOVQ BX, (g_stack+stack_lo)(DI) // 將 BX(新的棧指標值)儲存到 g0 結構的 stack.lo 欄位中 MOVQ SP, (g_stack+stack_hi)(DI) // find out information about the processor we're on MOVL $0, AX CPUID // 獲取cpu 資訊 CMPL AX, $0 JE nocpuinfo CMPL BX, $0x756E6547 // "Genu" JNE notintel // 如果不相等,跳轉到 notintel 標籤 CMPL DX, $0x49656E69 // "ineI" JNE notintel CMPL CX, $0x6C65746E // "ntel" JNE notintel MOVB $1, runtime·isIntel(SB) // 將 1 儲存到 runtime.isIntel 變數中,表示當前 CPU 是 Intel
TEXT runtime·rt0_go(SB),NOSPLIT|NOFRAME|TOPFRAME,$0 .....省略到 ok 標籤... ok: .....省略一些檢查程式碼... CALL runtime·check(SB) // 呼叫runtime庫的 check 函式 MOVL 24(SP), AX // copy argc MOVL AX, 0(SP) MOVQ 32(SP), AX // copy argv MOVQ AX, 8(SP) CALL runtime·args(SB) // runtime庫初始化處理命令列引數。 CALL runtime·osinit(SB) // 呼叫 runtime·osinit 函式,進行作業系統相關的初始化工作。 CALL runtime·schedinit(SB) // 呼叫 runtime·schedinit 函式,進行排程器的初始化 // create a new goroutine to start program MOVQ $runtime·mainPC(SB), AX // entry 將 runtime·mainPC 的地址載入到暫存器 AX 中,是程式的入口點 PUSHQ AX // 將暫存器 AX 中的值推入棧中,即將入口點的地址壓入棧頂 CALL runtime·newproc(SB) // 呼叫 runtime·newproc 函式,建立一個新的 goroutine 來執行程式的入口點 POPQ AX // start this M CALL runtime·mstart(SB) // 呼叫 runtime·mstart 函式,啟動當前的 M(機器執行緒) CALL runtime·abort(SB) // mstart should never return 呼叫 runtime·abort 函式,此處是在程式中斷或異常情況下的處理方式 RET
TEXT runtime·mstart(SB),NOSPLIT|TOPFRAME|NOFRAME,$0 CALL runtime·mstart0(SB) // 啟動runtime庫的 mstart0
RET // not reached
2.2 關鍵程式碼
從上述彙編程式碼可以看到,ok標籤下執行到 runtime庫的 osinit -> schedinit -> newproc -> mstart -> mstart0 彙編引導部分就結束了,
其中
- osinit:src/runtime/os_linux.go:343 func osinit()
- schedinit:src/runtime/proc.go:750 func schedinit()
- newproc:src/runtime/proc.go:4874 func newproc(fn *funcval)
- mstart :src/runtime/asm_amd64.s:393 TEXT runtime·mstart(SB),NOSPLIT|TOPFRAME|NOFRAME,$0
- mstart0:src/runtime/proc.go:1660 func mstart0()
再到 mstart0 -> mstart1 -> schedule 排程器就正式執行起來了
- mstart1:src/runtime/proc.go:1702 func mstart1()
- schedule:src/runtime/proc.go:3839 func schedule()
參考文章