返回介绍

上卷 程序设计

中卷 标准库

下卷 运行时

源码剖析

附录

4.7.1 系统调用

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

系统调用分 Syscall、RawSyscall 两种方式。

区别在于是否调用 entersyscall / exitsyscall 函数。

// src/syscall/asm_linux_amd64.s

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

enter

进入前,设置相关状态。

提前解除了绑定设置,可被抢走(sysmon retake),但并未上交到闲置队列或唤醒其他 M。

该方式不会提前累加计数器。

// proc.go

// Standard syscall entry used by the go syscall library and normal cgo calls.

func entersyscall() {
    reentersyscall(getcallerpc(), getcallersp())
}
func reentersyscall(pc, sp uintptr) {
    _g_ := getg()
    _g_.stackguard0 = stackPreempt
    
    // 保存执行现场。
    save(pc, sp)
    
    casgstatus(_g_, _Grunning, _Gsyscall)
    
    // 解除当前 M 和 P 关联,存储在 M.oldp 供退出时检查。
    // 当前 M 执行系统调用,P 可被抢走。
    pp := _g_.m.p.ptr()
    pp.m = 0
    _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)
    }
}

阻塞方式,累加计数器,提前主动交出 P 执行其他任务。

因为累加计数器,sysmon 可能会跳过检查。

// proc.go

// The same as entersyscall(), but with a hint that the syscall is blocking.

func entersyscallblock() {
    _g_ := getg()
    _g_.stackguard0 = stackPreempt  // see comment in entersyscall
    
    // 阻塞方式,累加计数器。
    _g_.m.p.ptr().syscalltick++
    
    // 保存现场。
    pc := getcallerpc()
    sp := getcallersp()
    save(pc, sp)
    
    casgstatus(_g_, _Grunning, _Gsyscall)
    systemstack(entersyscallblock_handoff)
    
    // Resave for traceback during blocked call.
    save(getcallerpc(), getcallersp())
}
func entersyscallblock_handoff() {
    handoffp(releasep())
}

exit

退出系统调用时,如果原 P 还在,那么最好。否则找一个闲置 P 继续。

// proc.go

// The goroutine g exited its system call.

func exitsyscall() {
    _g_ := getg()
    oldp := _g_.m.oldp.ptr()
    
    // 看老伙计还在不在(entersyscall)。
    // 如在,继续执行当前,无需切入调度循环。
    if exitsyscallfast(oldp) {
        
        // 累加计数,修改状态退出系统调用。
        _g_.m.p.ptr().syscalltick++  
        casgstatus(_g_, _Gsyscall, _Grunning)
        
        return
    }
    
    // Call the scheduler.
    mcall(exitsyscall0)
}
func exitsyscallfast(oldp *p) bool {
    
    // 继续使用原 P。(运气好,没被抢)
    if oldp != nil && oldp.status == _Psyscall && atomic.Cas(&oldp.status, _Psyscall, _Pidle) {
        wirep(oldp)
        exitsyscallfast_reacquired()
        return true
    }
    
    // 尝试获取一个空闲 P.
    if sched.pidle != 0 {
        systemstack(func() {
            ok = exitsyscallfast_pidle()
        })
        
        if ok {
            return true
        }
    }
    
    return false
}
func exitsyscallfast_pidle() bool {
    _p_ := pidleget()
    if _p_ != nil {
        acquirep(_p_)
        return true
    }
    
    return false
}

如绑定 P 失败,将 G 放回全局队列,M 休眠。

// exitsyscall slow path on g0.
// Failed to acquire P, enqueue gp as runnable.

func exitsyscall0(gp *g) {

    // 修改状态,解除 M 关联。
    casgstatus(gp, _Gsyscall, _Grunnable)
    dropg()
    
    // 获取闲置 P。
    var _p_ *p
    if schedEnabled(_g_) {
        _p_ = pidleget()
    }
    
    // 获取失败,将 G 放回全局队列。
    if _p_ == nil {
        globrunqput(gp)
    }
    
    // 关联 P,继续执行 G。
    if _p_ != nil {
        acquirep(_p_)
        execute(gp, false) // Never returns.
    }
    
    // 锁定模式。
    if _g_.m.lockedg != 0 {
        stoplockedm()
        execute(gp, false) // Never returns.
    }
    
    // 绑定失败,休眠。
    // 等待唤醒。
    stopm()
    schedule() // Never returns.
}

cgo

采取类似方式。

// cgocall.go

// Call from Go to C.

func cgocall(fn, arg unsafe.Pointer) int32 {
    entersyscall()
    errno := asmcgocall(fn, arg)
    exitsyscall()
}

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

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

发布评论

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