返回介绍

上卷 程序设计

中卷 标准库

下卷 运行时

源码剖析

附录

4.5.6 内核函数

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

内核使用的一些常用函数。

gopark, goready

休眠函数 gopark 解除当前 G 和 MP 的绑定,让 MP 重新去执行其他任务。

重点是 当前 G 并没有放回任务队列,除非直接唤醒,否则再不会被调度执行。

// proc.go

// Puts the current goroutine into a waiting state and calls unlockf.
// If unlockf returns false, the goroutine is resumed.

func gopark(unlockf func(*g, unsafe.Pointer) bool, lock unsafe.Pointer, reason waitReason, ...) {
    mp := acquirem()
    gp := mp.curg
    
    mp.waitlock = lock
    mp.waitunlockf = unlockf
    
    // mcall 会自动获取当前 G 做为 park_m 的调用参数。
    // 同时会保存当前 G.sched 状态,详情参考本章内核调用。
    mcall(park_m)
}
// park continuation on g0.
func park_m(gp *g) {
    
    // 这是 g0,参数 gp 是用户 G。
    _g_ := getg()
    
    // 修改状态,解除 G(gp)、M 关联。
    casgstatus(gp, _Grunning, _Gwaiting)
    dropg()
    
    // 调用 unlockf 函数。
    // 如果失败,则继续执行 G(gp),表示休眠失败。
    if fn := _g_.m.waitunlockf; fn != nil {
        ok := fn(gp, _g_.m.waitlock)
        _g_.m.waitunlockf = nil
        _g_.m.waitlock = nil
        
        if !ok {
            casgstatus(gp, _Gwaiting, _Grunnable)
            execute(gp, true) // Schedule it back, never returns.
        }
    }
    
    // 当前 MP 进入调度循环,执行其他任务。
    schedule()
}
func dropg() {
    // M.g0
    _g_ := getg()
    
    // 透过 g0 访问 G 和 M,解除关联。
    setMNoWB(&_g_.m.curg.m, nil)
    setGNoWB(&_g_.m.curg, nil)
}

与之配套的是 goready 函数。

它负责将因 gopark 休眠的 G 放回队列,甚至可以优先使用 runnext。

func goready(gp *g, traceskip int) {
    systemstack(func() {
        ready(gp, traceskip, true)
    })
}
// Mark gp ready to run.
func ready(gp *g, traceskip int, next bool) {
    
    // 修改状态,并重新放回队列。
    casgstatus(gp, _Gwaiting, _Grunnable)
    runqput(_g_.m.p.ptr(), gp, next)
    
    // 尝试唤醒 MP 出来服务。
    wakep()
}

notesleep

像 linux、freebsd 基于 futex 实现,其他操作系统则使用信号量(semaphore)。

它并不解除 G、MP 关联,适合一些自旋(spanning)等待场合。

Futex 通常称作 “快速用户区互斥”,是一种在用户空间实现的锁(互斥)机制。

多执行单位(进程或线程)通过共享同一快内存(整数)来实现等待和唤醒操作。

因为 Futex 只在操作结果不一致时才进入内核仲裁,所以有非常高的执行效率。

更多内容请参考 man 2 futex。

基于 futex 实现的 lock/unlock,参考《8. 其他》。

// runtime2.go

type m struct {
    park  note
}

type note struct {
    key uintptr
}

围绕 note.key 值进行休眠和唤醒操作。

// lock_futex.go

func notesleep(n *note) {
    ns := int64(-1)
    for atomic.Load(key32(&n.key)) == 0 {    
        futexsleep(key32(&n.key), 0, ns)     // 如果 key == 0,休眠。(ns < 0,表示不超时)
    }                                        // 直到被唤醒,跳出循环。
}
func notewakeup(n *note) {
    old := atomic.Xchg(key32(&n.key), 1)     // 修改 key = 1。
    futexwakeup(key32(&n.key), 1)            // 唤醒 1 个等待。
}
func noteclear(n *note) {                    // 将 key 赋值为 0。
    n.key = 0
}

通常情况下,notesleep 和 noteclear 组合使用。

1 表示唤醒,所以要重置为 0,否则下次 notesleep 失效。

notesleep(&_g_.m.park)   // 被唤醒后 key = 1
noteclear(&_g_.m.park)   // 重置 key = 0
notewakeup(&mp.park)     // 设置 key = 1

futex

具体实现依赖操作系统提供。

注意,futexsleep & futexwakeup 都不会修改 *addr 值,需要自行处理。

// os_linux.go

// Atomically,
//     if(*addr == val) sleep
// Might be woken up spuriously; that's allowed.
// Don't sleep longer than ns; ns < 0 means forever.

func futexsleep(addr *uint32, val uint32, ns int64) {
    
    // 不超时。
    if ns < 0 {
        futex(unsafe.Pointer(addr), _FUTEX_WAIT_PRIVATE, val, nil, nil, 0)
        return
    }
    
    var ts timespec
    ts.setNsec(ns)
    
    // 休眠,直到超时。
    futex(unsafe.Pointer(addr), _FUTEX_WAIT_PRIVATE, val, unsafe.Pointer(&ts), nil, 0)
}
// If any procs are sleeping on addr, wake up at most cnt.

func futexwakeup(addr *uint32, cnt uint32) {
    
    // 唤醒 cnt 个等待。
    ret := futex(unsafe.Pointer(addr), _FUTEX_WAKE_PRIVATE, cnt, nil, nil, 0)
    if ret >= 0 {
        return
    }
}

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

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

发布评论

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