The main points of this section are:
- How does a non-main goroutine return to Goexit
- How does McAll switch to G0 to continue execution
- Scheduling cycle
There is the following code
package main
import "time"
func hello(a) {
println("msg")}func main(a) {
go hello()
time.Sleep(time.Hour * 1)}Copy the code
Compile go build-gcflags “-n-l” -ldflags=-compressdwarf=false main.go with GDB: GDB main
(gdb) b main.hello
Breakpoint 1 at 0x1057240: file /Users/zixuan.xu/workspace/study/learn-go-plan9/goroutine/main.go, line 5.
(gdb) r
Starting program: /Users/zixuan.xu/workspace/study/learn-go-plan9/goroutine/main
[New Thread 0x1a03 of process 88861]
[New Thread 0x1b03 of process 88861]
warning: unhandled dyld version (17)
[New Thread 0x1d03 of process 88861]
[New Thread 0x1f07 of process 88861]
[New Thread 0x2307 of process 88861]
[New Thread 0x5403 of process 88861]
[Switching to Thread 0x2307 of process 88861]
Thread 5 hit Breakpoint 1, main.hello () at /Users/zixuan.xu/workspace/study/learn-go-plan9/goroutine/main.go:5
5 func hello(a) {
(gdb) disas
Dump of assembler code for function main.hello:
=> 0x0000000001057240 <+0>: cmp 0x10(%r14),%rsp
0x0000000001057244 <+4>: jbe 0x1057279 <main.hello+57>
0x0000000001057246 <+6>: sub $0x18,%rsp
0x000000000105724a <+10>: mov %rbp,0x10(%rsp)
0x000000000105724f <+15>: lea 0x10(%rsp),%rbp
0x0000000001057254 <+20>: call 0x102d2e0 <runtime.printlock>
0x0000000001057259 <+25>: lea 0xcc55(%rip),%rax # 0x1063eb5 <go.string. * +309>
0x0000000001057260 <+32>: mov $0x4,%ebx
0x0000000001057265 <+37>: call 0x102dc00 <runtime.printstring>
0x000000000105726a <+42>: call 0x102d360 <runtime.printunlock>
0x000000000105726f <+47>: mov 0x10(%rsp),%rbp
0x0000000001057274 <+52>: add $0x18,%rsp
0x0000000001057278 <+56>: ret
0x0000000001057279 <+57>: call 0x1052da0 <runtime.morestack_noctxt>
0x000000000105727e <+62>: xchg %ax,%ax
0x0000000001057280 <+64>: jmp 0x1057240 <main.hello>
End of assembler dump.
(gdb) b *0x0000000001057278
Breakpoint 2 at 0x1057278: file /Users/zixuan.xu/workspace/study/learn-go-plan9/goroutine/main.go, line 7.
(gdb) c
Continuing.
msg
Thread 5 hit Breakpoint 2.0x0000000001057278 in main.hello () at /Users/zixuan.xu/workspace/study/learn-go-plan9/goroutine/main.go:7
7 }
(gdb) disas
Dump of assembler code for function main.hello:
0x0000000001057240 <+0>: cmp 0x10(%r14),%rsp
0x0000000001057244 <+4>: jbe 0x1057279 <main.hello+57>
0x0000000001057246 <+6>: sub $0x18,%rsp
0x000000000105724a <+10>: mov %rbp,0x10(%rsp)
0x000000000105724f <+15>: lea 0x10(%rsp),%rbp
0x0000000001057254 <+20>: call 0x102d2e0 <runtime.printlock>
0x0000000001057259 <+25>: lea 0xcc55(%rip),%rax # 0x1063eb5 <go.string. * +309>
0x0000000001057260 <+32>: mov $0x4,%ebx
0x0000000001057265 <+37>: call 0x102dc00 <runtime.printstring>
0x000000000105726a <+42>: call 0x102d360 <runtime.printunlock>
0x000000000105726f <+47>: mov 0x10(%rsp),%rbp
0x0000000001057274 <+52>: add $0x18,%rsp
=> 0x0000000001057278 <+56>: ret
0x0000000001057279 <+57>: call 0x1052da0 <runtime.morestack_noctxt>
0x000000000105727e <+62>: xchg %ax,%ax
0x0000000001057280 <+64>: jmp 0x1057240 <main.hello>
End of assembler dump.
(gdb) si
runtime.goexit () at /Users/zixuan.xu/.gvm/gos/go117./src/runtime/asm_amd64.s:1582
1582CALL the runtime, goexit1 (SB)// does not return
(gdb) disas
Dump of assembler code for function runtime.goexit:
0x0000000001053060 <+0>: nop
=> 0x0000000001053061 <+1>: call 0x10551e0 <runtime.goexit1>
0x0000000001053066 <+6>: nop
End of assembler dump.
Copy the code
- Set a breakpoint at main.hello and execute there
- Place a breakpoint at RET and execute to ret
- Using si to enter the ret call, you can see that the function goexit is entered
asm_amd64.s:1580
// The top-most function running on a goroutine
// returns to goexit+PCQuantum.The TEXT, the runtime goexit (SB), NOSPLIT | TOPFRAME, $00
BYTE $0x90 // NOPCALL the runtime, goexit1 (SB)// does not return
// traceback from goexit1 must hit code range of goexit
BYTE $0x90 // NOP
Copy the code
In fact, after si is called, it points to the instruction in the second line of goexit, which is the newg.sched.sp field in the previous section. It is now also possible to prove that non-Girouitne execution does not exit
proc.go:3622
Next, call goexit1
// Finishes execution of the current goroutine.
func goexit1(a) {
if raceenabled {
racegoend()
}
if trace.enabled {
traceGoEnd()
}
mcall(goexit0)
}
Copy the code
Here we switch from goroutine’s stack to G0’s stack via McAll
asm_amd64.s:282
// func mcall(fn func(*g))
// Switch to m->g0's stack, call fn(g).
// Fn must never return. It should gogo(&g->sched)
// to keep running g.#ifdef GOEXPERIMENT_regabiargs TEXT Runtime · McAll <ABIInternal>(SB), NOSPLIT, $0- 8 -
MOVQ AX, DX // DX = fn
// save state in g->sched
MOVQ 0(SP), BX // caller's PC
MOVQ BX, (g_sched+gobuf_pc)(R14) // newg.sched.pc=return addr
LEAQ fn+0(FP), BX // caller's SP // BX=&fn
MOVQ BX, (g_sched+gobuf_sp)(R14) //newg.sched.sp=&fn
MOVQ BP, (g_sched+gobuf_bp)(R14) //newg.sched.bp = BP
// switch to m->g0 & its stack, call fn
MOVQ g_m(R14), BX // BX=newg.m
MOVQ m_g0(BX), SI // SI = g.m.g0
CMPQ SI, R14 // if g == m->g0 call badmcallJNE Goodm JMP Runtime · BADmCall (SB) GoodM: MOVQ R14, AX// AX (and arg 0) = g
MOVQ SI, R14 // g = g.m.g0
get_tls(CX) // Set G in TLS
MOVQ R14, g(CX)
MOVQ (g_sched+gobuf_sp)(R14), SP // sp = g0.sched.sp
PUSHQ AX // open up space for fn's arg spill slot
MOVQ 0(DX), R12
CALL R12 // fn(g)
POPQ AX
JMP runtime·badmcall2(SB)
RET
#else
Copy the code
By MOVQ (g_sched+ goBUF_sp)(R14), SP switches to g0.sched. SP, so each stack switches to the same position each time
proc.go:3633
// goexit continuation on g0.
func goexit0(gp *g) {
_g_ := getg()
casgstatus(gp, _Grunning, _Gdead)
if isSystemGoroutine(gp, false) {
atomic.Xadd(&sched.ngsys, - 1)
}
gp.m = nillocked := gp.lockedm ! =0
gp.lockedm = 0
_g_.m.lockedg = 0
gp.preemptStop = false
gp.paniconfault = false
gp._defer = nil // should be true already but just in case.
gp._panic = nil // non-nil for Goexit during panic. points at stack-allocated data.
gp.writebuf = nil
gp.waitreason = 0
gp.param = nil
gp.labels = nil
gp.timer = nil
ifgcBlackenEnabled ! =0 && gp.gcAssistBytes > 0 {
// Flush assist credit to the global pool. This gives
// better information to pacing if the application is
// rapidly creating an exiting goroutines.
assistWorkPerByte := float64frombits(atomic.Load64(&gcController.assistWorkPerByte))
scanCredit := int64(assistWorkPerByte * float64(gp.gcAssistBytes))
atomic.Xaddint64(&gcController.bgScanCredit, scanCredit)
gp.gcAssistBytes = 0
}
dropg()
if GOARCH == "wasm" { // no threads yet on wasm
gfput(_g_.m.p.ptr(), gp)
schedule() // never returns
}
if_g_.m.lockedInt ! =0 {
print("invalid m->lockedInt = ", _g_.m.lockedInt, "\n")
throw("internal lockOSThread error")
}
gfput(_g_.m.p.ptr(), gp)
if locked {
// The goroutine may have locked this thread because
// it put it in an unusual kernel state. Kill it
// rather than returning it to the thread pool.
// Return to mstart, which will release the P and exit
// the thread.
ifGOOS ! ="plan9" { // See golang.org/issue/22227.
gogo(&_g_.m.g0.sched)
} else {
// Clear lockedExt on plan9 since we may end up re-using
// this thread.
_g_.m.lockedExt = 0
}
}
schedule()
}
Copy the code
- Clear g’s information
- Put g in gfree
- Continue calling schedule
To summarize the process: