向下之旅(八):中断和中断处理程序(一)
Linux内核要对连接到计算机上的所有硬件设备进行管理,而想要管理这些硬件设备,首先要与它们互通音信才行。但是处理器的速度要远大于外围硬件设备的速度,一般可以让处理器采用轮询的方式不停的对设备的状态进行查询,更好的办法是让硬件设备在需要的时候再向内核发出信号,让处理器主动变为硬件主动,此为中断机制。
中断
硬件通过中断与处理器通信,例如,当你敲打键盘的时候,键盘控制器(控制键盘的硬件配备)就会给处理器发送一个中断,通知操作系统有键按下。中断本质是一种电信号,由硬件设备发向处理器,处理器接受到中断后,会马上向操作系统反映此信号的到来,然后由OS负责处理这些发送过来的数据。硬件设备生成中断的时候并不考虑与处理器的时钟同步,因此,内核随时可能因为新到来的中断而被打断。
不同设备对应的中断不同,而美国而中断都通过一个唯一的数字标识,从而使得操作系统能对中断进行区分,并作出相应的处理。
这些中断通常被称为中断请求(IRQ)线。通常IRQ都是一些数值量——如,在PC上IRQ0是时钟中断,IRQ1是键盘中断。并非所有的中断号都是这样严格定义的,对于链接在PCI总线上的设备而言,中断是动态分配的。其他非PC的体系结构也具有动态分配可用中断的特性。重点是特定的中断总是与特定的设备相关联,并且内核要知道这些信息。
异常
异常与中断不同,它在产生是必须考虑与处理器时钟同步,即异常常常被称为同步中断。当处理器执行到由于编程导致的错误时,或者执行期间出现特殊情况(缺页),处理器就会产生一个异常。之前熟悉的一种异常:在x86体系结构上如何通过软中断实现系统调用,那就是陷入内核,然后引起一种特殊的异常——系统调用处理程序异常。中断的工作方式与其类似,其差异只在于中断是由硬件而不是软件引起的。
中断处理程序
在处理一个中断的时候,内核会执行一个函数,该函数叫做中断处理程序或中断服务例程。如果一个设备能产生一个或多个中断,那么该设备就对应多个中断处理程序。通常中断处理程序不是和特定的设备关联,而是和特定的中断关联。相应的,该设备也就需要准备多个这样的函数。一个设备的中断处理程序是它设备驱动程序的一部分——设备驱动程序是用于对设备进行管理的内核代码。
中断处理程序与其他内核函数真的的区别在于:中断处理程序是被内核调用来响应中断的,他们运行于中断上下文的特殊上下文中。中断随时可能发生,因此中断处理程序也就能随时可能运行。所以必须保证中断处理程序能够快速执行,这样才能保证尽可能快地回复中断代码的执行。因此,尽管对硬件而言,迅速对其中断进行服务非常重要,但是对系统其他部分而言,让中断处理程序尽可能短的时间内完成也同样重要,但是又希望它来完成大量的其他工作。因此有了上半部和下半部。
上半部与下半部对比
又想程序运行的快,又想程序完成的工作量多,所以我们一般把中断处理切为两个部分或两半。中断处理程序是上半部。——收到一个中断立即执行,但只做有严格时限的工作,能够被允许稍后完成的工作会推迟到下半部去。此后,在合适的时机,下半部会被开始中断执行。
注册中断处理程序
中断处理程序是管理硬件的驱动程序的组成部分,每一个设备都有相关的驱动程序,如果设备使用中断(大部分设备如此),那么相应的驱动程序就注册一个中断处理程序。驱动程序通过以下函数注册并激活一个中断处理程序,以便处理中断:

第一个参数irq表示要分配的中断号。可写死,如PC设备的系统时钟或键盘。也可动态确定。第二个参数是handler是一个指针,指向处理这个中断的实际中断处理程序。只要操作系统一接收到中断,该函数就被调用。第三个参数irqflags可以为0,也可设置某种标志的位掩码(SA_INTERRUPT,SA_SAMPLE_RANDOM,SA_SHIRQ)。第四个参数devname是与中断相关的设备的ASCII文本表示法。例如,PC机上键盘中断对应的这个值是“keyboard”。第五个参数dev_id主要用于共享中断线。如果无需共享中断线,则将该值赋NULL就可以了。当一个中断处理程序需要释放时,dev_id将提供唯一的标志信息,以便从共享中断线的众多中断处理程序中删除指定的那一个。
request_irq成功执行返回0,如果返回非0值,就表示有错误发生。最常见的错误是-EBUSY,它表示给定的中断线已经在使用(或者当前用户或者你没有指定SA_SHIRQ)。该函数可能会睡眠,不能在中断上下文或其他不允许阻塞的代码中调用该函数。
在一个驱动程序中请求一个中断线,并在通过request_irq()安装中断处理程序。

在这个例子中,irqn是请求的中短线,my_interrupt是中断处理程序,中断线可以共享,设备名为”my_device“,而且通过dev_id传递到dev结构体。如果请求失败,那么这段代码将打印出一个错误并返回。如果调用返回0,则说明处理程序已经安装成功。
释放中断处理程序
卸载驱动程序时,需要注销相应的中断处理程序,并释放中断线,可以通过void free_irq(unsigned int irq, void *dev_id)来释放中断线。
若指定的中断线不是共享的,那么该函数删除处理程序的同时将禁用这条中断线。若是共享的,则仅仅删除dev_id所对应的处理程序,而这条中断线本身只有在删除了最后一个处理程序时才会被禁用。必须从进程上下文中调用free_irq()。
编写中断处理程序
以下是一个典型的中断处理程序声明:
static irqreturn_t intr_handler(int irq, void *dev_id, struct pt_regs *regs)
注意,它的类型与request_irq()参数中的handler所要求的参数类型相匹配。第一个参数irq就是这个处理程序要响应的中断的中断线号,现在已无太大用处。第二个蚕食是dev_id通用指针,它与在中断处理程序注册时传递给request_irq()的参数dev_id必须一致。若该值有唯一确定性(建议采用这样的值,以便支持共享),通常会把设备结构传递给dev_id。最后一个参数regs是一个指向结构的指针,该结构包含处理中断之前处理器的寄存器和状态。现在基本不用,可忽略。
中断处理程序的返回值是一个特殊类型:irqreturn_t。可能返回两个特殊的值:IRQ_NONE和IRQ_HANDLED。当中断处理程序检测到一个中断,但该中断对应的设备并不是在注册处理函数期间指定的产生源时,返回IRQ_NONE,当中断处理程序被正确调用,且确实是它对应的设备产生了中断时,返回IRQ_HANDLED。另外也可以通过宏IRQ_RETVAL(x)。若x为非0值则返回IRQ_HANDLED,否则返回IRQ_NONE。irqreturn_t实际上是int型。
重入和中断处理程序
Linux中的中断处理程序是无需重入的。当一个给定的中断处理程序正在执行时,相应的中断线在所有的处理器上都会被屏蔽掉,以防止在同一中断线上接受另一个新的中断。同一个中断处理程序绝对不会被同时调用以处理嵌套的中断。
共享中断处理程序
共享中断处理程序和非共享中断处理程序的主要差异为:
1.request_irq()的参数flags必须设置SA_SHIRQ
2.对每个注册的中断处理程序来说,dev_id参数必须唯一,指向任一设备结构的指针可以做到这点,所以通常会选择设备结构,而且中断处理程序可能会用到它。
3.中断处理程序必须能够区分它的设备是否真的产生了中断。这既需要硬件的支持,也需要处理程序中有相关的处理逻辑。
内核接收到一个中断后,它会依次调用在该中断线上的所有处理程序,所以处理程序必须知道它是否对这个中断负责,若与它相关的设备没有产生中断,那么处理器程序应该立即退出。这需要硬件设备提供状态寄存器(或类似机制),以便中断处理程序进行检查。大多数硬件都提供这种功能。
参考自:《Linux Kernel Development》.
浙公网安备 33010602011771号