上卷 程序设计
中卷 标准库
- 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
下卷 运行时
源码剖析
附录
文章来源于网络收集而来,版权归原创者所有,如有侵权请及时联系!
6.2 恐慌
编译器将 panic 翻译成 gopanic 调用。和 defer 类似,panic 也会保存到 G 里。
// runtime2.go type g struct { _panic *_panic } // A _panic holds information about an active panic. type _panic struct { argp unsafe.Pointer // pointer to arguments of deferred call run during panic; arg any // argument to panic link *_panic // link to earlier panic pc uintptr // where to return to in runtime if this panic is bypassed sp unsafe.Pointer // where to return to in runtime if this panic is bypassed recovered bool // whether this panic is over aborted bool // the panic was aborted goexit bool }
引发恐慌前,需确保 G 所有已注册(整个调用堆栈,非当前函数)延迟函数得以执行。
仅一个 panic 抛出,因为它导致进程崩溃。
// panic.go // The implementation of the predeclared function panic. func gopanic(e any) { gp := getg() // 新建,添加到 G._panic 链表头部。 var p _panic p.arg = e p.link = gp._panic gp._panic = (*_panic)(noescape(unsafe.Pointer(&p))) atomic.Xadd(&runningPanicDefers, 1) // 遍历执行 _defer。 for { d := gp._defer if d == nil { break } // If defer was started by earlier panic or Goexit (and, since we're back here, that triggered a new panic), // take defer off list. An earlier panic will not continue running, but we will make sure below that an // earlier Goexit does continue running. // 延迟函数(d)可再次引发 panic,那么 gopanic 会 “递归嵌套”。 // 在内层看来,如果当前 d 已被外层 gopanic 或 goexit 启动,跳过,从链表移除。 // 内层处理掉链表上剩余的 _defer,且不会返回(not return)外层。 // 结果要么崩溃(fatalpanic),要么 recover 回到 caller 函数。 // 如果当前 _defer 已执行,清理掉。 if d.started { if d._panic != nil { d._panic.aborted = true } d._panic = nil // 释放当前 defer,继续下一个。 if !d.openDefer { d.fn = nil gp._defer = d.link freedefer(d) continue } } // 执行当前 _defer,并绑定 _panic。 d.started = true d._panic = (*_panic)(noescape(unsafe.Pointer(&p))) done := true p.argp = unsafe.Pointer(getargp()) d.fn() p.argp = nil d._panic = nil // 创建 _defer 时保存的 caller 状态。 pc := d.pc sp := unsafe.Pointer(d.sp) // 丛 G._defer 链表删除。 if done { d.fn = nil gp._defer = d.link freedefer(d) } // 如果延迟调用内执行了 recover 函数。 // 那么 gopainc 执行到此结束,由 recover 恢复 caller 调用状态。 // 剩余 _defer 就交给 caller deferreturn 了。 if p.recovered { // 本次 panic 被捕获,从链表移除。 gp._panic = p.link atomic.Xadd(&runningPanicDefers, -1) d := gp._defer var prev *_defer if !done { // Skip our current frame, if not done. It is // needed to complete any remaining defers in // deferreturn() prev = d d = d.link } for d != nil { if d.started { break } prev = d d = d.link } // 将放弃的 panic 从链表移除。 gp._panic = p.link for gp._panic != nil && gp._panic.aborted { gp._panic = gp._panic.link } // 调用 recovery 恢复执行,当前 gopanic 不再返回。 gp.sigcode0 = uintptr(sp) gp.sigcode1 = pc mcall(recovery) throw("recovery failed") // mcall should not return } } // 输出错误信息,进程崩溃。 preprintpanics(gp._panic) fatalpanic(gp._panic) // should not return *(*int)(nil) = 0 // not reached }
进程崩溃前,不会等待其他 G,不会执行其他 G._defer。恐慌代表 “不可修复错误”,如等待其他 G,可能永远无法终止进程,这就违背设计初衷。
func fatalpanic(msgs *_panic) { pc := getcallerpc() sp := getcallersp() gp := getg() var docrash bool systemstack(func() { if startpanic_m() && msgs != nil { printpanics(msgs) } docrash = dopanic_m(gp, pc, sp) }) if docrash { crash() } systemstack(func() { exit(2) }) *(*int)(nil) = 0 // not reached }
恢复
在延迟函数内调用 recover,被编译器转换为 gorecover 调用。
仅标记(recovered),由 gopanic 调用 recovery 处理。
参数 argp 由调用 recover 的延迟函数传入,指向该
defer.caller.SP
。
// panic.go // The implementation of the predeclared function recover. func gorecover(argp uintptr) any { // Must be in a function running as part of a deferred call during the panic. // Must be called from the topmost function of the call // (the function used in the defer statement). // p.argp is the argument pointer of that topmost deferred function call. // Compare against argp reported by caller. // If they match, the caller is the one who can recover. gp := getg() p := gp._panic // 通过比对 caller.SP 地址,确保在 topmost 内调用。 if p != nil && !p.goexit && !p.recovered && argp == uintptr(p.argp) { p.recovered = true return p.arg } return nil }
执行逻辑 :
- 首先,gopanic 执行 G._defer 函数。
- 接着,_defer 调用 gorecover 设置 recovered = true 标记。
- 最后,recovery 获取 _defer.caller 状态,恢复执行 caller "call deferproc" 下一指令。
// panic.go // Unwind the stack after a deferred function calls recover // after a panic. Then arrange to continue running as though // the caller of the deferred function returned normally. func recovery(gp *g) { // sigcode 由 gopanic 设置。 // call deferproc 创建 _defer 时所保存 caller 状态, sp := gp.sigcode0 pc := gp.sigcode1 // Make the deferproc for this d return again, // this time returning 1. The calling function will // jump to the standard return epilogue. // 恢复到 call deferproc 下一条指令继续执行。 gp.sched.sp = sp gp.sched.pc = pc gp.sched.lr = 0 gp.sched.ret = 1 // !!! gogo(&gp.sched) }
执行函数 gogo 将 ret 值放入 AX 寄存器。编译器会在 "call deferproc" 下插入检查和跳转指令,调用尾部的 deferreturn 执行剩余延迟函数。
// asm_amd64.s TEXT gogo<>(SB), NOSPLIT, $0 get_tls(CX) MOVQ DX, g(CX) MOVQ DX, R14 // set the g register MOVQ gobuf_sp(BX), SP // restore SP MOVQ gobuf_ret(BX), AX // !!!! MOVQ gobuf_ctxt(BX), DX MOVQ gobuf_bp(BX), BP MOVQ gobuf_pc(BX), BX JMP BX
00037 CALL runtime.deferprocStack(SB) 00042 TESTL AX, AX // if AX == 0 then ZF = 1 else ZF = 0. 00044 JNE 72 // if ZF == 0 then jmp. 00046 JMP 48 00048 CALL "".test(SB) 00053 ... 00072 CALL runtime.deferreturn(SB)
而 deferproc 正常返回 0,不会影响 TEST 和跳转指令。
// panic.go func deferproc(fn func()) { // deferproc returns 0 normally. // a deferred func that stops a panic // makes the deferproc return 1. // the code the compiler generates always // checks the return value and jumps to the // end of the function if deferproc returns != 0. return0() }
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论