前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >对中断的一点思考

对中断的一点思考

作者头像
ternturing
发布2018-09-12 17:24:18
1.2K0
发布2018-09-12 17:24:18
举报
文章被收录于专栏:炉边夜话炉边夜话

对中断的一点思考

杨小华(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

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2007年06月12日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档