FUTEX_SWAP补丁分析-SwitchTo?如何大幅度提升切换性能?
扫描二维码
随时随地手机看文章
作者简介
胡哲宁,西安邮电大学计算机科学与技术专业大二学生。
Google SwitchTo
由于协程本身对操作系统的不可见性,协程中出现的 BUG 往往不能通过一些已有的工具去排查。在谷歌内部有一套闭源的用户态任务调度框架SwitchTo
, 这个框架可以为谷歌提供延迟敏感的服务,对运行的内容进行细粒度的用户空间控制/调度,它可以让内核来实现上下文的切换,同时将任务何时切换,何时恢复的工作交给了用户态的程序来做,这样既可以实现在任务间协作式切换的功能,又可以不丧失内核对于任务的控制和观察能力。谷歌去年恢复尝试将其 SwitchTo
API 上游引入 Linux。相关补丁见:[1],[2],[3],[4].1pid_t switchto_wait(timespec *timeout)
2/* Enter an 'unscheduled state', until our control is re-initiated by another thread or external event (signal). */
3void switchto_resume(pid_t tid)
4/* Resume regular execution of tid */
5pid_t switchto_switch(pid_t tid)
6/* Synchronously transfer control to target sibling thread, leaving the current thread unscheduled.Analogous to:Atomically { Resume(t1); Wait(NULL); }
7*/
这是使用 SwitchTo
和使用其他线程间切换的组件的上下文切换性能对比:Benchmark | Time(ns) | CPU(ns) | Iterations |
---|---|---|---|
BM_Futex | 2905 | 1958 | 1000000 |
BM_GoogleMutex | 3102 | 2326 | 1000000 |
BM_SwitchTo | 179 | 178 | 3917412 |
BM_SwitchResume | 2734 | 1554 | 1000000 |
SwitchTo
后切换的性能比其他组件提高了一个数量级别。SwitchTo
是如何做到在切换性能上大幅度领先的呢?我们暂时可能无法看到它们,但让我们来看看 Peter Oskolkov
向 LKML(Linux Kernel Mail List) 提出的补丁中有关 futex_swap()
的实现。可以确定的是,SwitchTo
构建在这个内核函数之上。什么是 futex
futex
全称 fast user-space locking
,快速用户空间互斥锁,作为内核中一种基本的同步原语,它提供了非常快速的无竞争锁获取和释放,用于构建复杂的同步结构:互斥锁、条件变量、信号量等。由于 futex
的一些机制和使用过于复杂,glibc
没有为 futex
提供包装器,但我们仍然可以使用 syscall
来调用这个 极其 hack 的系统调用。1static int futex(uint32_t *uaddr, int futex_op, uint32_t val,
2 const struct timespec *timeout, uint32_t *uaddr2,
3 uint32_t val3)
4 {
5 return syscall(SYS_futex, uaddr, futex_op, val, timeout, uaddr2, val3);
6}
uaddr
: 一个四字节的用户空间地址。多个任务间可以通过*uaddr
的值的变化来控制阻塞或者运行。futex_op
: 用于控制futex
执行的命令 如FUTEX_WAIT
,FUTEX_WAKE
,FUTEX_LOCK_PI
,FUTEX_UNLOCK_PI
…val
: 在不同的futex_op
具有不同的含义,如在futex(uaddr, FUTEX_WAKE)
中作为唤醒等待在该futex
上所有任务的数量。timeout
: 作为等待(如FUTEX_WAIT
)的超时时间。uaddr2
:uaddr2
参数是一个四字节的用户空间地址 在需要的场景使用(如后文的FUTEX_SWAP
)。val3
: 整数参数val3
的解释取决于在操作上。
为什么 futex “快速”?
由于用户模式和内核模式之间的上下文切换很昂贵,futex
实现的同步结构会尽可能多地留在用户空间,这意味着它们只需要执行更少的系统调用。futex
的状态存储在用户空间变量中,futex
可以通过一些原子操作在没有竞争的情况下更改 futex
的状态,而无需系统调用的开销。futex_wait() 和 futex_wake()
在看futex_swap()
之前让我们先看看 内核中 与 futex
最重要的两个内核函数:1static int futex_wait(u32 __user *uaddr, unsigned int flags, u32 val, ktime_t *abs_time, u32 bitset);
简单来说 对于 futex_wait()
有用的参数就只有 uaddr
,val
,abs_time
,就像 futex_wait(uaddr,val,abs_time)
。其含义是当这个用户空间地址 uaddr
的值等于传入的参数 val
的时候睡眠,即 if (*uaddr == val) wait()
. futex_wake()
可以将它唤醒,另外还可以通过指定超时时间来超时唤醒。 1static int futex_wait(u32 __user *uaddr, unsigned int flags, u32 val,
2 ktime_t *abs_time, u32 bitset)
3{
4 struct hrtimer_sleeper timeout, *to;
5 struct restart_block *restart;
6 struct futex_hash_bucket *hb;
7 struct futex_q q = futex_q_init;
8 int ret;
9
10 if (!bitset)
11 return -EINVAL;
12 q.bitset = bitset;
13 /* 设置定时器 */
14 to = futex_setup_timer(abs_time,