分类: LINUX
2014-07-18 14:50:06
原文地址:内核中断 上半部 下半部 分析 作者:fjaygrfjaygr
内核中断-2014.7
1. arm平台的中断
arm核心拥有2个外部中断线,IRQ和FIQ;这两根中断线连接到中断控制器上;中断控制器(IC)利用IRQ/FIQ向arm核报告外部中断的产生;IC在上报中断之前,如果同时有多个中断产生,就要按照优先级进行排队,把优先级最高的中断送至“当前服务寄存器”,也就是报告给arm核,“当前服务寄存器”需要在中断服务代码中(在内核的中断服务公共代码中)进行清除,以允许新的中断到来;很可能上一个中断服务程序还没有处理完毕,下一个优先级更高的中断就到来了,这样上一个中断服务程序就会被打断,然后执行优先级高的中断服务程序;
2. 中断嵌套
相同中断号的中断服务程序是绝对不可能嵌套的,原因(1)在IC的中断排队寄存器中,优先级小于等于当前服务中断号的中断是不可能被报告给arm核的;(2)好像在linux内核代码中会使用disable_irq之类的函数禁止当前的中断?;总之,同一个中断服务程序是不会发生嵌套的,嵌套只发生在高优先级的中断打断低优先级的中断,从而发生嵌套;
3. 中断不可打断/中断不可调度/中断不能睡眠…..的解释
网上对这类问题的解释太混乱,这里针对arm-linux做出解释:(1)中断上半部是可以睡眠的,但是睡眠会使得内核变得非常复杂,难以追踪代码执行路径,所以强烈不支持在上半部使用会睡眠的函数,这样设计也是为了使得内核变得简单;(2)中断上半部确实不可以被调度,因为它不是一个可以被调度的实体,例如进程或者内核线程;事实上,应该根本就没有“中断可否调度”之类的问题;(3)中断上半部可以被打断,但是只能被优先级更高的中断打断,被打断后就进入高优先级中断的服务程序,也就是实现了中断嵌套;可以使用local_irq_ disable()或者local_irq_save()或者spin_lock_irqsave()禁止本CPU核的所有中断,也就是禁止IRQ线以及FIQ线,这样就不会发生中断的嵌套了;
4. 发生中断嵌套时的内核状态
首先看一段代码
asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
{
struct pt_regs *old_regs = set_irq_regs(regs);
irq_enter();
if (unlikely(irq >= NR_IRQS)) {
if (printk_ratelimit())
printk(KERN_WARNING "Bad IRQ%u\n", irq);
ack_bad_irq(irq);
} else {
generic_handle_irq(irq);
}
irq_finish(irq);
irq_exit();
set_irq_regs(old_regs);
}
这是arm平台下处理中断的开始点,每产生一个中断都会调用这个函数;irq_enter()就让内核执行路径进入了中断上下文;irq_exit()让内核退出中断上下文;所有的中断上半部都是在generic_handle_irq(irq)中被调用的,也就很明确的表明了使用request_irq()注册的中断服务程序是运行于中断上下文中的;当发生中断嵌套,假定只有2层嵌套,当高优先级中断服务程序返回后,内核会如何处理呢?irq_enter的作用是禁止抢占,是通过把preempt_count加上HARDIRQ_OFFSET,HARDIRQ_OFFSET代表中断的上半部,preempt_count是进程调度时用到的。也就是系统会根据preempt_count的值来判断是否可以调度。只有当preempt_count为0时才可以调度。当调用preempt_disable或add_preempt_count函数时都不可以进行调度,因为都会改变preempt_count的值为非0。
所以irq_enter就是告诉系统,现在正在处理中断的上半部分工作,不可以进行调度。你可能会奇怪,既然此时的irq中断都是都是被禁止的,为何还要禁止抢占?这是因为要考虑中断嵌套的问题,一旦驱动程序主动通过local_irq_enable打开了IRQ[2014.7.8日,事实上,在驱动中注册的中断函数执行时,如果不使用local_irq_disable,中断总是打开的,就是为了允许中断嵌套],而此时该中断还没处理完成,新的irq请求到达,这时代码会再次进入irq_enter,在本次嵌套中断返回时,preempt_count不为0,内核不希望进行抢占调度,而是要等到最外层的被打断的中断处理完成后才做出调度动作,所以才有了禁止抢占这一处理 。
5. 一句话总结上半部
中断嵌套的整个过程都是处于中断上下文中;一旦处于中断上下文,或者说正在执行中断处理,那么当前的CPU核上是不可以被调度一个进程来运行的,由中断处理程序独占,只能被更高优先级的中断打断嵌套;中断上半部可以嵌套;……………注意,前面说的都是中断上半部!!!
6. 下半部
下半部虽说有softirq、tasklet、workqueue三种机制,但一般只用tasklet、workqueue;workqueue 就是一个普通的内核线程,所以它想睡眠就睡眠,任何内核线程能做的事它都可以做,这样的灵活性的代价就是它的效率比tasklet差;tasklet也是工作在中断上下文,下面专门讨论tasklet。
7. tasklet
同一个tasklet多次调度可能只会执行一次,所以需要在这种下半部中注意这种问题,比如对控制器的重置就可以使用tasklet,因为多次重置和一次重置的效果是完全一样的,由于do_softirq会判断当前是否处于软中断上下文,如果是,则直接退出不执行tasklet代码;一个tasklet同时只能在一个核上运行,不同的tasklet可以在多核上同时执行;tasklet应该继承了softirq的特点,谁(CPU核)调度,谁执行;tasklet处于中断上下文,所以也和普通中断一样,是独占CPU,但是会被优先级更高硬件中断IRQ打断,但是tasklet不会打断tasklet;其实还有很多问题不够明白!!!!!!但写驱动足以;
8. 关于软中断/tasklet的调用时刻
一般在中断上半部中会调度tasklet,上半部执行完后,在退出中断上下文时
void irq_exit(void)
{……………………………….
preempt_count_sub(HARDIRQ_OFFSET);
if (!in_interrupt() && local_softirq_pending())
invoke_softirq();
…………………………….
}
会用invoke_softirq来尝试运行被激活的软中断,接着__do_softirq,在这里,会关闭软中断并开启硬件(也就是上半部)中断,也就是印证了上面那句话,在同一个cpu上,软中断不可以被软中断打断,但是可以被硬件中断(也就是上半部)打断。
这里,是不是还可以表明,tasklet和workqueue作为下半部而言,tasklet会更快的得到执行。Workqueue应该是在irq_exit结束之后,退出中断上下文后,才有可能被调度。