引导启动和初始化
1.引导启动
1.1启动函数入口
1.1.1 准备运行文件
hello.go
func main() {
fmt.Println("hello world")
}
使用-gcflags "-N -l" 参数关闭编译器代码优化和函数内联,避免断点和单步执行无法准确对应源码行数,避免小函数和局部变量被优化掉。
go build -gcflags "-N -l" -o hello hello.go
1.1.2 找到入口文件
用GDB 设置断点命令看看这个 rt0_go入门文件在什么位置。
根据系统平台架构不相同,入口文件也会不一样,这里的入口文件在/usr/local/go/src/runtime/rt0_linux_amd64.s第八行。
gdb hello
(gdb) info files
Local exec file:
Entry point: 0x4561d0
(gdb) b *0x4561d0
Breakpoint 1 at 0x4561d0: file /usr/local/go/src/runtime/rt0_linux_amd64.s, line 8.
1.1.3 查看入口源码
TEXT _rt0_amd64_linux(SB),NOSPLIT,$-8
…省略代码…
MOVQ $main(SB), AX //跳转到62行
TEXT main(SB),NOSPLIT,$-8
MOVQ $runtime·rt0_go(SB), AX
JMP AX
注意:源码文件中的 “·” 符号编译后变成正常的 “.”。
(gdb) b runtime.rt0_go
Breakpoint 2 at 0x44a780: file /usr/local/go/src/runtime/asm_amd64.s, line 12.
TEXT runtime·rt0_go(SB),NOSPLIT,$0
…省略代码…
// 初始化函数
CALL runtime·args(SB)
CALL runtime·osinit(SB)
CALL runtime·schedinit(SB)
// 准备执行runtime.main
MOVQ $runtime·mainPC(SB), AX
//创建main goroutine
CALL runtime·newproc(SB)
// 让当前线程开始执行main goroutine
CALL runtime·mstart(SB)
RET
DATA runtime·mainPC+0(SB)/8,$runtime·main(SB)
GLOBL runtime·mainPC(SB),RODATA,$8
1.2 初始化流程
1.2.1 runtime.args
将参数(argc 和 argv )存储到静态变量中。
(gdb) b runtime.args
Breakpoint 4 at 0x434ec0: file /usr/local/go/src/runtime/runtime1.go, line 48.
func args(c int32, v **byte) {
argc = c
argv = v
sysargs(c, v)
}
1.2.2 runtime.osinit
在启动过程中接下来调用的是 runtime.osinit 函数。在 Linux 系统上,这个函数唯 一做的事就是初始化 ncpu 变量,这个变量存储了当前系统的 CPU 的数量。这是通过一个系统调用来实现的。
(gdb) b runtime.osinit
Breakpoint 5 at 0x424290: file /usr/local/go/src/runtime/os1_linux.go, line 172.
func osinit() {
ncpu = getproccount()
}
1.2.3 runtime.schedinit
接下便调用了 runtime.schedinit 函数,这个函数比较有意思。首先,它获得当前 goroutine 的指针,该指针指向一个 g 结构体。在讨论 TLS 实现的时候,我们就已经讨论过这个指针是如何存储的。接下来,它会调用 runtime.raceinit。这里我们不会讨论 runtime.raceinit 函数,因为正常情况下竞争条件(race condition)被禁止时,这个函数是不会被调用的。随后,runtime.schedinit 函数中还会调用另外一些初始化函数。
(gdb) b runtime.schedinit
Breakpoint 6 at 0x429eb0: file /usr/local/go/src/runtime/proc1.go, line 40.
//运用时环境初始化构造都在里被调,内存分配,垃圾回收,调度器等。
func schedinit() {
//最大系统线程数量设置
sched.maxmcount = 10000
//栈空间复用链表初始化
stackinit()
//内存分配器初始化
mallocinit()
//初始化当前M
mcommoninit(_g_.m)
//垃圾回收初始化
gcinit()
//初始化,调整P的数量
if procresize(int32(procs)) != nil {
throw("unknown runnable goroutine during bootstrap")
}
}
1.2.4 runtime.main
接下来要执行的是 runtime.main,而不是用户逻辑入口函数 main.main。
(gdb) b runtime.main
Breakpoint 10 at 0x428b70: file /usr/local/go/src/runtime/proc.go, line 28.
func main() {
//系统后台监控(内存释放,并发调度,垃圾回收监控)
systemstack(func() {
newm(sysmon, nil)
})
//执行runtime包所有的init函数
runtime_init()
//启动垃圾回收器(后台运行)
gcenable()
//执行用户用户包init(包括标准库)
main_init()
//执行用户逻辑入口,main.main函数
main_main()
//执行结束
exit(0)
}
参考资料:http://blog.jobbole.com/author/scbzyhx/