专栏首页炉边夜话对中断的一点思考

对中断的一点思考

对中断的一点思考

杨小华(normalnotebook@126.com)

    对于X86的单处理器机器,一般采用可编程中断控制器8259A做为中断控制电路。传统的PIC(Programmable Interrupt Controller)是由两片8259A风格的外部芯片以“级联”的方式连接在一起。每个芯片可处理多达8个不同的IRQ输入线。因为从PIC的INT输出线连接到主PIC的IRQ2引脚,所以可用IRQ线的个数限制为15,如图1所示。

图 1  8259A级联原理图(此图摘自《Linux内核完全注释》)

    “中断屏蔽寄存器”(Interrupt Mask Register,简称IMR)用于屏蔽8259A的中断信号输入,每一位对应一个输入。当IMR中的bit[i](0≤i≤7)位被置1时,相对应的中断信号输入线IRi上的中断信号将被8259A所屏蔽,也即IRi被禁止。     当外设产生中断信号时(由低到高的跳变信号,80x86系统中的8259A是边缘触发的,Edge Triggered),中断信号被输入到“中断请求寄存器”(Interrupt Request Register,简称IRR),并同时看看IMR中的相应位是否已被设置。如果没有被设置,则IRR中的相应位被设置为1,表示外设产生一个中断请求,等待CPU服务。 然后,8259A的优先级仲裁部分从IRR中选出一个优先级最高中断请求。优先级仲裁之后,8259A就通过其INT引脚向CPU发出中断信号,以通知CPU有外设请求中断服务。CPU在其当前指令执行完后就通过他的INTA引脚给8259A发出中断应答信号,以告诉8259A,CPU已经检测到有中断信号产生。     8259A在收到CPU的INTA信号后,将优先级最高的那个中断请求在ISR寄存器(In-Service Register,简称ISR)中对应的bit置1,表示该中断请求已得到CPU的服务,同时IRR寄存器中的相应位被清零重置。     然后,CPU再向8259A发出一个INTA脉冲信号,8259A在收到CPU的第二个INTA信号后,将中断请求对应的中断向量放到数据总线上,以供CPU读取。CPU读到中断向量后,就可以装入执行相应的中断处理程序。     如果8259A工作在AEOI(Auto End Of Interrupt,简称AEOI)模式下,则当他收到CPU的第二个INTA信号时,它就自动重置ISR寄存器中的相应位。否则,ISR寄存器中的相应位就一直保持为1,直到8259A显示地收到来自于CPU的EOI命令。 打住,各位看官读到这里,能回答如下问题吗? 1.    在执行中断处理程序时,中断一直是关闭着的吗? 2.    在执行中断处理程序时,本条中断线上的中断是否会被屏蔽? 3.    如果该条中断线被屏蔽了,那么是否一直要到该中断返回(即执行iter指令)时才开启? 4.    禁止中断后,异常还会执行吗?timer还会被执行吗?     如果不能回答这些问题,请继续欣赏。如果你能回答,请关闭本文档,努力工作吧,或拿起一本英语书看看,这年头不好混,多看看英语吧 !:)     当中断发生,CPU在穿越中断门时会关闭本处理器上所有的中断。此时中断执行路线是:common_interrupt->do_IRQ()->__do_IRQ()->handle_IRQ_event()->具体的中断处理程序。大家都知道中断类型包括三种:

标志

含义

SA_INTERRUPT

当该位被设置时,表明这是一个快速的中断处理程序。在本地处理器上,快速中断处理程序在禁止所有中断的情况下运行。除了时钟中断外,绝大多数中断都不使用该标志。

SA_SHIRQ

该位表示中断可以在设备之间共享。

SA_SAMPLE_RANDOM

该位指出产生的中断能对/dev/random设备和/dev/urandom设备使用的熵池有贡献。

表 1中断类型标志位及其含义表

    如果相应的中断处理程序在注册时,即调用request_irq()函数进行中断处理程序注册时,会传递这三种中类型中的一个或数个。如果没有指定SA_INTERRUPT该类型。则在handle_IRQ_event()函数中会第一次开中断;如果指定了该参数,则会在关闭中断的情况下执行中断处理程序。 fastcall int handle_IRQ_event(unsigned int irq, struct pt_regs *regs,                 struct irqaction *action) {     int ret, retval = 0, status = 0;     if (!(action->flags & SA_INTERRUPT))         local_irq_enable();    ……//调用中断处理程序        local_irq_disable();     return retval; }     如果此时开中断了,本条IRQ线上的中断也打开了吗?我要告诉你的是,在执行到这里的时候,本条线上的中断已经被屏蔽了,但也不是问题3中所说的一直到iret时才打开。 fastcall unsigned int __do_IRQ(unsigned int irq, struct pt_regs *regs) {     irq_desc_t *desc = irq_desc + irq;     ……     desc->handler->ack(irq);     ……     action_ret = handle_IRQ_event(irq, regs, desc->action);     ……     desc->handler->end(irq);     …… } desc->handler->ack(irq);最终会调用mask_and_ack_8259A()(为什么会调用该函数,请查看源代码或相关书籍)。mask_and_ack_8259A的功能是:因为中断处理器在将中断请求“上报”到CPU后,期待CPU给它一个确认(ACK),也就是给8259A芯片一个应答信号,表示“我已经在处理”,应答时应该遵循这样的顺序:首先,屏蔽相应的IRQ;然后,发送EOI(End of Interrupt)命令。此外,如果IRQ来自于从8259A,还必须先向从 8259A发送EOI命令,再向主8259A发送EOI命令。如果IRQ来自于主 8259A,则仅仅向主8259A发送EOI命令就可以了。当一个中断服务结束后,CPU可利用中断结束命令EOI通知8259A,以便复位ISR中的相应位。因此当调用handle_IRQ_event()时,即使开中断,该条中断线的中断也是关闭的。一直到调用desc->handler->end(irq);时才清除中断屏蔽。     细心的读者可能还有一个问题,为什么在handle_IRQ_event()返回时,还要关闭本地所有的中断(即代码中的local_irq_disable();)。因为在中断返回时,将会进行退栈清理性的工作,如果此时响应中断,鬼知道后果是什么?嘿嘿, Linus Torvalds肯定知道结果。因为他比鬼还厉害 :)     虽然,中断关闭了,但异常并没有关闭。关中断只是关掉了外部中断,cli只是设置EFLAGS寄存器的IF位,如果该位被清除,则表示CPU会禁止外部中断传递信号给INTR引脚,但对于CPU内部异常和不可屏蔽中断(NMI)并不起作用。由于timer是外部中断,所以在禁中断后,timer中断将不在起作用,一直到开中断。 1.    如果在desc->handler->ack(irq);和desc->handler->end(irq);之间,该条中断线上再次发生中断,该中断是否会被丢失? 2.    在中断处理快结束时,会执行软中断。可在执行do_softirq()时,又会执行 if (in_interrupt())         return;     难道软中断不在中断中吗?岂不是执行到这里都返回了? 如果你又知道这些问题的答案,那我只能自认倒霉了,难道你就是传说中的hacker!!!:). 对于第一个问题,我也不能给出明确的答案。我只是把所收集的资料写出来。至于对不对,有大家自己去判断。 中断不会丢失,会被挂起(pending),保存在IPR(Interrupt Pending Register)中,等到中断允许位打开后再执行相应的中断处理程序。(但我查了8259A的寄存器,好像没有这个寄存器。)     在《源代码情景分析》一书中p216第10行提到:“这样,就把本来可能发生在同一通道(甚至可能来自同一中断源)的中断嵌套化解为一个循环”。本人认为这种嵌套仅仅针对的是SMP的情况。因为对单CPU来说,ack操作已经将本条中断线给屏蔽了,根本不可能再响应了。     如果哪位这里有比较好的权威性的答案,请记得发封邮件给我,先谢过了。     在do_IRQ()中,将会调用irq_enter()()和irq_exit()两个函数。 fastcall unsigned int do_IRQ(struct pt_regs *regs) {        ……     irq_enter();     ……     __do_IRQ(irq, regs);     ……     irq_exit();     …… }     在执行irq_enter()时,将会调用(preempt_count() += HARDIRQ_OFFSET);在执行irq_exit()函数时,又会: void irq_exit(void) {     preempt_count()-= IRQ_EXIT_OFFSET;// # define IRQ_EXIT_OFFSET  HARDIRQ_OFFSET     if (!in_interrupt() && local_softirq_pending())         do_softirq();     preempt_enable_no_resched(); }     而in_interrupt()函数只是对preempt_count的某些位进行测试。所以在执行do_softirq()时,preempt_count经过一次加减运算后,将值还原了。所以只要不是中断嵌套的话,in_interrupt()会为假。所以执行到这里时,根本不会返回,除非中断嵌套。     我们也该休息一下了,广告之后马上回来,进入下一个环节--有关调度的问题。     我曾经在一个培训资料上看到如下的结论: 实时应用中,中断的发生不但要求迅速的中断服务,还要求迅速的调度有关的进程进入运行,在用户空间中对事件处理。 可是,如果这样的中断发生在内核时,本次中断返回是不会引起调度的,而要到最初使CPU从用户空间进入内核的那次系统调用或中断(或异常)返回时才会发生调度。倘若内核中的这段代码恰好需要较长时间来完成的话,或者连续又发生几次中断的话,就可能将调度过分的推迟。     当中断发生在内核时,本次中断返回时是有可能引起调度的,原因是,只要need_resched被置位并且preempt_count=0,如果这这两个条件都满足,那么就会发生schedule()操作, 我认为写这段话的作者,当时可能研究的是2.4的内核,因为那时的内核是不可抢占的。当返回到用户态时,只需要对need_resched进行检查。     另外一个问题:     当idle运行时,发生外部中断A,中断处理程序A将一个进程P1唤醒,并设置了调度标志need_resched,在中断处理程序A还没有结束前,又有一高优先级的外部中断B发生,响应B,当中断处理程序B结束后,内核进行调度,选择中断A所唤醒的进程P1运行,此后,产生许多可运行的进程,致使idle不能很快再运行。这不就存在着很大的问题,甚者会导致该条线上的中断永远都不能响应。     的确不错,当调用wake_up_process()时,如果被唤醒进程的优先级比当前进程的优先级高,那么将设置need_resched位。但这个问题忽略了一个很重要的问题,那就是中断B返回时,当返回内核态时(我想不可能返回到用户态吧),将会对need_resched和preempt_count进行检查,如果这两者都满足,才会进行调度。从上面分析可知,当发生中断嵌套时,preempt_count此时等于一个很大的值,虽然B在执行时,preempt_count经过一次加和减的操作,但A还是将该值设置成禁止抢占的,所以中断B返回时,根本不可能发生调度,而是会执行中断上下文A。 说明:有些结论来自论坛上网友的回答,向他们表示感谢。 友情鸣谢: [1] www.linuxforum.net [2] Daniel P.Bovel & Marco Cesati 《深入理解Linux内核》 中国电力出版社,2004 [3] 毛德操,胡希明  《Linux内核源代码情景分析》 浙江大学出版社,2001 [4] Robert Love 《Linux内核设计与实现》 机械工业出版社 ,2004

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 儿童创造力教育与编程教育的碰撞——MIT雷斯尼克教授最新理论梗概

    儿童编程教育已经在我国各一线二线城市疯狂出现,颇有“烂大街”的趋势。我们不禁要问很多很多问题:

    一石匠人
  • 《动物魔法学校》儿童学编程Scratch之“外观”部分

    导读:本文通过一个案例《动物魔法学校》来学习Scratch语言的“外观”部分。之后通过一系列其他功能的综合运用对作品功能进行了扩展。

    一石匠人
  • 什么样的人生才是有意义的人生——没有标准的标准答案

    【导读】其实我们可以跳出这个小圈圈去更加科客观地看一下这个世界。在夜晚的时候我们仰望天空,浩瀚的宇宙中整个地球只是一粒浮尘,何况地球上一个小小的人类?在漫长的历...

    一石匠人
  • 复杂业务下向Mysql导入30万条数据代码优化的踩坑记录

    从毕业到现在第一次接触到超过30万条数据导入MySQL的场景(有点low),就是在顺丰公司接入我司EMM产品时需要将AD中的员工数据导入MySQL中,因此楼主负...

    haifeiWu
  • 天干地支五行八卦的对应关系

    一石匠人
  • SQL中GROUP BY用法示例

    GROUP BY我们可以先从字面上来理解,GROUP表示分组,BY后面写字段名,就表示根据哪个字段进行分组,如果有用Excel比较多的话,GROUP BY比较类...

    Awesome_Tang
  • 【系统设置】CentOS 修改机器名

    ken.io
  • 声音功能让儿童编程更有创造性

    导读:Scratch中声音功能非常强大,除了常规的音效,你甚至可以模拟各种乐器的各个发音、设置节拍、休止……如果你愿意,甚至可以用它创作一个交响乐。我们可以引导...

    一石匠人
  • 我不是算命先生,却对占卜有了疑惑——如何论证“占卜前提”的正确与否

    事出有因,我对《周易》感兴趣了很多年。只是觉得特别有趣,断断续续学习了一些皮毛。这几天又偶然接触到了《梅花易数》,觉得很是精彩,将五行八卦天干地支都串联了起来。...

    一石匠人
  • 一张图理清《梅花易数》梗概

    学《易经》的目的不一定是为了卜卦,但是了解卜卦绝对能够让你更好地了解易学。今天用一张思维导图对《梅花易数》的主要内容进行概括,希望能够给学友们提供帮助。

    一石匠人

扫码关注云+社区

领取腾讯云代金券