内核自旋锁在释放锁之前启用抢占

Jan*_*Jan 4 linux kernel locking scheduling linux-kernel

当我与一些同事讨论 uni- 和 SMP 内核中自旋锁的行为时,我们深入研究了代码,发现了一条令我们非常惊讶的行,我们无法弄清楚为什么会这样。

简短的 calltrace 来显示我们来自哪里:

spin_lock 调用 raw_spin_lock

raw_spin_lock 调用 _raw_spin_lock,和

在单处理器系统上,_raw_spin_lock #defined 为 __LOCK

__LOCK 是一个定义:

#define __LOCK(lock) \
  do { preempt_disable(); ___LOCK(lock); } while (0)
Run Code Online (Sandbox Code Playgroud)

到现在为止还挺好。我们通过增加内核任务的锁定计数器来禁用抢占。我认为这样做是为了提高性能:因为你不应该持有一个自旋锁超过很短的时间,你应该只是完成你的关键部分而不是被中断,并且可能有另一个任务在等待你的时候旋转它的调度片段结束。

然而,现在我们终于来回答我的问题了。对应的解锁码如下所示:

#define __UNLOCK(lock) \
  do { preempt_enable(); ___UNLOCK(lock); } while (0)
Run Code Online (Sandbox Code Playgroud)

为什么要在 ___UNLOCK 之前调用 preempt_enable()?这对我们来说似乎很不直观,因为您可能会在调用 preempt_enable 后立即被抢占,而没有机会释放您的自旋锁。感觉这使得整个 preempt_disable/preempt_enable 逻辑有些无效,特别是因为 preempt_disable 在其调用期间专门检查锁计数器是否再次为 0,然后调用调度程序。在我们看来,首先释放锁,然后减少锁计数器并因此可能再次启用调度会更有意义。

我们缺少什么?在 ___UNLOCK 之前调用 preempt_enable 而不是相反的想法是什么?

Fre*_*rdt 5

您正在查看单处理器定义。正如评论中spinlock_api_up.h所说(http://lxr.free-electrons.com/source/include/linux/spinlock_api_up.h#L21):

/*
 * In the UP-nondebug case there's no real locking going on, so the
 * only thing we have to do is to keep the preempt counts and irq
 * flags straight, to suppress compiler warnings of unused lock
 * variables, and to add the proper checker annotations:
 */
Run Code Online (Sandbox Code Playgroud)

___LOCK___UNLOCK宏在那里为注释目的的,除非__CHECKER__被定义(它定义sparse),它最终将被编出来。

换句话说,preempt_enable()并且preempt_disable()是在单个处理器情况下进行锁定的人。