linux中断的不确定性带来的问题

如果说一个函数不能在中断中调用,那么很多人都会想到那个函数可能会睡眠,而中断中不能睡眠。可是仅仅是因为不能睡眠吗?实际上因为中断是完全不确定的,所以它带来的限制不仅仅只是不能睡眠,往往确定的东西我们直接就可以管理,而不确定的东西就要靠限制来管理了。不能睡眠就是限制之一,当然还有别的,看一 下下面的代码:

int del_timer_sync(struct timer_list *timer) //2.6.9版本

{

         tvec_base_t *base;

         int i, ret = 0;

         check_timer(timer);

del_again:

         ret += del_timer(timer);  //将此timer的base置为NULL,以防止它重新被add,此函数会争抢base的自旋锁

         for_each_online_cpu(i) {  //确保此timer在所有的cpu上都结束其生命

                 base = &per_cpu(tvec_bases, i);

                 if (base->running_timer == timer) {

                         while (base->running_timer == timer) {

                                 cpu_relax();

                                 preempt_check_resched();//如果在中断中,抢占计数不为0,抢占计数不为0的条件下,这个函数里面是不会调用schedule的。

                         }

                         break;

                 }

         }

         smp_rmb();

         if (timer_pending(timer)) //必须再次确认此timer在所有的cpu上都结束其生命

                 goto del_again;

         return ret;

}

linux 明确规定上述函数不能在中断上下文调用,再看看上述函数的实现发现怕的不是它会睡眠,而是怕它会死锁,看看那个无穷的goto del_again吧,只要有无穷的循环就有死锁的可能,因为跳出循环是需要条件的,如果那个条件死锁了,系统实际上就down掉不干活了。现在我们看看 它在中断上下文为何会死锁,我们希望如果在timer的function中调用上述函数来删除自身那么必然在base的spin_lock上死锁,因为我 们不喜欢任何理由的"自指行为",可是在执行timer的function之前base的自旋锁已经放开了,没有达到我们的希望的要求,这种情况不是我们 关注的。我们知道在中断处理执行的时候是抢占失效的,如果中断的cpu的running_timer就是这个要删除的timer的话,此timer的 base的自旋锁就已经被占有了(比如在__run_timers函数中,系统中断时正好在set_running_timer(base, timer)和spin_unlock_irq(&base->lock)之间),而在del_timer中会有获得自旋锁的操作,这样的 话就会死锁,注意这里的死锁不是无限循环造成的,而是自旋锁造成的,正是因为中断的不确定性才会导致你根本不发确定发生中断的时候是否那个timer正在 运行或者那个timer的base的自旋锁是否已经被拥有。同样还是这个函数,在后期的版本中造成死循环的原因就更加明显了

int del_timer_sync(struct timer_list *timer)

{

         for (;;) {  //事发地,恐怖的无限循环

                 int ret = try_to_del_timer_sync(timer);

                 if (ret >= 0)  //退出条件

                         return ret;

                 cpu_relax();

         }

}

int try_to_del_timer_sync(struct timer_list *timer)

{

         struct tvec_base *base;

         unsigned long flags;

         int ret = -1;

         base = lock_timer_base(timer, &flags);  //这lock_timer_base实际上也会争抢base的自旋锁从而造成和2.6.9版本一样原因的死锁。

         if (base->running_timer == timer)  //如果中断的正是本timer的base的循环处理函数,那么直接返回-1,从而使得del_timer_sync中的循环退出条件不满足,进入无限死循环

                 goto out;

         ret = 0;

         if (timer_pending(timer)) {

                 detach_timer(timer, 1);

                 ret = 1;

         }

out:

         spin_unlock_irqrestore(&base->lock, flags);

         return ret;

}

可 以看到,虽然死锁点在lock_timer_base,但是即使没有这个死锁点,该函数在中断上下文中调用还是可能死锁的,那个无穷的for循环就是另一 个死锁点,虽然内核可抢占而且中断可以嵌套重入,但是由于初始被中断的进程的抢占计数器不为0,进程还是无法切换,实际上就死锁在这里了。你是否会说,死 锁这么容易造成是不是锁的粒度太大了,一锁就锁一个base,只能说有这方面的原因,可是这个原因不是本质原因,本质原因就是中断的不确定性,还是那样, 你无法确定中断发生的时间以及中断发生时系统所处的状态。
同样的原因,do_exit的上下文不能是中断上下文,那样的话你可能释放任意进程。因此,基于中断的不确定性,内核规定了种种限制,而开发人员只有遵守这些限制规则才可以写出没有bug的程序,当然这和闯红灯一样,如果你硬闯红灯,得到的不仅仅是罚单,可能要付出生命的代价。linux中中断的不确定性 使得中断处理过程和别的过程相比多少有些另类,如果能把中断处理和其他的执行过程统一起来的话,内核看上去会更加和谐一些,可是也仅仅是看上去而已,详情请参考windows的irql,那个实现十分和谐,但是又带来了新的问题。最后我们得到一个总结:如果遇到不确定的另类因素,我们是保持它的另类呢还是为了美观强行将其与别的传统统一呢?linux的做法就是保持它的另类,而windows的做法恰恰相反,谁对谁错,无从考证,看你关注哪一方面了个人比较推崇linux的方式,事情已经不可收拾的情况下,维持原状可能仍然不可收拾,但是你的任何强加可能使事情更加不可收拾,因为它原本就是那样的,惰性往往是自我保护的一种很好的方式。

原文链接: https://blog.csdn.net/dog250/article/details/5303635

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍;

也有高质量的技术群,里面有嵌入式、搜广推等BAT大佬

    linux中断的不确定性带来的问题

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/410019

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年4月26日 上午11:46
下一篇 2023年4月26日 上午11:46

相关推荐