上卷 程序设计
中卷 标准库
- bufio 1.18
- bytes 1.18
- io 1.18
- container 1.18
- encoding 1.18
- crypto 1.18
- hash 1.18
- index 1.18
- sort 1.18
- context 1.18
- database 1.18
- connection
- query
- queryrow
- exec
- prepare
- transaction
- scan & null
- context
- tcp
- udp
- http
- server
- handler
- client
- h2、tls
- url
- rpc
- exec
- signal
- embed 1.18
- plugin 1.18
- reflect 1.18
- runtime 1.18
- KeepAlived
- ReadMemStats
- SetFinalizer
- Stack
- sync 1.18
- atomic
- mutex
- rwmutex
- waitgroup
- cond
- once
- map
- pool
- copycheck
- nocopy
- unsafe 1.18
- fmt 1.18
- log 1.18
- math 1.18
- time 1.18
- timer
下卷 运行时
源码剖析
附录
文章来源于网络收集而来,版权归原创者所有,如有侵权请及时联系!
4.5.3 执行
利用 gogo 切换到 G.stack,然后执行。
参考本章《内核调用》。
// proc.go // Schedules gp to run on the current M. // If inheritTime is true, gp inherits the remaining time in the // current time slice. Otherwise, it starts a new time slice. // Never returns. func execute(gp *g, inheritTime bool) { _g_ := getg() _g_.m.curg = gp gp.m = _g_.m casgstatus(gp, _Grunnable, _Grunning) gp.preempt = false gp.stackguard0 = gp.stack.lo + _StackGuard // 任务执行计数器。 if !inheritTime { _g_.m.p.ptr().schedtick++ } gogo(&gp.sched) }
goexit
无论是 execute,还是 gogo 都没有直接回到 schedule 的意思。
那么,如何清理执行现场?比如将 dead G 放回复用链表。如何继续循环调度呢?
在 newproc1 设置 G.sched 的时候,实际保存在 pc 的是 goexit。
func newproc1(fn *funcval, argp unsafe.Pointer, narg int32, callergp *g, callerpc uintptr) *g { newg.sched.pc = funcPC(goexit) + sys.PCQuantum newg.sched.g = guintptr(unsafe.Pointer(newg)) gostartcallfn(&newg.sched, fn) }
关键就在于 gostartcallfn 做了什么。
// stack.go // adjust Gobuf as if it executed a call to fn // and then did an immediate gosave. func gostartcallfn(gobuf *gobuf, fv *funcval) { var fn unsafe.Pointer if fv != nil { fn = unsafe.Pointer(fv.fn) } else { fn = unsafe.Pointer(funcPC(nilfunc)) } gostartcall(gobuf, fn, unsafe.Pointer(fv)) }
// sys_x86.go // adjust Gobuf as if it executed a call to fn with context ctxt // and then did an immediate gosave. func gostartcall(buf *gobuf, fn, ctxt unsafe.Pointer) { sp := buf.sp // 将 SP 前移,留出空间保存 buf.pc。 // 目的是将前面预先设置的 goexit 入栈。 sp -= sys.PtrSize *(*uintptr)(unsafe.Pointer(sp)) = buf.pc // 重新设置 sched。此时,将 G.fn 保存到 sched.pc。 buf.sp = sp buf.pc = uintptr(fn) buf.ctxt = ctxt }
在 execute 通过 gogo 切换到 G.stack 时,堆栈情况如下:
lo | | | G.fn | +----------+ G.sched.sp | goexit | hi +----------+ | |
而 gogo 以 JMP
指令调用 G.fn,也就是说不会将 gogo IP/PC 入栈。
等 G.fn 的 RET
指令执行时, POP PC
的结果自然就是 goexit。
// This function must never be called directly. Call goexit1 instead. func goexit(neverCallThisFunction) // Finishes execution of the current goroutine. func goexit1() { mcall(goexit0) }
// goexit continuation on g0. func goexit0(gp *g) { casgstatus(gp, _Grunning, _Gdead) gp.m = nil locked := gp.lockedm != 0 // 解除 dead G 相关信息,放回复用列表。 dropg() gfput(_g_.m.p.ptr(), gp) // 如果没有解除锁定,出错,终止线程。 // 参考本章《线程终止》。 if locked { // Return to mstart, which will release the P and exit the thread. if GOOS != "plan9" { gogo(&_g_.m.g0.sched) } } // 回到调度循环。 schedule() }
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论