返回介绍

上卷 程序设计

中卷 标准库

下卷 运行时

源码剖析

附录

4.7.1 系统调用

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

系统调用分 Syscall、RawSyscall 两种方式,区别在于是否调用 entersyscall、exitsyscall 函数。

// src/syscall/asm_linux_amd64.s

// func Syscall(trap int64, a1, a2, a3 uintptr) (r1, r2, err uintptr);
// Trap # in AX, args in DI SI DX R10 R8 R9, return in AX DX
// Note that this differs from "standard" ABI convention, which
// would pass 4th arg in CX, not R10.

TEXT ·Syscall(SB),NOSPLIT,$0-56
    CALL	runtime·entersyscall(SB)
    MOVQ	a1+8(FP),  DI
    MOVQ	a2+16(FP), SI
    MOVQ	a3+24(FP), DX
    MOVQ	trap+0(FP), AX	// syscall entry
    SYSCALL
    CMPQ	AX, $0xfffffffffffff001
    JLS	ok
    MOVQ	$-1, r1+32(FP)
    MOVQ	$0, r2+40(FP)
    NEGQ	AX
    MOVQ	AX, err+48(FP)
    CALL	runtime·exitsyscall(SB)
    RET
ok:
    MOVQ	AX, r1+32(FP)
    MOVQ	DX, r2+40(FP)
    MOVQ	$0, err+48(FP)
    CALL	runtime·exitsyscall(SB)
	RET
// func RawSyscall(trap, a1, a2, a3 uintptr) (r1, r2, err uintptr)
TEXT ·RawSyscall(SB),NOSPLIT,$0-56
	MOVQ	a1+8(FP), DI
	MOVQ	a2+16(FP), SI
	MOVQ	a3+24(FP), DX
	MOVQ	trap+0(FP), AX	// syscall entry
	SYSCALL
	CMPQ	AX, $0xfffffffffffff001
	JLS	ok1
	MOVQ	$-1, r1+32(FP)
	MOVQ	$0, r2+40(FP)
	NEGQ	AX
	MOVQ	AX, err+48(FP)
	RET
ok1:
	MOVQ	AX, r1+32(FP)
	MOVQ	DX, r2+40(FP)
	MOVQ	$0, err+48(FP)
	RET

进入

函数 entersyscall 解除 P 绑定,当前 M 依旧等待系统调用返回。P 并未放入闲置队列,也未主动让其绑定其他 M 积极工作。而是暂存起来,等系统调用结束时重新绑定。

可被 sysmon 抢走(retake)。

// proc.go

// Standard syscall entry used by the go syscall library and normal cgo calls.
// This is exported via linkname to assembly in the syscall package.

func entersyscall() {
	reentersyscall(getcallerpc(), getcallersp())
}
func reentersyscall(pc, sp uintptr) {
	_g_ := getg()
	_g_.m.locks++

	// 保存现场(g.sched.pc, sp),设置 syscall 状态。。
	save(pc, sp)
	_g_.syscallsp = sp
	_g_.syscallpc = pc
	casgstatus(_g_, _Grunning, _Gsyscall)
    
    // 如果 sysmon 休眠,则唤醒它。
	if atomic.Load(&sched.sysmonwait) != 0 {
		systemstack(entersyscall_sysmon)
		save(pc, sp)
	}

    // 类似 forEachP 之类的安全函数。
	if _g_.m.p.ptr().runSafePointFn != 0 {
		systemstack(runSafePointFn)
		save(pc, sp)
	}

    // 执行计数器。
	_g_.m.syscalltick = _g_.m.p.ptr().syscalltick
    
    // 解除当前 M 和 P 的关联。
	pp := _g_.m.p.ptr()
	pp.m = 0
    
    // P 并未放回闲置列表,而是暂存在 M.oldp。
	_g_.m.oldp.set(pp)
	_g_.m.p = 0
    
    // 修改状态。
	atomic.Store(&pp.status, _Psyscall)
    
    // STW!!!
	if sched.gcwaiting != 0 {
		systemstack(entersyscall_gcwait)
		save(pc, sp)
	}

	_g_.m.locks--
}

阻塞版本则提前累加执行计数器,跳过 sysmon 检查。主动解除 P 绑定,让其匹配其他 M 积极工作。

// proc.go

// The same as entersyscall(), but with a hint that the syscall is blocking.
func entersyscallblock() {
	_g_ := getg()
	_g_.m.locks++
    
    // 提前累加执行计数器。
	_g_.m.syscalltick = _g_.m.p.ptr().syscalltick
	_g_.m.p.ptr().syscalltick++

	// 保存现场。
	pc := getcallerpc()
	sp := getcallersp()
	save(pc, sp)
	_g_.syscallsp = _g_.sched.sp
	_g_.syscallpc = _g_.sched.pc

    // 修改状态。
	casgstatus(_g_, _Grunning, _Gsyscall)

    // 解除 P 绑定,让其匹配其他 M,积极执行任务。
	systemstack(entersyscallblock_handoff)

	save(getcallerpc(), getcallersp())
	_g_.m.locks--
}
func entersyscallblock_handoff() {
	handoffp(releasep())
}

退出

退出系统调用时,如果暂存 P 尚在,那么直接绑定,否则找闲置 P 继续。

// proc.go

// The goroutine g exited its system call.
// Arrange for it to run on a cpu again.
// This is called only from the go syscall library, not
// from the low-level system calls used by the runtime.

func exitsyscall() {
	_g_ := getg()
	_g_.m.locks++
	_g_.waitsince = 0
    
    // 暂存 P。
	oldp := _g_.m.oldp.ptr()
	_g_.m.oldp = 0
    
    // 快模式:暂存 P 还在,或有闲置 P,直接绑定继续执行。
	if exitsyscallfast(oldp) {
        
		// 累加计数器。
		_g_.m.p.ptr().syscalltick++
        
		// 修改状态。
		casgstatus(_g_, _Gsyscall, _Grunning)

		_g_.syscallsp = 0
		_g_.m.locks--

		return
	}

	_g_.sysexitticks = 0
	_g_.m.locks--

    // 慢模式:没有可用 P,放到全局队列,等其他 MP 提取执行。
	mcall(exitsyscall0)

	_g_.syscallsp = 0
	_g_.m.p.ptr().syscalltick++
}
func exitsyscallfast(oldp *p) bool {
	_g_ := getg()

    // 重新绑定暂存 P。(status != _Psyscall 表示已经被 sysmon 夺走)
	if oldp != nil && oldp.status == _Psyscall && atomic.Cas(&oldp.status, _Psyscall, _Pidle) {
		wirep(oldp)
		exitsyscallfast_reacquired()
		return true
	}

	// 绑定空闲 P。
	if sched.pidle != 0 {
		var ok bool
		systemstack(func() {
			ok = exitsyscallfast_pidle()
		})
		if ok {
			return true
		}
	}
    
	return false
}
// exitsyscall slow path on g0.
// Failed to acquire P, enqueue gp as runnable.

func exitsyscall0(gp *g) {
    
    // 修改状态。
	casgstatus(gp, _Gsyscall, _Grunnable)
    
    // 解除 M 绑定。
	dropg()
    
	lock(&sched.lock)
    
    // 再次尝试获取一个闲置 P。
	var _p_ *p
	if schedEnabled(gp) {
		_p_ = pidleget()
	}
    
    // 依旧没找到 P。
	if _p_ == nil {
        
        // 将 syscall G 放到全局任务队列。
		globrunqput(gp)
        
	} else if atomic.Load(&sched.sysmonwait) != 0 {
        
        // 唤醒 sysmon。
		atomic.Store(&sched.sysmonwait, 0)
		notewakeup(&sched.sysmonnote)
	}
    
	unlock(&sched.lock)
    
    // 如果找到 P,绑定,继续 syscall G 执行。
	if _p_ != nil {
		acquirep(_p_)
		execute(gp, false) // Never returns.
	}
    
    // 当前 M 休眠,等待唤醒后重新进度调度循环。
	stopm()
	schedule() // Never returns.
}

CGO

采取和系统调用相同方式。

// cgocall.go

// Call from Go to C.
func cgocall(fn, arg unsafe.Pointer) int32 {
	mp := getg().m
	mp.ncgocall++
	mp.ncgo++

	entersyscall()
	mp.incgo = true

    errno := asmcgocall(fn, arg)

	mp.incgo = false
	mp.ncgo--
	exitsyscall()

	KeepAlive(fn)
	KeepAlive(arg)
	KeepAlive(mp)

	return errno
}

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

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

发布评论

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