本文所使用的Golang为1.14,dlv为1.4.0。linux
package main import "fmt" func main() { fmt.Println("Hello") }
root@xiamin:~/study# dlv debug test.go Type 'help' for list of commands. (dlv) l > _rt0_amd64_linux() /root/go/src/runtime/rt0_linux_amd64.s:8 (PC: 0x465800) Warning: debugging optimized function 3: // license that can be found in the LICENSE file. 4: 5: #include "textflag.h" 6: 7: TEXT _rt0_amd64_linux(SB),NOSPLIT,$-8 => 8: JMP _rt0_amd64(SB) 9: 10: TEXT _rt0_amd64_linux_lib(SB),NOSPLIT,$0 11: JMP _rt0_amd64_lib(SB)
能够看到最开始是从_rt0_amd64_linux执行,而后直接跳转到_rt0_amd64。执行si进入_rt0_amd64。bootstrap
(dlv) si > _rt0_amd64() /root/go/src/runtime/asm_amd64.s:15 (PC: 0x461c20) Warning: debugging optimized function 10: // _rt0_amd64 is common startup code for most amd64 systems when using 11: // internal linking. This is the entry point for the program from the 12: // kernel for an ordinary -buildmode=exe program. The stack holds the 13: // number of arguments and the C-style argv. 14: TEXT _rt0_amd64(SB),NOSPLIT,$-8 => 15: MOVQ 0(SP), DI // argc,将参数个数存入DI 16: LEAQ 8(SP), SI // argv,参数数组的地址存入SI 17: JMP runtime·rt0_go(SB)
继续执行,runtime.rt0_go() /root/go/src/runtime/asm_amd64.s:89 (PC: 0x461c30)windows
runtime.rt0_go中代码较多,但咱们只关注与调度相关的。数组
TEXT runtime·rt0_go(SB),NOSPLIT,$0 // 忽略处理命令行参数相关 // 为全局变量g0设置一些栈相关的属性 MOVQ $runtime·g0(SB), DI // 将全局变量g0的存入DI LEAQ (-64*1024+104)(SP), BX // bx = SP-(64*1024+104),g0的栈帧大小 MOVQ BX, g_stackguard0(DI) // g0.stackguard0 = bx MOVQ BX, g_stackguard1(DI) // g0.stackguard1 = bx MOVQ BX, (g_stack+stack_lo)(DI) // g0.stack.lo = bx 栈的低地址 MOVQ SP, (g_stack+stack_hi)(DI) // g0.stack.hi = sp 栈的高地址 // 忽略获取cpu型号等相关与cgo初始化 // 线程本地存储(tls)相关设置 LEAQ runtime·m0+m_tls(SB), DI // di = &m0.tls CALL runtime·settls(SB) // 设置tls,下面有详细分析 // 验证tls是否生效:经过tls设置一个数值,而后m0.tls[0]获取,与设置的值对比。 get_tls(BX) // 获取fs地址到bx MOVQ $0x123, g(BX) // 反编译后 mov qword ptr fs:[0xfffffff8], 0x123,表示设置fs-8地址中的内容为0x123,其实就是m0.tls[0]的地址。 MOVQ runtime·m0+m_tls(SB), AX // ax = m0.tls[0] CMPQ AX, $0x123 // 比较 JEQ 2(PC) CALL runtime·abort(SB) // m0.tls[0] = &g0; g0与m0相互绑定 get_tls(BX) // 获取fs地址到bx LEAQ runtime·g0(SB), CX // cx = &g0 MOVQ CX, g(BX) // m0.tls[0] = &g0 LEAQ runtime·m0(SB), AX // ax = &m0 MOVQ CX, m_g0(AX) // m0.g0 = &g0 MOVQ AX, g_m(CX) // g0.m = &m0 // 忽略copy argc和argv的代码 CALL runtime·args(SB) // 命令行参数相关,暂不关心 CALL runtime·osinit(SB) // 设置全局变量ncpu(cpu个数),全局变量physHugePageSize CALL runtime·schedinit(SB) // 调度器初始化 // 调用runtime·newproc建立goroutine,指向函数为runtime·main MOVQ $runtime·mainPC(SB), AX // runtime·mainPC就是runtime·main PUSHQ AX // newproc的第二个参数,也就是goroutine要执行的函数。 PUSHQ $0 // newproc的第一个参数,表示要传入runtime·main中参数的大小,此处为0。 // 建立 main goroutine。非main goroutine也是此方法建立。 // go编译会将语句 go foo() 编译为 runtime·newproc(SB) 并传入参数。 CALL runtime·newproc(SB) POPQ AX POPQ AX CALL runtime·mstart(SB) // 进入调度循环 CALL runtime·abort(SB) // mstart应该永不返回,若是返回,则是程序出现错误了。 RET MOVQ $runtime·debugCallV1(SB), AX RET DATA runtime·mainPC+0(SB)/8,$runtime·main(SB) GLOBL runtime·mainPC(SB),RODATA,$8
runtime·settls中经过调用arch_prctl系统调用设置FS来实现线程本地存储。函数
经过syscall指令调用系统调用ui
新建非m0的m时也会经过runtime·clone调用此函数。atom
TEXT runtime·settls(SB),NOSPLIT,$32 // 此时di = &m.tls[0] ADDQ $8, DI // ELF 须要使用 -8(FS),di+=8,执行完此指令后 di = &m.tls[1] MOVQ DI, SI // 将地址移动到si中,做为系统调用的第二个参数 MOVQ $0x1002, DI // ARCH_SET_FS表示设置FS,做为系统调用的第一个参数 MOVQ $SYS_arch_prctl, AX // rax存储系统调用号 SYSCALL CMPQ AX, $0xfffffffffffff001 // 比较返回结果 JLS 2(PC) MOVL $0xf1, 0xf1 // crash RET
runtime.schedinit中包含了不少功能的初始化,本文暂且分析与调度相关的命令行
func schedinit() { _g_ := getg() // 未找到getg()的源代码,经过注释得知getg()返回当前g,此处 _g_为&g0 .......... sched.maxmcount = 10000 // m的最大数量为10000 .......... mcommoninit(_g_.m) // 此处_g_.m即为m0,对m0的一些初始化工做,下面详细分析 .......... // 获取要初始化的p的数量,默认与cpu个数相同,若是指定了GOMAXPROCS,则为GOMAXPROCS procs := ncpu if n, ok := atoi32(gogetenv("GOMAXPROCS")); ok && n > 0 { procs = n } // 初始化allp并为allp中的元素初始化、赋值等,详见下方 if procresize(procs) != nil { throw("unknown runnable goroutine during bootstrap") } .......... }
func mcommoninit(mp *m) { _g_ := getg() // 获取当前g,也就是g0 // g0 stack won't make sense for user (and is not necessary unwindable). if _g_ != _g_.m.g0 { callers(1, mp.createstack[:]) // 调用栈相关 } lock(&sched.lock) if sched.mnext+1 < sched.mnext { throw("runtime: thread ID overflow") } mp.id = sched.mnext // 设置m的id sched.mnext++ // 加1,之后分配给下一个m checkmcount() // 检查非空闲数量的m是否超过了10000 // rand相关 mp.fastrand[0] = uint32(int64Hash(uint64(mp.id), fastrandseed)) mp.fastrand[1] = uint32(int64Hash(uint64(cputicks()), ^fastrandseed)) if mp.fastrand[0]|mp.fastrand[1] == 0 { mp.fastrand[1] = 1 } // 新建一个32k栈大小的g,赋值给m0.gsignal。并使 m0.gsignal.m = *m0 mpreinit(mp) if mp.gsignal != nil { mp.gsignal.stackguard1 = mp.gsignal.stack.lo + _StackGuard } // 下面两步将mp放入全局变量allm中,allm是个链表 mp.alllink = allm atomicstorep(unsafe.Pointer(&allm), unsafe.Pointer(mp)) unlock(&sched.lock) // Allocate memory to hold a cgo traceback if the cgo call crashes. if iscgo || GOOS == "solaris" || GOOS == "illumos" || GOOS == "windows" { mp.cgoCallers = new(cgoCallers) } }
mcommoninit基本上就是作一些m0的初始化。线程
// 传入参数nprocs为指望的全部p的个数 func procresize(nprocs int32) *p { old := gomaxprocs // gomaxprocs在本方法的末尾会被更改 if old < 0 || nprocs <= 0 { throw("procresize: invalid arg") } if trace.enabled { traceGomaxprocs(nprocs) } // 更新统计信息 now := nanotime() if sched.procresizetime != 0 { sched.totaltime += int64(old) * (now - sched.procresizetime) } sched.procresizetime = now // 初始化allp if nprocs > int32(len(allp)) { // Synchronize with retake, which could be running // concurrently since it doesn't run on a P. lock(&allpLock) if nprocs <= int32(cap(allp)) { allp = allp[:nprocs] } else { // 初始化一个临时变量nallp,与现存的allp合并,而后将nallp赋值给全局变量allp nallp := make([]*p, nprocs) copy(nallp, allp[:cap(allp)]) allp = nallp } unlock(&allpLock) } // 初始化新添加到allp中的元素 for i := old; i < nprocs; i++ { pp := allp[i] if pp == nil { pp = new(p) } pp.init(i) // 会初始化p结构的属性:id,status,mcache等 atomicstorep(unsafe.Pointer(&allp[i]), unsafe.Pointer(pp)) // 赋值 } _g_ := getg() // 初始化的时候 _g_.m.p = 0 因此走else if _g_.m.p != 0 && _g_.m.p.ptr().id < nprocs { // continue to use the current P _g_.m.p.ptr().status = _Prunning _g_.m.p.ptr().mcache.prepareForSweep() } else { // 此处省略一些初始化时不会进入的代码 _g_.m.p = 0 _g_.m.mcache = nil p := allp[0] p.m = 0 p.status = _Pidle acquirep(p) // m.mcache = p.mcache;p和m相互绑定;p.status = _Prunning。下面有分析。 if trace.enabled { traceGoStart() } } // 释放未使用的p的资源,好比调用runtime.GOMAXPROCS(num),会调用procresize。 // num小于当前p的数量时,会执行此处 for i := nprocs; i < old; i++ { p := allp[i] p.destroy() // can't free P itself because it can be referenced by an M in syscall } // Trim allp. if int32(len(allp)) != nprocs { lock(&allpLock) allp = allp[:nprocs] unlock(&allpLock) } // 将除了当前m绑定p的其他allp中的都以链表形式存入sched.pidle中 var runnablePs *p for i := nprocs - 1; i >= 0; i-- { p := allp[i] if _g_.m.p.ptr() == p { // 是不是当前g.m的p continue } p.status = _Pidle if runqempty(p) { pidleput(p) // 将p放入到空闲列表中 } else { p.m.set(mget()) p.link.set(runnablePs) runnablePs = p } } // 这里会更改gomaxprocs的值 stealOrder.reset(uint32(nprocs)) var int32p *int32 = &gomaxprocs // make compiler check that gomaxprocs is an int32 atomic.Store((*uint32)(unsafe.Pointer(int32p)), uint32(nprocs)) return runnablePs }
总结一下procresize的工做:debug
acquirep(p)->wirep(_p_) :acquirep中的主要逻辑就是调用了wirep
func wirep(_p_ *p) { _g_ := getg() if _g_.m.p != 0 || _g_.m.mcache != nil { throw("wirep: already in go") } if _p_.m != 0 || _p_.status != _Pidle { id := int64(0) if _p_.m != 0 { id = _p_.m.ptr().id } print("wirep: p->m=", _p_.m, "(", id, ") p->status=", _p_.status, "\n") throw("wirep: invalid p state") } _g_.m.mcache = _p_.mcache // p的mcache赋值给m.mcache _g_.m.p.set(_p_) // 与下面的一行为 p和m相互绑定 _p_.m.set(_g_.m) _p_.status = _Prunning // 更改p的状态 }