为什么main函数是终结者

来一个hello, world!

package main

func main() {
    println("hello, world!")
} // line 5

编译调试。

# go build -o debug_main main.go // 编译
# gdb debug_main                 // 开始调试
(gdb) b 5 // 在第5行打断点
(gdb) r   // 执行,这时代码停在第5行,还在main函数中,其实在二进制文件里面它符号是main_main
(gdb) s   // 单步往下走,进入runtime.main代码
runtime.main () at /home/zenk/tools/goroot/src/runtime/proc.go:207
207             if atomic.Load(&runningPanicDefers) != 0 {
(gdb) bt  // 查看调用栈
#0  runtime.main () at /home/zenk/tools/goroot/src/runtime/proc.go:207
#1  0x0000000000446891 in runtime.goexit () at /home/zenk/tools/goroot/src/runtime/asm_amd64.s:2361
#2  0x0000000000000000 in ?? ()
(gdb) s
216             if atomic.Load(&panicking) != 0 {
(gdb) s
220             exit(0)
(gdb) s
runtime.exit () at /home/zenk/tools/goroot/src/runtime/sys_linux_amd64.s:52
52              MOVL    code+0(FP), DI

从上面的结果可以知道,自己写的main函数被编译成main_main,然后被runtime.main所调用。通过查看runtime.main可以看到以下代码,说明它执行结束以后会调用exit(0)

   // file: goroot/src/runtime/proc.go: main()
   220   exit(0)
   221   for {
   222     var x *int32
   223     *x = 0
   224   }

查看exit函数代码,它调用了系统调用exit_group,退出所有线程。

// file: goroot/src/runtime/sys_linux_amd64.s:52
 51 TEXT runtime·exit(SB),NOSPLIT,$0-4
 52     MOVL    code+0(FP), DI
 53     MOVL    $SYS_exit_group, AX
 54     SYSCALL
 55     RET

还有一个比较有意思的事情是当查看调用栈的时候,显示runtime.main是通过runtime.goexit()调用的。其实这是因为创建goroutine的时候会把goexit的地址加1这个值放到它的栈顶,这样goroutine的函数执行完毕就会接着执行goexitgoexit又会调用schedule这个函数,继续寻找goroutine并执行。

goexit放到goroutine的栈顶代码:

// file: goroot/src/runtime/proc.go:newproc1(fn *funcval, argp *uint8, narg int32, callerpc uintptr)

3315   newg.sched.pc = funcPC(goexit) + sys.PCQuantum // +PCQuantum so that previous instruction is in same function
3316   newg.sched.g = guintptr(unsafe.Pointer(newg))
3317   gostartcallfn(&newg.sched, fn)
            |
            V
// file: goroot/src/runtime/stack.go:func gostartcallfn(gobuf *gobuf, fv *funcval) 
1085   gostartcall(gobuf, fn, unsafe.Pointer(fv))
            |
            V
// file: goroot/src/runtime/sys_x86.go:func gostartcall(buf *gobuf, fn, ctxt unsafe.Pointer)
22   sp -= sys.PtrSize
23   *(*uintptr)(unsafe.Pointer(sp)) = buf.pc // 这里的pc就是 funcPC(goexit) + sys.PCQuantum

再看runtime.goexit代码:

// file: goroot/src/runtime/asm_amd64.s
2360 TEXT runtime·goexit(SB),NOSPLIT,$0-0
2361     BYTE    $0x90   // NOP
2362     CALL    runtime·goexit1(SB) // does not return
2363     // traceback from goexit1 must hit code range of goexit
2364     BYTE    $0x90   // NOP

因为压入goroutine栈的值是runtime.goexit+sys.PCQuantum(=1),因此goroutine函数返回的时候会执行第2361行代码。

← RocketMQ offset管理 高可用 →
存档 关于