返回介绍

上卷 程序设计

中卷 标准库

下卷 运行时

源码剖析

附录

4.6.5 扩容

发布于 2024-10-12 19:16:06 字数 4952 浏览 0 评论 0 收藏 0

编译器在函数头尾插入相关指令,用欲栈内存扩展。

package main

func main() {
	println("hello, world!")
}
$ go build -gcflags "-S"

# test
"".main STEXT size=86 args=0x0 locals=0x18
	0x0000 00000 	TEXT	"".main(SB), ABIInternal, $24-0

	0x0000 00000 	MOVQ	(TLS), CX    // 将当前 G 地址载入 CX。
	0x0009 00009 	CMPQ	SP, 16(CX)   // 16(CX) 对应 G.stackguard0,与 SP 比较。
	0x000d 00013 	JLS	79             // 如果 SP 小于等于 stackguard0,则跳转。(JLS: JBE)

	0x000f 00015 	SUBQ	$24, SP
	0x0013 00019 	MOVQ	BP, 16(SP)
	0x0018 00024 	LEAQ	16(SP), BP

	0x001d 00029 	...

	0x0045 00069 	MOVQ	16(SP), BP
	0x004a 00074 	ADDQ	$24, SP
	0x004e 00078 	RET

	0x004f 00079 	CALL	runtime.morestack_noctxt(SB)  // 执行栈内存扩张。
	0x0054 00084 	JMP	0
// runtime2.go

type g struct {
    stack       stack
    stackguard0 uintptr
    stackguard1 uintptr
}

type stack struct {
    lo uintptr
    hi uintptr
}

TLS

头部 MOV (TLS), CX 指令对应 get_tls 宏函数。

// go_tls.h

#define  get_tls(r)  MOVQ TLS, r
#define  g(r)        0(r)(TLS*1)
// golang.org/doc/asm.html

Within the runtime, the get_tls macro loads its argument register 
with a pointer to the g pointer, and the g struct contains the m pointer. 
The sequence to load g and m using CX looks like this:

get_tls(CX)        // Move g into CX.
MOVL g(CX), AX     // Move g into AX.
MOVL g_m(AX), BX   // Move g.m into BX.

至于最后 TLS 被翻译成什么样,须看不同平台定义。

// cmd/link/internal/ld/sym.go

// computeTLSOffset records the thread-local storage offset.

func (ctxt *Link) computeTLSOffset() {
    /*
     * ELF uses TLS offset negative from FS.
     * Translate 0(FS) and 8(FS) into -16(FS) and -8(FS).
     */
    
    /*
     * OS X system constants - offset from 0(GS) to our TLS.
     * For x86, Apple has reserved a slot in the TLS for Go.
     * That slot is at offset 0x30 on amd64, and 0x18 on 386.
     */
}
$ GOOS=darwin GOARCH=amd64 go build
$ go tool objdump -s "main\.main" test

TEXT main.main(SB)
    MOVQ GS:0x30, CX            
    CMPQ 0x10(CX), SP           
    JBE 0x104df5a
$ GOOS=linux GOARCH=amd64 go build
$ go tool objdump -s "main\.main" test

TEXT main.main(SB)
    MOVQ FS:0xfffffff8, CX          
    CMPQ 0x10(CX), SP           
    BE 0x44ea1a

morestack

由汇编实现的 morestack 函数,最终调用 newstack 完成具体操作。

// asm_amd64.s

TEXT runtime·morestack_noctxt(SB),NOSPLIT,$0
    MOVL    $0, DX
    JMP runtime·morestack(SB)
TEXT runtime·morestack(SB),NOSPLIT,$0-0
    // 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
// 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
        
    // 抢占调度 ...
        
    // 分配 2 倍新栈,拷贝数据到新栈。
    oldsize := gp.stack.hi - gp.stack.lo
    newsize := oldsize * 2
        
    casgstatus(gp, _Grunning, _Gcopystack)
    copystack(gp, newsize)
        
    // 恢复 G 运行。
    casgstatus(gp, _Gcopystack, _Grunning)
    gogo(&gp.sched)
}
// stack.go

// Copies gp's stack to a new stack of a different size.

func copystack(gp *g, newsize uintptr) {

    old := gp.stack
    used := old.hi - gp.sched.sp
        
    // 创建新栈。
    new := stackalloc(uint32(newsize))
        
    // 计算新旧栈地址偏移(delta),用于调整指针。
    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)
        
    // 切换到新栈。
    gp.stack = new
    gp.stackguard0 = new.lo + _StackGuard 
    gp.sched.sp = new.hi - used
    gp.stktopsp += adjinfo.delta
        
    // 释放旧栈。
    stackfree(old)
}

拷贝栈需要调整指针指向。

计算新旧栈基址差(newbase - oldbase),然后修改指针内容(+= delta)。

无需扫描栈内容,因为它们基于栈寄存器(SP、G.sched.sp)寻址。

// stack.go

type adjustinfo struct {
    old   stack
    delta uintptr           // ptr distance from old to new stack (newbase - oldbase)
    cache pcvalueCache
        
    // sghi is the highest sudog.elem on the stack.
    sghi uintptr
}
// 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文