LWN:使用rseq()来帮助完成用户空间自旋锁!

Linux News搬运工

共 2431字,需浏览 5分钟

 ·

2023-10-12 02:30

关注了就能看到更多这么棒的文章哦~

User-space spinlocks with help from rseq()

By Jonathan Corbet
September 22, 2023
OSSEU
ChatGPT translation
https://lwn.net/Articles/944895/

之前五月份的时候,André Almeida在使用自适应自旋方式(adaptive spinning)创建用户空间自旋锁(user-space spinlocks)的工作 进行了一些工作。当时,这项工作因为Linux中目前没有一种方法能快速确定指定线程是否实际在CPU上执行而停滞不前。此后在2023年欧洲开源峰会上,Almeida又回来继续讨论了如何克服这个困难。

2ffd693fafe3e4c02e86206d078b81e6.webp

他首先对锁相关的原语(locking primitives)以及自旋锁的工作原理进行了概述。简而言之,自旋锁之所以被命名为自旋锁,是因为如果尝试获取锁失败,代码将在一个循环中重新检查其状态(因此称为“自旋”),直到锁可用为止。在内核中相对容易实现自旋锁,因为根据自旋锁操作的规则,锁的持有者目前必定正在系统中的某个CPU上运行,应该很快释放锁;这确保了自旋导致的CPU时间损失会很小。

在用户空间,情况更加复杂。一个线程可能正在持续试图获取自旋锁,但是锁的当前持有者已被抢占,根本不在运行。在这种情况下,锁不会很快释放,自旋就只会浪费CPU时间。在最坏的情况下,正在自旋的线程可能正是阻止它所需的锁被释放的线程,这意味着自旋线程正在主动阻止其所需的锁被释放。在这种情况下,代码应该停止自旋,马上休眠,直到锁被释放。

然而,这样做需要有一种方式能让获取锁的这部分代码知道锁的所有者不在运行。可以为此添加一个系统调用,但系统调用开销很大;在这种情况下,系统调用开销可能很容易超过由锁保护的临界区(critical section)中花费的时间。如果必须要调用进入内核,最好是保持阻塞直到锁被释放。真正需要的是一种不进行系统调用就能获取这些信息的方法。

在五月份的讨论中提到了使用可重启序列 (restartable sequences)功能来获取这些信息。这个子系统在scheduler调度器内有hook,用于跟踪任务抢占等事件;它还使用一个共享内存段(shared-memory segment)来将某些信息传递给用户空间。也许可重启序列也可以用来解决这个问题?

可重启序列代码的维护者Mathieu Desnoyers迅速回应并提供了一个实现这一功能的补丁。这个补丁在内核和用户空间之间共享的 rseq 结构中添加了一个新的成员:

  struct rseq_sched_state {
/*
* Version of this structure. Populated by the kernel, read by
* user-space.
*/
__u32 version;
/*
* The state is updated by the kernel. Read by user-space with
* single-copy atomicity semantics. This field can be read by any
* userspace thread. Aligned on 32-bit. Contains a bitmask of enum
* rseq_sched_state_flags. This field is provided as a hint by the
* scheduler, and requires that the page holding this state is
* faulted-in for the state update to be performed by the scheduler.
*/
__u32 state;
/*
* Thread ID associated with the thread registering this structure.
* Initialized by user-space before registration.
*/
__u32 tid;
};

state 字段包含一组描述了所关注进程的执行状态的flag,这是此方案的关键。这里只定义了一个flag, RSEQ_SCHED_STATE_FLAG_ON_CPU 。每当与该结构关联的线程被放置到CPU上执行时,都会设置这个flag;如果线程因任何原因停止运行,则该标志会再次被清0。

这些信息足以在用户空间实现自适应自旋。如果尝试获取自旋锁失败,第一步是检查持有锁的线程的 rseq_sched_state (这里隐含了一个要求就是这种通信发生在可以访问彼此的可重启序列状态的线程之间)。如果检查显示线程正在运行,那么自旋等待锁被释放就有意义的(当然,需要在循环内部进行检查,以检测随后持有者是否被抢占了)。否则,会进行系统调用,以简单地阻塞住,直到锁被释放。

不过,Almeida总结说他仍然不完全确定这个想法是否能够充分发挥其潜力。还有需要进行优化cache行为,并且将自适应自旋集成到POSIX线程锁定原语中,并进行大量的性能基准测试工作。但这种方法似乎很有可行性,剩下的只是具体工作量而已。
[感谢Linux基金会支持我们前往此活动。]

全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。

欢迎分享、转载及基于现有协议再创作~

长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~



浏览 33
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报