中断和锁的应用实例之自己挖的坑自己填: )
题目:
请指出如下代码缺陷在哪?
前提条件:foo1有可能在进程上下文或中断上下文执行,且假设执行该函数之前没有关闭中断。
func foo1(void)
{
raw_spin_lock(&lock);
/* 被保护临界区 */
raw_spin_unlock(&lock);
}
foo2是timer callback, 如下:
func foo2(void)
{
raw_spin_lock(&lock))
/* 被保护临界区 */
raw_spin_unlock(&lock);
}
func main(void)
{
timer_setup(&timer, foo2, TIMER_PINNED);
}
考察点:
(1)中断上半部和下半部(定时器回调函数执行在下半部执行)。
(2)一个软中断不会抢占另一个软中断,唯一可以抢占软中断的是硬中断。
(3)自旋锁的使用,避免死锁。
答案解析:
定时器作为软中断是在下半部上下文(中断上下文)中执行的。
1.如果foo1先拿锁,有以下三种情况:
(1)foo1在进程上下文执行,
时钟中断到,执行foo2,此时拿锁是拿不到的。因为foo1处于进程上下文此时CPU已被foo2抢占,foo1始终无法释放锁,此时发生死锁。
(2)foo1在中断上半部执行,
时钟中断到,因为timer的中断执行函数在软中断中执行,属于下半部上下文,不能抢占上半部,所以不会发生死锁。
(3)foo1在中断下半部执行,
时钟中断到,因为foo2和foo1同为下半部执行,所以不会抢占foo1,不会死锁。
2.如果foo2先拿锁,也有以下三种情况:
(1)foo1在进程上下文执行,
因为foo2在中断上下文,不会被foo1抢,所以不会发生死锁。
(2)foo1在中断上半部执行,
因为foo1之前没有关中断,foo2在软中断中执行,硬中断可以打断软中断,foo2未释放锁,foo1拿不到锁,发生死锁。
(3)foo1在中断下半部执行,
时钟中断到,因为foo2和foo1同为下半部执行,所以不会抢占foo2,不会死锁。
中断处理函数只有被执行,才有机会去拿锁,如果连被执行的机会都没有,是不会有抢别人锁的机会的。
附加题:
如果在foo2中做如下修改,可以避免死锁吗?
func foo2(void)
{
if (!raw_spin_trylock(&lock))
return;
/* 被保护临界区 */
raw_spin_unlock(&lock);
}
解析:
这种情况和foo2先拿锁时foo1在中断上半部执行的情况是相同的,可能会造成死锁。
几个问题总结:
1.linux中断可以嵌套吗?linux中断有优先级吗?中断抢占?
参考链接:
中断没有优先级的问题
中断不可以嵌套
韦东山:一文看懂linux对中断处理的前世今生
↓
中断不能嵌套
官方资料:中断处理不能嵌套
中断处理函数需要调用C函数,这就需要用到栈。 中断A正在处理的过程中,假设又发生了中断B,那么在栈里要保存A的现场,然后处理B。在处理B的过程中又发生了中断C,那么在栈里要保存B的现场,然后处理C。 如果中断嵌套突然暴发,那么栈将越来越大,栈终将耗尽。所以,为了防止这种情况发生,也是为了简单化中断的处理,在Linux系统上中断无法嵌套:即当前中断A没处理完之前,不会响应另一个中断B(即使它的优先级更高)。
——摘自《韦东山:一文看懂linux对中断处理的前世今生》
2.硬中断和软中断的区别?
3.软中断和软件中断是一回事吗?
4.软中断类型
参考源码:include/linux/interrupt.h
/* PLEASE, avoid to allocate new softirqs, if you need not _really_ high
frequency threaded job scheduling. For almost all the purposes
tasklets are more than enough. F.e. all serial device BHs et
al. should be converted to tasklets, not to softirqs.
*/
enum
{
HI_SOFTIRQ=0,
TIMER_SOFTIRQ,
NET_TX_SOFTIRQ,
NET_RX_SOFTIRQ,
BLOCK_SOFTIRQ,
IRQ_POLL_SOFTIRQ,
TASKLET_SOFTIRQ,
SCHED_SOFTIRQ,
HRTIMER_SOFTIRQ,
RCU_SOFTIRQ, /* Preferable RCU should always be the last softirq */
NR_SOFTIRQS
};
同上,参考《Linux内核设计与实现(第三版)》P113
tasklet | 优先级 | 软中断描述 |
---|---|---|
HI_SOFTIRQ | 0 | 优先级高的tasklet |
TIMER_SOFTIRQ | 1 | 定时器的下半部 |
NET_TX_SOFTIRQ | 2 | 发送网络数据包 |
NET_RX_SOFTIRQ | 3 | 接收网络数据包 |
BLOCK_SOFTIRQ | 4 | BLOCK装置 |
IRQ_POLL_SOFTIRQ | 5 | |
TASKLET_SOFTIRQ | 6 | 正常优先权的tasklet |
SCHED_SOFTIRQ | 7 | 调度相关 |
HRTIMER_SOFTIRQ | 8 | 高精度定时器 |
RCU_SOFTIRQ | 9 | RCU锁定 |
NR_SOFTIRQS | 10 |
5.中断上下文加什么锁?自旋锁还是互斥锁?
中断上下文只能加自旋锁,因为中断上下文是不允许睡眠的。互斥锁是允许睡眠的,所以不能用。
6.软中断的数据被其他软中断共享,加锁的必要性?加什么锁?
因为软中断是在中断上下文,所以加锁要加自旋锁。
对于软中断,无论是否同种类型,如果数据被软中断共享,那么它必须得到锁的保护。因为即使是同种类型的两个软中断也可以同时运行在一个系统的多个处理器上。但是,同一处理器上的一个软中断绝不会抢占另一个软中断。
------摘自《Linux内核设计与实现(第三版)》P150 10.2.3自旋锁和下半部
7.下半部和进程上下文或中断上下文共享数据时使用自旋锁的注意事项?
由于下半部可以抢占进程上下文中的代码,所以当下半部和进程上下文共享数据时,必须对进程上下文中的共享数据进行保护,所以需要加锁的同时还要禁止下半部执行。
同样,由于中断处理程序可以抢占下半部,所以如果中断处理程序和下半部共享数据,那么就必须在获取恰当的锁的同时还要禁止中断。
------摘自《Linux内核设计与实现(第三版)》P150 10.2.3自旋锁和下半部
8.使用自旋锁的注意事项
更新
(1)linux内核自旋锁不可递归。要防止自己被自己锁死。
(2)
中断处理程序的临界区保护只能使用自旋锁,但需要注意:在拿锁之前一定要禁止本地中断(在当前处理器上的中断请求),否则,中断处理程序就会打断正持有锁的内核代码,有可能会试图去争取这个已经被持有的自旋锁。这样一来,中断处理程序就会自旋,等待该锁重新可用,但是锁的持有者在这个中断处理程序执行完毕前不可能运行。这正是我们在前面的内容中提到的双重请求死锁。注意,需要关闭的只能是当前处理器上的中断。如果中断发生在不同的处理器上,即使中断处理程序在同一锁上自旋,也不会妨碍锁的持有者(在不同处理器上)最终释放锁。
内核提供的禁止本地中断请求锁的接口,如下:
DEFINE_SPINLOCK(mr_lock);
unsigned long flags;
spin_lock_irqsave(&mr_lock,flags); //保存中断当前状态并禁止本地中断,然后再去获取指定的锁。
//临界区
spin_unlock_irqrestore(&mr_lock,flags); //释放锁,让中断恢复到加锁前状态
------摘自《Linux内核设计与实现(第三版)》P149 10.2.1自旋锁方法