本节的重点主要有:
非main goroutine如何返回到goexit的?
mcall如何切换到g0继续执行的?
调度循环。
有如下代码:
packagemainimport"time"funchello(){println("msg")}funcmain(){gohello()time.Sleep(time.Hour*1)}
编译go build -gcflags "-N -l" -ldflags=-compressdwarf=false main.go
使用gdb调试:gdb main
(gdb)bmain.helloBreakpoint1at0x1057240:file/Users/zixuan.xu/workspace/study/learn-go-plan9/goroutine/main.go,line5.(gdb)rStartingprogram:/Users/zixuan.xu/workspace/study/learn-go-plan9/goroutine/main[NewThread0x1a03ofprocess88861][NewThread0x1b03ofprocess88861]warning:unhandleddyldversion(17)[NewThread0x1d03ofprocess88861][NewThread0x1f07ofprocess88861][NewThread0x2307ofprocess88861][NewThread0x5403ofprocess88861][SwitchingtoThread0x2307ofprocess88861]Thread5hitBreakpoint1,main.hello()at/Users/zixuan.xu/workspace/study/learn-go-plan9/goroutine/main.go:55funchello(){(gdb)disasDumpofassemblercodeforfunctionmain.hello:=>0x0000000001057240<+0>:cmp0x10(%r14),%rsp0x0000000001057244<+4>:jbe0x1057279<main.hello+57>0x0000000001057246<+6>:sub$0x18,%rsp0x000000000105724a<+10>:mov%rbp,0x10(%rsp)0x000000000105724f<+15>:lea0x10(%rsp),%rbp0x0000000001057254<+20>:call0x102d2e0<runtime.printlock>0x0000000001057259<+25>:lea0xcc55(%rip),%rax#0x1063eb5<go.string.*+309>0x0000000001057260<+32>:mov$0x4,%ebx0x0000000001057265<+37>:call0x102dc00<runtime.printstring>0x000000000105726a<+42>:call0x102d360<runtime.printunlock>0x000000000105726f<+47>:mov0x10(%rsp),%rbp0x0000000001057274<+52>:add$0x18,%rsp0x0000000001057278<+56>:ret0x0000000001057279<+57>:call0x1052da0<runtime.morestack_noctxt>0x000000000105727e<+62>:xchg%ax,%ax0x0000000001057280<+64>:jmp0x1057240<main.hello>Endofassemblerdump.(gdb)b*0x0000000001057278Breakpoint2at0x1057278:file/Users/zixuan.xu/workspace/study/learn-go-plan9/goroutine/main.go,line7.(gdb)cContinuing.msgThread5hitBreakpoint2,0x0000000001057278inmain.hello()at/Users/zixuan.xu/workspace/study/learn-go-plan9/goroutine/main.go:77}(gdb)disasDumpofassemblercodeforfunctionmain.hello:0x0000000001057240<+0>:cmp0x10(%r14),%rsp0x0000000001057244<+4>:jbe0x1057279<main.hello+57>0x0000000001057246<+6>:sub$0x18,%rsp0x000000000105724a<+10>:mov%rbp,0x10(%rsp)0x000000000105724f<+15>:lea0x10(%rsp),%rbp0x0000000001057254<+20>:call0x102d2e0<runtime.printlock>0x0000000001057259<+25>:lea0xcc55(%rip),%rax#0x1063eb5<go.string.*+309>0x0000000001057260<+32>:mov$0x4,%ebx0x0000000001057265<+37>:call0x102dc00<runtime.printstring>0x000000000105726a<+42>:call0x102d360<runtime.printunlock>0x000000000105726f<+47>:mov0x10(%rsp),%rbp0x0000000001057274<+52>:add$0x18,%rsp=>0x0000000001057278<+56>:ret0x0000000001057279<+57>:call0x1052da0<runtime.morestack_noctxt>0x000000000105727e<+62>:xchg%ax,%ax0x0000000001057280<+64>:jmp0x1057240<main.hello>Endofassemblerdump.(gdb)siruntime.goexit()at/Users/zixuan.xu/.gvm/gos/go1.17/src/runtime/asm_amd64.s:15821582CALLruntime·goexit1(SB)//doesnotreturn(gdb)disasDumpofassemblercodeforfunctionruntime.goexit:0x0000000001053060<+0>:nop=>0x0000000001053061<+1>:call0x10551e0<runtime.goexit1>0x0000000001053066<+6>:nopEndofassemblerdump.
在main.hello下断点,并执行到这里
在ret处下断点,并执行到ret位置
使用si进入ret的调用,能看到进入的是goexit这个函数
asm_amd64.s:1580
//Thetop-mostfunctionrunningonagoroutine//returnstogoexit+PCQuantum.TEXTruntime·goexit(SB),NOSPLIT|TOPFRAME,$0-0BYTE$0x90//NOPCALLruntime·goexit1(SB)//doesnotreturn//tracebackfromgoexit1musthitcoderangeofgoexitBYTE$0x90//NOP
其实si调用完,指向的指令就是goexit的第二行指令,就是上一节中newg.sched.sp
字段。 现在也可以证明非girouitne执行完并不会退出
proc.go:3622
接下来调用goexit1
//Finishesexecutionofthecurrentgoroutine.funcgoexit1(){ifraceenabled{racegoend()}iftrace.enabled{traceGoEnd()}mcall(goexit0)}
这里通过mcall
从goroutine
的栈切换到g0
的栈
asm_amd64.s:282
//funcmcall(fnfunc(*g))//Switchtom->g0'sstack,callfn(g).//Fnmustneverreturn.Itshouldgogo(&g->sched)//tokeeprunningg.#ifdefGOEXPERIMENT_regabiargsTEXTruntime·mcall<ABIInternal>(SB),NOSPLIT,$0-8MOVQAX,DX//DX=fn//savestateing->schedMOVQ0(SP),BX//caller'sPCMOVQBX,(g_sched+gobuf_pc)(R14)//newg.sched.pc=returnaddrLEAQfn+0(FP),BX//caller'sSP//BX=&fnMOVQBX,(g_sched+gobuf_sp)(R14)//newg.sched.sp=&fnMOVQBP,(g_sched+gobuf_bp)(R14)//newg.sched.bp=BP//switchtom->g0&itsstack,callfnMOVQg_m(R14),BX//BX=newg.mMOVQm_g0(BX),SI//SI=g.m.g0CMPQSI,R14//ifg==m->g0callbadmcallJNEgoodmJMPruntime·badmcall(SB)goodm:MOVQR14,AX//AX(andarg0)=gMOVQSI,R14//g=g.m.g0get_tls(CX)//SetGinTLSMOVQR14,g(CX)MOVQ(g_sched+gobuf_sp)(R14),SP//sp=g0.sched.spPUSHQAX//openupspaceforfn'sargspillslotMOVQ0(DX),R12CALLR12//fn(g)POPQAXJMPruntime·badmcall2(SB)RET#else
通过MOVQ (g_sched+gobuf_sp)(R14), SP
就能看出,sp切换到g0.sched.sp,所以每次栈每次都会切换到同样的位置
proc.go:3633
//goexitcontinuationong0.funcgoexit0(gp*g){_g_:=getg()casgstatus(gp,_Grunning,_Gdead)ifisSystemGoroutine(gp,false){atomic.Xadd(&sched.ngsys,-1)}gp.m=nillocked:=gp.lockedm!=0gp.lockedm=0_g_.m.lockedg=0gp.preemptStop=falsegp.paniconfault=falsegp._defer=nil//shouldbetruealreadybutjustincase.gp._panic=nil//non-nilforGoexitduringpanic.pointsatstack-allocateddata.gp.writebuf=nilgp.waitreason=0gp.param=nilgp.labels=nilgp.timer=nilifgcBlackenEnabled!=0&&gp.gcAssistBytes>0{//Flushassistcredittotheglobalpool.Thisgives//betterinformationtopacingiftheapplicationis//rapidlycreatinganexitinggoroutines.assistWorkPerByte:=float64frombits(atomic.Load64(&gcController.assistWorkPerByte))scanCredit:=int64(assistWorkPerByte*float64(gp.gcAssistBytes))atomic.Xaddint64(&gcController.bgScanCredit,scanCredit)gp.gcAssistBytes=0}dropg()ifGOARCH=="wasm"{//nothreadsyetonwasmgfput(_g_.m.p.ptr(),gp)schedule()//neverreturns}if_g_.m.lockedInt!=0{print("invalidm->lockedInt=",_g_.m.lockedInt,"\n")throw("internallockOSThreaderror")}gfput(_g_.m.p.ptr(),gp)iflocked{//Thegoroutinemayhavelockedthisthreadbecause//itputitinanunusualkernelstate.Killit//ratherthanreturningittothethreadpool.//Returntomstart,whichwillreleasethePandexit//thethread.ifGOOS!="plan9"{//Seegolang.org/issue/22227.gogo(&_g_.m.g0.sched)}else{//ClearlockedExtonplan9sincewemayendupre-using//thisthread._g_.m.lockedExt=0}}schedule()}
清理g的信息
将g放入gfree
继续调用schedule
总结下流程: