您的位置 首页 golang

Golang源码学习:调度逻辑(一)初始化

本文所使用的Golang为1.14,dlv为1.4.0。

源代码

package mainimport "fmt"func main() {fmt.Println("Hello")}

开始调试

root@xiamin:~/study# dlv debug test.goType '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。

(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:MOVQ0(SP), DI// argc,将参数个数存入DI    16:LEAQ8(SP), SI// argv,参数数组的地址存入SI    17:JMPruntime·rt0_go(SB)

继续执行,runtime.rt0_go() /root/go/src/runtime/asm_amd64.s:89 (PC: 0x461c30)

runtime.rt0_go

runtime.rt0_go中代码较多,但我们只关注与调度相关的。

TEXT runtime·rt0_go(SB),NOSPLIT,$0        // 忽略处理命令行参数相关        // 为全局变量g0设置一些栈相关的属性        MOVQ$runtime·g0(SB), DI// 将全局变量g0的存入DILEAQ(-64*1024+104)(SP), BX// bx = SP-(64*1024+104),g0的栈帧大小MOVQBX, g_stackguard0(DI)// g0.stackguard0 = bxMOVQBX, g_stackguard1(DI)// g0.stackguard1 = bxMOVQBX, (g_stack+stack_lo)(DI)// g0.stack.lo = bx    栈的低地址MOVQSP, (g_stack+stack_hi)(DI)// g0.stack.hi = sp    栈的高地址        // 忽略获取cpu型号等相关与cgo初始化        // 线程本地存储(tls)相关设置        LEAQruntime·m0+m_tls(SB), DI// di = &m0.tlsCALLruntime·settls(SB)// 设置tls,下面有详细分析        // 验证tls是否生效:通过tls设置一个数值,然后m0.tls[0]获取,与设置的值对比。get_tls(BX)// 获取fs地址到bxMOVQ$0x123, g(BX)// 反编译后 mov qword ptr fs:[0xfffffff8], 0x123,表示设置fs-8地址中的内容为0x123,其实就是m0.tls[0]的地址。MOVQruntime·m0+m_tls(SB), AX// ax = m0.tls[0]CMPQAX, $0x123// 比较JEQ 2(PC)CALLruntime·abort(SB)// m0.tls[0] = &g0;  g0与m0相互绑定get_tls(BX)// 获取fs地址到bxLEAQruntime·g0(SB), CX// cx = &g0MOVQCX, g(BX)// m0.tls[0] = &g0LEAQruntime·m0(SB), AX// ax = &m0MOVQCX, m_g0(AX)// m0.g0 = &g0MOVQAX, g_m(CX)// g0.m = &m0        // 忽略copy argc和argv的代码CALLruntime·args(SB)// 命令行参数相关,暂不关心CALLruntime·osinit(SB)// 设置全局变量ncpu(cpu个数),全局变量physHugePageSizeCALLruntime·schedinit(SB)// 调度器初始化        // 调用runtime·newproc创建goroutine,指向函数为runtime·mainMOVQ$runtime·mainPC(SB), AX// runtime·mainPC就是runtime·mainPUSHQAX// newproc的第二个参数,也就是goroutine要执行的函数。PUSHQ$0// newproc的第一个参数,表示要传入runtime·main中参数的大小,此处为0。// 创建 main goroutine。非main goroutine也是此方法创建。// go编译会将语句 go foo() 编译为 runtime·newproc(SB) 并传入参数。CALLruntime·newproc(SB)POPQAXPOPQAXCALLruntime·mstart(SB)// 进入调度循环CALLruntime·abort(SB)// mstart应该永不返回,如果返回,则是程序出现错误了。RETMOVQ$runtime·debugCallV1(SB), AXRETDATAruntime·mainPC+0(SB)/8,$runtime·main(SB)GLOBLruntime·mainPC(SB),RODATA,$8

runtime·settls 设置线程本地存储

runtime·settls中通过调用arch_prctl系统调用设置FS来实现线程本地存储。

通过syscall指令调用系统调用

  • rax存放系统调用号,调用返回值也会放在rax中
  • 当系统调用参数小于等于6个时,参数则须按顺序放到寄存器 rdi,rsi,rdx,r10,r8,r9中。
  • 如果系统调用的参数数量大于6个,需将参数保存在一块连续的内存中,并将地址存入rbx中。

新建非m0的m时也会通过runtime·clone调用此函数。

TEXT runtime·settls(SB),NOSPLIT,$32        // 此时di = &m.tls[0]ADDQ$8, DI// ELF 需要使用 -8(FS),di+=8,执行完此指令后 di = &m.tls[1]MOVQDI, SI// 将地址移动到si中,作为系统调用的第二个参数MOVQ$0x1002, DI// ARCH_SET_FS表示设置FS,作为系统调用的第一个参数MOVQ$SYS_arch_prctl, AX// rax存储系统调用号SYSCALLCMPQAX, $0xfffffffffffff001// 比较返回结果JLS2(PC)MOVL$0xf1, 0xf1  // crashRET

runtime.schedinit 调度初始化

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,则为GOMAXPROCSprocs := ncpuif n, ok := atoi32(gogetenv("GOMAXPROCS")); ok && n > 0 {procs = n}        // 初始化allp并为allp中的元素初始化、赋值等,详见下方if procresize(procs) != nil {throw("unknown runnable goroutine during bootstrap")}        ..........}

schedinit->mcommoninit

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的idsched.mnext++// 加1,以后分配给下一个mcheckmcount()// 检查非空闲数量的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 = *m0mpreinit(mp)if mp.gsignal != nil {mp.gsignal.stackguard1 = mp.gsignal.stack.lo + _StackGuard}                // 下面两步将mp放入全局变量allm中,allm是个链表mp.alllink = allmatomicstorep(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的初始化。

schedinit->procresize

// 传入参数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// 初始化allpif 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赋值给全局变量allpnallp := 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 所以走elseif _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 = nilp := allp[0]p.m = 0p.status = _Pidleacquirep(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 *pfor i := nprocs - 1; i >= 0; i-- {p := allp[i]if _g_.m.p.ptr() == p {// 是否是当前g.m的pcontinue}p.status = _Pidleif 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 int32atomic.Store((*uint32)(unsafe.Pointer(int32p)), uint32(nprocs))return runnablePs}

总结一下procresize的工作:

  • allp切片中p的数量小于期望p数量时,对allp进行扩容
  • 使用new创建p并调用p.init初始化刚扩容出的,init中为p分配id和mcache
  • 初始化时,调用acquirep使allp[0]与m0相互绑定,并且m.mcache = p.mcache,p.status = _Prunning
  • allp切片中p的数量大于期望p数量时,调用p.destroy释放未使用的p的资源
  • 将除了allp[0]之外的p状态设置为_Pidle并加入到全局空闲列表sched.pidle中
  • 更改gomaxprocs值为nprocs

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的状态}

文章来源:智云一二三科技

文章标题:Golang源码学习:调度逻辑(一)初始化

文章地址:https://www.zhihuclub.com/1108.shtml

关于作者: 智云科技

热门文章

网站地图