- 论坛徽章:
- 0
|
本帖最后由 l4rmbr 于 2014-07-30 18:51 编辑
mingzhi5288 发表于 2014-07-18 14:10 ![]()
在单cpu下 使用spin_lock_irqsave 会变成
{
preempt_disable()
local_irq_save();
}
在使用这个函数的时候 如果用了schedule 为什么不可以呢?
是因为调度另一个进程之后不行呢?
还是根本无法调度另一个进程 ?
不能睡眠的原因能够从源码角度帮我分析一下吗?
以下讨论只适用于mainline kernel,不适用linux-rt kernel.
假设在单CPU下,进入临界区可以抢占。看看会发生什么问题。
考虑以下可能的场景, 一个双进程竞争同一资源的场景:
Task 1:
if ( is_resource_availabel() )
get_resource();
它做一个检查,检查资源是否可用,可用的话,就获取它。
在单CPU上,这是安全的操作。但是,一旦考虑抢占,那么就可能出现TOCTTOU问题(Time of check to time of use):
这是一个两步的操作,即先检查,状态符合后,再使用。
这种非原子化的操作,存在中间状态,如下:
if ( is_resource_availabel() )
<--- 中间状态 --->
get_resource();
假设Task 2在中间状态抢占了Task 1, 并且,它也执行该段代码:
if ( is_resource_availabel() )
get_resource();
注意,Task 1 在检查资源可用后即被抢占,它实际并未改变资源状态,所以,Task 2也成功检查了资源可用,
并成功get_resource()。然后,它被调度,又回到Task 1的执行上下文。
假设资源是唯一的。此时Task 1运行get_resource()失败。这在Task 1来看,就是不一致的状态,明明它检查成功,
结果获取资源时却失败。
因此,在单CPU上,在可抢占的情况下,这段代码是一段可能引起竞争的临界区,必须获取排它的权限,如自旋锁,之后才能
进入。
spin_lock(lock);
if ( is_resource_availabel() )
get_resource();
spin_unlock(lock);
但获得锁还没解决问题!
再考虑另一场景:
Task 1获取了锁lock,然后被Task 2抢占了。
假设Task 2执行以下代码:
spin_lock(another_lock);
... do something ...
spin_lock(lock);
if ( is_resource_availabel() )
get_resource();
spin_unlock(lock);
spin_unlock(another_lock);
Task 2先获取另一个锁another_lock,然后在获取被Task 1拿着的lock时自旋等待。
直到Task 2被调度或抢占。但内核并不能保证能调度回Task 1。
如果调度到Task 3, 而Task 3想获取another_lock!
于是,进入了一个僵死局面,可以看到这里有循环依赖的问题。这是AB-BA死锁的表现。
正常编程应该避免。但如果依赖链条太长,你是根本看不出来存在死锁的。
综上两种情况,都是抢占带来的问题。于是,一种简单的做法就是,在进入临界区时,关闭抢占!
这样,这里的spin_lock()只要实现为关闭抢占就可以了:
void spin_lock(lock) {
preemt_disable();
}
void spin_unlock(lock) {
preemt_enable();
}
这种简单,但粗暴的方法,却可以一箭双雕,解决以上两个问题。
呼~ 在单CPU下,关了抢占,似乎可以高枕无忧了!但!还存在一种情况,中断!
在上面的临界区中,只是关闭了抢占,但并未关闭中断。假设在上述临界区中,发生了中断,
在中断处理上下文中,理论上也可以发生上述情况.
还有更糟的情况是,假设在中断处理上下文中,也要获取该锁。因为锁被进程获取着,所以获取失败,
于是, 中断处理程序会在原地自旋, 等待锁被释放。 问题就来了,中断上下文是只能被更高优先级的中断抢占,
但这个进程却不行,进程抢占不了,它就无法释放锁; 无法释放锁, 中断处理程序就只能自旋,于是,就死锁了。
所以,新增加一个中断安全的spin_lock:
void spin_lock_irqsave(lock) {
preemt_disable();
local_irq_save();
}
另,注意到,在单CPU上,关闭本地中断,除非进程主动调用schedule(), 否则调度器是无法被激活的,所以,关闭中断,
也相当于把抢占给禁止了,所以,上述实现可以简化为:
void spin_lock_irqsave(lock) {
local_irq_save();
}
|
|