Linux内核设计与实现 阅读笔记:7、中断和中断处理
<p class="artical_littlestyle1">1、中断概述</p><p style="text-indent: 2em;">如何让处理器和外部设备协同工作,并且不会降低机器的整体性能呢?这里有两种方法:<br/></p><p style="text-indent: 2em;">轮询是其中之一的解决办法,但是这种方法可能会让内核做不少的无用功。<br/></p><p style="text-indent: 2em;">中断是另一种解决办法,即硬件在需要的时候向内核发出信号,此时内核会暂停当前正在处理的任务,去处理硬件发出的请求,待处理完成之后,接着执行之前的任务。<br/></p><p style="text-indent: 2em;">中断分为同步中断(异常)和异步中断(一般由硬件产生)。<br/></p><p style="text-indent: 2em;">硬件设备生成中断的时候并不考虑和处理器的时钟同步—换句话说就是中断随时可以产生。因此内核随时可能因为新到来的中断而被打断。不同的设备对应的中断不同,而每个中断都通过唯一的数字标志。这些中断值通常被称为中断请求(IRQ)线。每个IRQ线都会关联一个数值量。例如,在经典的PC机上,IRQ0是时钟中断,而IRQ1是键盘中断。</p><p class="artical_littlestyle2">2、中断处理程序</p><p style="text-indent: 2em;">在响应一个特定中断的时候,内核会执行一个函数,这个函数就叫做中断处理程序(interrupt handler)或者中断服务例程(interrupt service routine,ISR)。<br/></p><p style="text-indent: 2em;">一个设备的中断处理程序是它设备驱动程序的一部分—设备驱动程序是用于对设备进行管理的内核代码。<br/></p><p style="text-indent: 2em;">中断随时可能发生,因此中断处理程序也就随时可能执行。所以中断处理程序必须尽可能快的被执行完,同时中断打断了系统其它任务的执行,如果中断处理程序运行的时间过长,那么系统的整体性能也就会下降。<br/></p><p style="text-indent: 2em;">下面来看一个例子,假设某个中断需要处理的工作量比较大,比如网卡的中断处理程序,它在接收到中断之后,除了对硬件做出应答,还需要把硬件的网络数据包拷贝到内存,然后对其进行处理再交给合适的协议栈或应用程序。显然,这种工作量不会太小。<br/></p><p style="text-indent: 2em;">为了解决中断处理程序既想运行得快,又想完成的工作量多的需求,我们一般中断处理分成两个部分或两半。中断处理程序是上半部(top half)—接收到一个中断,它就立即开始执行,但只做有严格时限的工作,例如对接收的中断进行应答或者复位硬件,这些工作都是在所有中断被禁止的情况下完成的。能够被允许稍后完成的工作会被推迟到下半部(bottom half)去,此后,在合适的时机,下半部会被开中断执行。这里只关心中断的上半部也就是中断的处理程序,而对于中断的下半部将在第8章中进行说明。<br/></p><p class="artical_littlestyle3">3、中断处理相关函数</p><p style="text-indent: 0em;"><span style="background-color: rgb(118, 146, 60);">1)、</span>注册中断处理程序<br/></p><p style="text-indent: 2em;">中断处理程序是管理硬件的驱动程序的一部分。驱动程序可以通过<span style="color: rgb(0, 112, 192);">request_irq()</span>函数注册一个中断处理程序(它被声明在文件<linux/interrupt.h>),并且激活给定的中断线,以处理中断,函数的定义如下:<br/><span style="color: rgb(0, 112, 192);">/*<br/> * irq – 表示要分配的中断号<br/> * handler – 实际的中断处理程序<br/> * flags – 标志位,表示此中断的具有特性<br/> * name – 中断设备名称的ASCII 表示,这些会被/proc/irq和/proc/interrupts文件使用<br/> * dev – 用于共享中断线,多个中断程序共享一个中断线时(共用一个中断号),依靠dev来区别各个中断程序<br/> * 返回值:<br/> * 执行成功:0<br/> * 执行失败:非0<br/>*/<br/>int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char* name, void *dev)</span><br/>其中flags常用的标志:<br/></p><p style="text-indent: 2em;"><span style="color: rgb(0, 112, 192);">IRQF_DISABLED</span>:该标志被设置后,意味着内核在处理中断处理程序本身期间,要禁止所有的其它中断。如果不设置,中断处理程序可以与除本身外的其它任何中断同时运行。<br/></p><p style="text-indent: 2em;"><span style="color: rgb(0, 112, 192);">IRQF_TIMER</span>:该标志是特别为系统定时器的中断处理而准备的。<br/></p><p style="text-indent: 2em;"><span style="color: rgb(0, 112, 192);">IRQF_SHARED</span>:此标志表明可以在多个中断处理程序之间共享中断线。在同一个给定线上注册的每个处理程序必须指定这个标志;否则,在每条线上只能有一个处理程序。<br/></p><p style="text-indent: 2em;"><span style="color: rgb(255, 0, 0);">注意:</span><span style="color: rgb(0, 112, 192);">request_irq()</span>函数可能会睡眠,因此,不能在中断上下文或者其他不允许阻塞的代码中调用该函数。还有一点很重要,初始化硬件和注册中断处理程序的顺序必须正确,以防止中断处理程序在设备初始化完成之前就开始执行。</p><p style="text-indent: 0em;"><span style="background-color: rgb(118, 146, 60);">2)、</span>释放中断处理程序<br/></p><p style="text-indent: 2em;">卸载驱动程序时,需要注销相应的中断处理程序,并释放中断线,此时需要调用函数:<br/><span style="color: rgb(0, 112, 192);">void free_irq(unsigned int irq, void *dev)</span><br/></p><p style="text-indent: 2em;">如果指定的中断线不是共享的,那么,该函数删除处理程序的同时将禁用这条中断线。如果中断线是共享的,那么仅删除dev所对应的处理程序,而这条中断线本身只有在删除了最后一个处理程序时才会被禁用。<span style="color: rgb(255, 0, 0);">必须在进程上下文中调用free_irq()。</span></p><p style="text-indent: 0em;"><span style="background-color: rgb(118, 146, 60);">3)、</span>中断处理程序的声明<br/><span style="color: rgb(0, 112, 192);">/*<br/> * 中断处理程序的声明<br/> * @irq – 中断处理程序(即request_irq()中handler)关联的中断号<br/> * @dev – 与 request_irq()中的dev一样,表示一个设备的结构体<br/> * 返回值:<br/> * irqreturn_t – 执行成功:IRQ_HANDLED 执行失败:IRQ_NONE<br/>*/<br/>static irqreturn_t intr_handler(int, irq, void *dev)</span></p><p class="artical_littlestyle4">4、中断处理机制</p><p style="text-indent: 2em;">中断处理的过程主要涉及3个函数:<br/></p><p style="text-indent: 2em;"><span style="color: rgb(0, 112, 192);">do_IRQ()</span> — 与体系结构有关,对所接收的中断进行应答<br/></p><p style="text-indent: 2em;"><span style="color: rgb(0, 112, 192);">handle_IRQ_event()</span> — 调用中断线上所有中断处理<br/></p><p style="text-indent: 2em;"><span style="color: rgb(0, 112, 192);">ret_from_intr()</span> — 恢复寄存器,将内核恢复到中断前的状态<br/></p><p style="text-indent: 2em;">中断从硬件到内核的路由如下图:</p><p style="text-align:center"><img src="/uploads/AilsonJack/2018.08.26/1535215350205410.png" onclick="preview_image('/uploads/AilsonJack/2018.08.26/1535215350205410.png')"/></p><p class="artical_littlestyle1">5、中断控制</p><p style="text-indent: 2em;">Linux内核提供了一组接口用于操作机器上的中断状态。这些接口为我们提供了能够禁止当前处理器的中断系统,或屏蔽掉整个机器的一条中断线的能力,这些例程都是与体系结构相关的,可以在<asm/system.h>和<asm/irq.h>文件中找到。<br/></p><p style="text-indent: 2em;">常见的控制方法见下表:<br/></p><table><tbody><tr class="firstRow"><td style="word-break: break-all;" width="534" valign="top">函数<br/></td><td style="word-break: break-all;" width="534" valign="top">说明<br/></td></tr><tr><td style="word-break: break-all;" width="534" valign="top">local_irq_disable()<br/></td><td style="word-break: break-all;" width="534" valign="top">禁止本地中断传递<br/></td></tr><tr><td style="word-break: break-all;" width="534" valign="top">local_irq_enable()</td><td style="word-break: break-all;" width="534" valign="top">激活本地中断传递<br/></td></tr><tr><td style="word-break: break-all;" width="534" valign="top">local_irq_save()</td><td style="word-break: break-all;" width="534" valign="top">保存本地中断传递的当前状态,然后禁止本地中断传递</td></tr><tr><td style="word-break: break-all;" width="534" valign="top">local_irq_restore()</td><td style="word-break: break-all;" width="534" valign="top">恢复本地中断传递到给定的状态</td></tr><tr><td style="word-break: break-all;" width="534" valign="top">disable_irq()<br/></td><td style="word-break: break-all;" width="534" valign="top">禁止给定中断线,并确保该函数返回之前在该中断线上没有处理程序在运行<br/></td></tr><tr><td style="word-break: break-all;" width="534" valign="top">disable_irq_nosync()<br/></td><td style="word-break: break-all;" width="534" valign="top">禁止给定中断线<br/></td></tr><tr><td style="word-break: break-all;" width="534" valign="top">enable_irq()</td><td style="word-break: break-all;" width="534" valign="top">激活给定中断线</td></tr><tr><td style="word-break: break-all;" width="534" valign="top">irqs_disabled()<br/></td><td style="word-break: break-all;" width="534" valign="top">如果本地中断传递被禁止,则返回非0;否则返回0<br/></td></tr><tr><td style="word-break: break-all;" width="534" valign="top">in_interrupt()<br/></td><td style="word-break: break-all;" width="534" valign="top">如果在中断上下文中,则返回非0;如果在进程上下文中,则返回0<br/></td></tr><tr><td colspan="1" rowspan="1" style="word-break: break-all;" valign="top">in_irq()<br/></td><td colspan="1" rowspan="1" style="word-break: break-all;" valign="top">如果当前正在执行中断处理程序,则返回非0;否则返回0<br/></td></tr></tbody></table><p class="artical_littlestyle2">6、总结</p><p style="text-indent: 2em;">中断处理程序对处理时间的要求很高,如果一个中断要花费较长时间,那么中断处理一般分为2部分:<br/></p><p style="text-indent: 2em;">上半部只做一些必要的工作后,立即通知硬件继续自己的工作。<br/></p><p style="text-indent: 2em;">中断处理中耗时的部分,也就是下半部的工作,CPU会在适当的时候去完成。</p>
你可能也喜欢:
暂无评论,要不要来个沙发
发表评论
JLink V9掉固件修复(灯不亮) 3Zephyr笔记2 - 在STM32F429上运行HelloWorld 2计算NandFlash要传入的行地址和列地址 1Linux MMC子系统 - 6.eMMC 5.1工作模式-设备识别模式 0Linux MMC子系统 - 5.eMMC 5.1工作模式-引导模式 0Linux MMC子系统 - 4.eMMC 5.1常用命令说明(2) 0
标签云
Linux嵌入式实用技巧ARM内核学习问题集合CC++编程语言阅读笔记汇编Linux内核完全注释Windows驱动开发计算机基础ARM11ARMv7-ASTM32IDESublimeLinux内核学习eMMCMMC子系统Ubuntu操作系统OfficeVMWareAPUEgccRTOS中断漫游世界随笔感悟开发工具软件应用编程VsCodearmccarmclang编译器ZephyrSPIJLink网卡驱动安装各种芯片库函数NFSμCOS内核sambaFlashUnix命令与脚本输入法Linux内核设计与实现gitRIFFWAVJATGFTPar8161安装centos有线上网μCGUI字库工程建立右键菜单网络文件系统Firefox百度NTFS文件系统CodeBlocksCentOS数据结构算法PhotoShop51KeilQTUltraEditscanfglibc宏定义UIDGID优先级娱乐天地SourceInsight磁盘扇区总线I2CPDFBComparePythonI2SFPUMakefileSWDCPUARP软件推荐FileZilla