返回介绍

上卷 程序设计

中卷 标准库

下卷 运行时

源码剖析

附录

3.6.1 写屏障

发布于 2024-10-12 19:15:57 字数 5481 浏览 0 评论 0 收藏 0

已扫描黑色对象,可能被并发用户代码修改。此时,写屏障会拦截指针修改操作,使其被重新处理。

// test/main.go

package main

var x *int

func main() {
	x = new(int)
	println(x)
}
$ go build -gcflags "-S"

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

	0x0006 00006 SUBQ	$24, SP
	0x000a 00010 MOVQ	BP, 16(SP)
	0x000f 00015 LEAQ	16(SP), BP

	0x0014 00020 LEAQ	type.int(SB), AX
	0x001b 00027 NOP
	0x0020 00032 CALL	runtime.newobject(SB)

    // 判断写屏障是否启用。(writeBarrier.enabled != 0)
	0x0025 00037 CMPL	runtime.writeBarrier(SB), $0 
	0x002c 00044 JNE	55

	0x002e 00046 MOVQ	AX, "".x(SB)
	0x0035 00053 JMP	69

    // 写屏障拦截。
	0x0037 00055 LEAQ	"".x(SB), DI
	0x003e 00062 NOP
	0x0040 00064 CALL	runtime.gcWriteBarrier(SB)

	0x0045 00069 CALL	runtime.printlock(SB)
	0x004a 00074 MOVQ	"".x(SB), AX
	0x0051 00081 CALL	runtime.printpointer(SB)
	0x0056 00086 CALL	runtime.printnl(SB)
	0x0060 00096 CALL	runtime.printunlock(SB)

	0x0065 00101 MOVQ	16(SP), BP
	0x006a 00106 ADDQ	$24, SP
	0x006e 00110 RET

从输出汇编可以看出,编译器插入写屏障相关代码。

// mgc.go

var writeBarrier struct {
	enabled bool    // compiler emits a check of this before calling write barrier
}

func setGCPhase(x uint32) {
	atomic.Store(&gcphase, x)
	writeBarrier.needed = gcphase == _GCmark || gcphase == _GCmarktermination
	writeBarrier.enabled = writeBarrier.needed || writeBarrier.cgo
}

拦截代码以汇编实现,将新老两个目标对象放入 P.wbBuf 内。

// runtime2.go

type p struct {
    
	// wbBuf is this P's GC write barrier buffer.
	wbBuf wbBuf 
}
// mwbbuf.go

// wbBuf is a per-P buffer of pointers queued by the write barrier.
// This buffer is flushed to the GC workbufs when it fills up and on
// various GC transitions.

//     +----------------------+  next += ptrsize * 2
//     |                      |
//     |                      v
// +------+-----+-------------+-------------+-----+
// | next | end | old0 | new0 | old1 | new1 | ... | buf[256*2]
// +------+-----+-------------+-------------+-----+
//           |                                    ^
//           |                                    |
//           +------------------------------------+

type wbBuf struct {
    
    // next points to the next slot in buf. 
	next uintptr
    
    // end points to just past the end of buf.
	end uintptr

	// buf stores a series of pointers to execute write barriers
	// on. This must be a multiple of wbBufEntryPointers because
	// the write barrier only checks for overflow once per entry.
	buf [wbBufEntryPointers * wbBufEntries]uintptr
}
// asm_amd64.s

// gcWriteBarrier performs a heap pointer write and informs the GC.
//
// gcWriteBarrier does NOT follow the Go ABI. It takes two arguments:
// - DI is the destination of the write
// - AX is the value being written at DI

TEXT runtime·gcWriteBarrier<ABIInternal>(SB),NOSPLIT,$112

    // across a sequence of write barriers.
    MOVQ	g_m(R14), R13                   // m
    MOVQ	m_p(R13), R13                   // p
    MOVQ	(p_wbBuf+wbBuf_next)(R13), R12  // p.wbBuf.next

    // Increment wbBuf.next position.       
    LEAQ	16(R12), R12                    
    MOVQ	R12, (p_wbBuf+wbBuf_next)(R13)  // next 指向下一组,预留一组位置。
    CMPQ	R12, (p_wbBuf+wbBuf_end)(R13)   // next 与 end 比较,是否满了?
    
    // Record the write.                    
    MOVQ	AX, -16(R12)	// Record value  // 在预留位置保存 AX 和 (DI)。
    MOVQ	(DI), R13
    MOVQ	R13, -8(R12)	// Record *slot
    
    // Is the buffer full? (flags set in CMPQ above) // 满了,就主动提交。
    JEQ	flush
    
ret:
    // Do the write.
    MOVQ	AX, (DI)   // 已拦截,还是要完成用户写入操作。
    RET
    
flush:
    CALL	runtime·wbBufFlush(SB) // 主动提交到队列。
    JMP	ret

当工人调用 gcDrain 时,会检查并处理写屏障缓存。

// mgcmark.go

func gcDrain(gcw *gcWork, flags gcDrainFlags) {
    
	// Drain root marking jobs ...
    
	// Drain heap marking jobs.
	for !(gp.preempt && (preemptible || atomic.Load(&sched.gcwaiting) != 0)) {
        
		b := gcw.tryGetFast()
		if b == 0 {
			b = gcw.tryGet()
            
            // 灰色队列没找到,处理写屏障缓存。
			if b == 0 {
				// Flush the write barrier
				// buffer; this may create
				// more work.
				wbBufFlush(nil, 0)
				b = gcw.tryGet()
			}
		}
        
		if b == 0 {
			// Unable to get work.
			break
		}
        
		scanobject(b, gcw)

	}
}
// wbBufFlush flushes the current P's write barrier buffer to the GC
// workbufs. It is passed the slot and value of the write barrier that
// caused the flush so that it can implement cgocheck.

func wbBufFlush(dst *uintptr, src uintptr) {
	wbBufFlush1(getg().m.p.ptr())
}
// wbBufFlush1 flushes p's write barrier buffer to the GC work queue.

func wbBufFlush1(_p_ *p) {
    
	// 将 buf 数组转换为指针切片。
	start := uintptr(unsafe.Pointer(&_p_.wbBuf.buf[0]))
	n := (_p_.wbBuf.next - start) / unsafe.Sizeof(_p_.wbBuf.buf[0])
	ptrs := _p_.wbBuf.buf[:n]

	_p_.wbBuf.next = 0

	gcw := &_p_.gcw
	pos := 0
    
    // 扫描新、旧对象。
	for _, ptr := range ptrs {
        
		obj, span, objIndex := findObject(ptr, 0, 0)
		if obj == 0 {
			continue
		}
        
        // 检查标记。
		mbits := span.markBitsForIndex(objIndex)
		if mbits.isMarked() {
			continue
		}
        
        // 新标记。
		mbits.setMarked()

        // 没有指针,无需加入灰色队列。
		if span.spanclass.noscan() {
			continue
		}
        
        // 新标记对象暂存在头部已检查区域。
		ptrs[pos] = obj
		pos++
	}

	// 将新标记对象放入灰色队列。
	gcw.putBatch(ptrs[:pos])

    // 重置写屏障缓冲。
	_p_.wbBuf.reset()
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

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

发布评论

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