上卷 程序设计
中卷 标准库
- 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
下卷 运行时
源码剖析
- 1. 初始化
- 2. 内存分配
- 3. 垃圾回收
- 4. 并发调度
- 5. 通道
- 6. 延迟调用
- 7. 终结器
- 8. 其他
附录
文章来源于网络收集而来,版权归原创者所有,如有侵权请及时联系!
4.6.5 扩容
编译器在函数头尾插入指令,用于栈内存检查和扩展。
// test/main.go func main() { println("hello, world!") }
# 查看 native code,而不是 compile -S 输出的中间汇编代码。 $ go build && go tool objdump -s "main\.main" ./test TEXT main.main(SB) test/main.go ; R14 保存当前 G。 ; 0x10(G) 指向 G.stackguard0。 ; 与 SP 比较,以确定是否要跳转到 morestack 扩容。 0x4551e0 CMPQ 0x10(R14), SP 0x4551e4 JBE 0x455219 0x4551e6 SUBQ $0x18, SP 0x4551ea MOVQ BP, 0x10(SP) 0x4551ef LEAQ 0x10(SP), BP 0x4551f4 CALL runtime.printlock(SB) 0x4551f9 LEAQ 0xd7a6(IP), AX 0x455200 MOVL $0xe, BX 0x455205 CALL runtime.printstring(SB) 0x45520a CALL runtime.printunlock(SB) 0x45520f MOVQ 0x10(SP), BP 0x455214 ADDQ $0x18, SP 0x455218 RET ; 扩容后跳回头部,重新执行。 0x455219 CALL runtime.morestack_noctxt.abi0(SB) 0x45521e NOPW 0x455220 JMP main.main(SB)
// runtime2.go type g struct { stack stack stackguard0 uintptr stackguard1 uintptr } type stack struct { lo uintptr hi uintptr }
寄存器
不同平台使用不同寄存器存储 G 地址。
// src/cmd/internal/obj/x86/a.out.go const REGG = REG_R14 // g register in ABIInternal
// src/cmd/asm/internal/arch/arch.go func archX86(linkArch *obj.LinkArch) *Arch { // Pseudo-registers. register["SB"] = RSB register["FP"] = RFP register["PC"] = RPC if linkArch == &x86.Linkamd64 { register["g"] = x86.REGG // Alias g to R14 } } func archArm64() *Arch { // Pseudo-registers. register["SB"] = RSB register["FP"] = RFP register["PC"] = RPC register["SP"] = RSP register["g"] = arm64.REG_R28 }
$ GOARCH=arm64 go build && go tool objdump -s "main\.main" ./test TEXT main.main(SB) test/main.go 0x62c60 MOVD 16(R28), R16 0x62c64 MOVD RSP, R17 0x62c68 CMP R16, R17 0x62c6c BLS 13(PC)
汇编代码里,用 get_tls 将 G 地址载入寄存器。
// doc/asm.html The runtime pointer to the g structure is maintained through the value of an otherwise unused (as far as Go is concerned) register in the MMU. In the runtime package, assembly code can include go_tls.h, which defines an OS- and architecture-dependent macro get_tls for accessing this register. The get_tls macro takes one argument, which is the register to load the g pointer into. get_tls(CX) MOVL g(CX), AX // Move g into AX. MOVL g_m(AX), BX // Move g.m into BX.
// go_tls.h #ifdef GOARCH_amd64 #define get_tls(r) MOVQ TLS, r #define g(r) 0(r)(TLS*1) #endif
扩容
扩容操作使用 g0 栈,注意保存 G 现场。
// asm_amd64.s TEXT runtime·morestack_noctxt(SB),NOSPLIT,$0 MOVL $0, DX JMP runtime·morestack(SB) // Called during function prolog when more stack is needed. TEXT runtime·morestack(SB),NOSPLIT,$0-0 // Cannot grow scheduler stack (m->g0). // Cannot grow signal stack (m->gsignal). get_tls(CX) MOVQ g(CX), BX // g MOVQ g_m(BX), BX // m get_tls(CX) MOVQ g(CX), SI // Set g->sched to context in f. MOVQ 0(SP), AX // morestack 并未使用栈内存。 MOVQ AX, (g_sched+gobuf_pc)(SI) // 仅 call morestack 指令将 LEAQ 8(SP), AX // f PC 入栈。 MOVQ AX, (g_sched+gobuf_sp)(SI) // MOVQ BP, (g_sched+gobuf_bp)(SI) // 因此,0(SP) -> f.PC MOVQ DX, (g_sched+gobuf_ctxt)(SI) // 8(SP) -> f.SP (before call) // Call newstack on m->g0's stack. MOVQ m_g0(BX), BX MOVQ BX, g(CX) MOVQ (g_sched+gobuf_sp)(BX), SP CALL runtime·newstack(SB) CALL runtime·abort(SB) // crash if newstack returns RET
执行 newstack 分配更大的内存,并拷贝数据。
// stack.go // Called from runtime·morestack when more stack is needed. // Allocate larger stack and relocate to new stack. // Stack growth is multiplicative, for constant amortized cost. func newstack() { thisg := getg() // g0 gp := thisg.m.curg // G // preempt ... // 2x oldsize := gp.stack.hi - gp.stack.lo newsize := oldsize * 2 // 分配新栈内存,复制数据。 casgstatus(gp, _Grunning, _Gcopystack) copystack(gp, newsize) // 使用 morestack 保存的现场恢复 G 执行。 casgstatus(gp, _Gcopystack, _Grunning) gogo(&gp.sched) }
// Copies gp's stack to a new stack of a different size. // Caller must have changed gp status to Gcopystack. func copystack(gp *g, newsize uintptr) { old := gp.stack used := old.hi - gp.sched.sp // 老栈释放需要垃圾清理。 gcController.addScannableStack(getg().m.p.ptr(), int64(newsize)-int64(old.hi-old.lo)) // 分配新栈。 new := stackalloc(uint32(newsize)) // 计算新旧地址偏移,以便调整指针。 var adjinfo adjustinfo adjinfo.old = old adjinfo.delta = new.hi - old.hi // 复制栈数据。 ncopy := used memmove(unsafe.Pointer(new.hi-ncopy), unsafe.Pointer(old.hi-ncopy), ncopy) // 调整指针。 adjustctxt(gp, &adjinfo) adjustdefers(gp, &adjinfo) adjustpanics(gp, &adjinfo) if adjinfo.sghi != 0 { adjinfo.sghi += adjinfo.delta } // 切换到新栈。 gp.stack = new gp.stackguard0 = new.lo + _StackGuard // NOTE: might clobber a preempt request gp.sched.sp = new.hi - used gp.stktopsp += adjinfo.delta // 释放旧栈。 stackfree(old) }
指针
指针调整只是简单的加上新旧地址偏移(+= delta)。
func adjustctxt(gp *g, adjinfo *adjustinfo) { adjustpointer(adjinfo, unsafe.Pointer(&gp.sched.ctxt)) adjustpointer(adjinfo, unsafe.Pointer(&gp.sched.bp)) }
type adjustinfo struct { old stack delta uintptr // ptr distance from old to new stack (newbase - oldbase) } // Adjustpointer checks whether *vpp is in the old stack described by adjinfo. // If so, it rewrites *vpp to point into the new stack. func adjustpointer(adjinfo *adjustinfo, vpp unsafe.Pointer) { pp := (*uintptr)(vpp) p := *pp if adjinfo.old.lo <= p && p < adjinfo.old.hi { *pp = p + adjinfo.delta } }
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论