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

Kernel spin-lock enables preemption before releasing lock

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

显示我们来自何处的简短调用跟踪:

spin_lock calls raw_spin_lock,

raw_spin_lock calls _raw_spin_lock

on a uni-processor system, _raw_spin_lock is #defined as __LOCK

__LOCK 是一个定义:

#define __LOCK(lock) \
  do { preempt_disable(); ___LOCK(lock); } while (0)

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

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

#define __UNLOCK(lock) \
  do { preempt_enable(); ___UNLOCK(lock); } while (0)

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

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

您正在查看单处理器定义。正如 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:
 */

___LOCK___UNLOCK 宏用于注释目的,除非定义了 __CHECKER__(它由 sparse 定义),否则它最终是编译出来了。

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