Jan*_*Jan 4 linux kernel locking scheduling linux-kernel
当我与一些同事讨论 uni- 和 SMP 内核中自旋锁的行为时,我们深入研究了代码,发现了一条令我们非常惊讶的行,我们无法弄清楚为什么会这样。
简短的 calltrace 来显示我们来自哪里:
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 而不是相反的想法是什么?
您正在查看单处理器定义。正如评论中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()是在单个处理器情况下进行锁定的人。