package main
import "fmt"
func main() {
fmt.Println("Hello World!")
}
通过跟踪上述经典案例从启动到退出的完整运行流程,来分析Go调度器的初始化,以及goroutine的创建和退出,还有工作线程的调度循环和goroutine的切换等内容。
先从程序开始启动时分析调度器初始化。
在此之前先看下程序在执行第一条指令之前栈的初始状态。
任何由编译型语言(C、C++、Go以及汇编语言)编写的程序在被操作系统加载执行的顺序都会经过如下几个阶段:
-
从磁盘上将可执行程序读入内存。
-
创建进程和主线程。
-
为主线程分配栈空间。
-
将用户在命令行输入的参数拷贝到主线程的栈。
-
将主线程放入操作系统的运行队列,等待被调度起来执行。
在主线程被第一次调度起来执行第一条指令之前,主线程函数栈的状态如下所示:
之后在Linux平台运行go build编译hello.go得到可执行hello文件,再使用gdb调试。
在gdb中首先使用info files找到程序入口(Entry point)地址为0x452270,然后使用b*0x452270在0x452270处下个断点,gdb就会显示出来入口对应的源码为文件runtime/rt0_linux_amd64.s第8行。
go$ go build hello.go
go$ gdb hello
GNU gdb (GDB) 8.0.1
(gdb) info files
Symbols from "go/main".
Local exec file:
`go/main', file type elf64-x86-64.
Entry point: 0x452270
0x0000000000401000 - 0x0000000000486aac is .text
0x0000000000487000 - 0x00000000004d1a73 is .rodata
0x00000000004d1c20 - 0x00000000004d27f0 is .typelink
0x00000000004d27f0 - 0x00000000004d2838 is .itablink
0x00000000004d2838 - 0x00000000004d2838 is .gosymtab
0x00000000004d2840 - 0x00000000005426d9 is .gopclntab
0x0000000000543000 - 0x000000000054fa9c is .noptrdata
0x000000000054faa0 - 0x0000000000556790 is .data
0x00000000005567a0 - 0x0000000000571ef0 is .bss
0x0000000000571f00 - 0x0000000000574658 is .noptrbss
0x0000000000400f9c - 0x0000000000401000 is .note.go.buildid
(gdb) b *0x452270
Breakpoint 1 at 0x452270: file /usr/local/go/src/runtime/rt0_linux_amd64.s, line 8.
使用代码编辑器找到runtime/rt0_linux_amd64.s文件,它是由Go汇编语言编写的源码文件,可参考《试着理解下Golang中的汇编语言》来阅读和理解以下代码,来看第8行:
TEXT _rt0_amd64_linux(SB),NOSPLIT,$-8
JMP_rt0_amd64(SB)
上述代码第一行定义了_rt0_amd64_linux符号,并不是真正的CPU指令,下面的JMP才是主线程的第一条指令,这条指令意为跳到_rt0_amd64符号处继续执行(相当于Go/C的goto关键字)。
_rt0_amd64定义在runtime/asm_amd64.s文件第14行,代码如下:
TEXT _rt0_amd64(SB),NOSPLIT,$-8
MOVQ0(SP), DI// argc
LEAQ8(SP), SI // argv
JMPruntime·rt0_go(SB)
前两行指令将操作系统内核传递过来的argc和argv两个参数地址分别放在DI和SI寄存器中,第三行指令意为跳到rt0_go符号处继续执行。
rt0_go完成了Go程序启动时所有初始化工作,此函数比较长,也很复杂,所以只看调度器相关的初始化。
先来看runtime/asm_amd64.s文件第87行定义的符号rt0_go:
TEXT runtime·rt0_go(SB),NOSPLIT,$0
// copy arguments forward on an even stack