Go内核源码剖析 一 程序执行启动过程
来源:互联网 发布:php插件 贴吧 编辑:程序博客网 时间:2024/05/16 10:23
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 = c argv = v sysargs(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 call typelinksinit() // uses maps itabsinit() 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.mainBreakpoint 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 32bit if 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 defer needUnlock := true defer 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 = false unlockOSThread() 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 libfunc init(){ println("sum.init")}func Sum(x ...int)int{ n:=0 for _,i:=range x{ n+=i }return n}test.go
package mainimport ( "lib")func init() { println("test.init")}func test() { println(lib.Sum(1, 2, 3))}main.go
package mainimport ( _ "net/http" )func init() { println("main.init.2")}func main() { test()}func init() { println("main.init.1")}编译,执⾏输出。
$ go build -gcflags "-N -l" -o test$ ./testsum.initmain.init.2main.init.1test.init6接下来我们⽤反汇编⼯具,看看最终动态⽣成代码的真实⾯⽬:
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), AXmstats.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 UD2mstats.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 0x42394cpanic.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), BPproc.go:214 0x426861 ADDQ $0x18, SPproc.go:214 0x426865 RETproc.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 0x445dc0zcallback_windows.go:6 0x4460a4 CALL runtime.morestack_noctxt(SB)zcallback_windows.go:6 0x4460a9 JMP runtime.init(SB):-1 0x4460ae INT $0x3
runtime里面的多个init被赋予唯一的函数名,再由 runtime.init(SB)统一调用,⾄于 main.init,情况基本⼀致。区别在于它负责调⽤⾮ runtime 包的初始化函数:
$ go tool objdump -s "main\.init\b" testTEXT main.init.1(SB) src/main.gomain.go:7 ...TEXT main.init.2(SB) src/main.gomain.go:15 ...TEXT main.init.3(SB) src/test.gotest.go:7 ...TEXT main.init(SB) src/test.gotest.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内核源码剖析 一 程序执行启动过程
- MFC程序执行过程剖析
- MFC程序执行过程剖析
- MFC程序执行过程剖析
- MFC程序执行过程剖析
- 【Linux 1.0内核源码剖析】执行程序——exec.c
- Linux 内核中RAID5源码详解之写过程剖析(一)
- 《Unix内核源码剖析》读书笔记(一)
- 深入剖析 MFC程序的启动过程
- uboot内核启动过程源码分析
- 【PHP7内核剖析】3.3 Zend引擎执行过程
- Linux 文件系统、启动过程 之 内核剖析 读笔
- Android Framework启动过程(android内核剖析笔记)
- linux内核启动过程(一)
- linux内核init启动过程分析(一)
- go-ethereum源码剖析:交易
- Netty的启动执行过程分析(一)
- 【android内核剖析笔记】APK程序的运行过程
- shape 描边只描上下边
- React Native FlexBox布局
- Jmeter调试工具---HTTP Mirror Server
- net-snmp添加自定义MIB
- group_concat多列数据一列显示用法
- Go内核源码剖析 一 程序执行启动过程
- java之动态加载类
- dos2unix
- 汇总数据
- 几个常见的指标总结precision,recall,accuracy
- C++primer第4版第三章标准库类型
- jmap/jstack 在Windows系统下报错的解决
- xgboost WIN7 64的安装
- Android4.4.2 获取NTP时间,并设置系统时间