《Linux内核设计与实现》读书笔记(4)--- 中断和中断处理程序

时间:2022-09-13 16:55:18

中断和中断处理程序

1.中断

    中断本质上是一种特殊的电信号,由硬件设备发向处理器。处理器接收到中断后,会马上向操作系统反映此信号的到来,然后由OS负责处理这些新到来的数据。硬件设备生成中断的时候并不考虑与处理器的时钟同步,内核随时可能因为新到来的中断而被打断。不同的设备对应的中断不同,都通过一个唯一的数字标识,称之为中断请求(IRQ)线。

    在操作系统中,讨论中断就不得不提及异常。异常与中断不同,它在产生时必须考虑与处理器时钟同步。实际上,异常也常常称为同步中断。在处理器执行到由于编程失误而导致的错误指令的时候,或者是在执行期间出现特殊情况,必须靠内核来处理的时候,处理器就会产生一个异常。因为许多处理器体系结构处理异常与处理中断的方式类似,因此,内核对它们的处理也很类似。

 

2.中断处理程序

    在响应一个特定中断的时候,内核会执行一个函数,该函数叫做中断处理程序(interrupt handler)或中断服务例程(interrupt service routine,ISR)。中断处理程序通常不是和特定设备关联,而是和特定中断关联,也就是说,如果一个设备可以产生多种不同的中断,那么该设备就可以对应多个中断处理程序,相应的,该设备的驱动程序也就需要准备多个这样的函数。

    中断处理程序与其他内核函数的真正区别在于:中断处理程序是被内核调用来响应中断的,而它们运行于称之为中断上下文的特殊上下文中。

    中断处理程序一方面需要迅速和尽可能短的时间内完成中断处理,另一方面,处理程序内的工作量也不小。所以把中断处理切为两个部分。中断处理程序是上半部(top half)——接收到一个中断,它就立即开始执行,但只做有严格时限的工作,例如对接收的中断进行应答或复位硬件,这些工作都是在所有中断被禁止的情况下完成的,能够被允许稍后完成的工作推迟到下半部(bottom half)。

 

3.共享的中断处理程序

    共享与非共享的处理程序差异有以下三处:

    1)request_irq()的参数flags必须设置SA_SHIRQ标志,但只有在中断栈当前未被注册,或者在该栈上的所有已注册处理程序都指定了SA_SHIRQ的情况下才会成功。

    2)对每个注册的中断处理程序来说,dev_id参数必须唯一。

    3)需要硬件的支持。

    内核接收一个中断后,会依次调用在该中断线上注册的每一个处理程序。因此,一个处理程序必须知道它是否应该为这个中断负责。如果与它相关的设备并没有产生中断,那么处理程序应该立即退出。

 

4.中断上下文

    当执行一个中断处理程序或下半部时,内核处于中断上下文(interrupt context)中,中断上下文具有较为严格的时间限制,因为它打断了其他代码(甚至打断了其他中断线上的另一个中断处理程序),它应迅速简洁,尽量不要使用循环去处理繁重的工作。另一方面,中断处理程序拥有自己的栈,每个处理器一个,大小为一页(32位为4KB,64位为8KB),称之为中断栈。中断处理程序的编写要注意这两个方面。

 

5.中断控制

    Linux内核提供了一组接口用于操作机器上的中断状态。这些接口为我们提供了能够禁止当前处理顺的中断系统,或屏蔽掉整个机器的一条中断线的能力。一般来说,控制中断系统的原因归根结底是需要提供同步。通过禁止中断,可以确保某个中断处理程序不会抢占当前的代码。然而,不管是禁止中断还是禁止内核抢占,都没有提供任何保护机制来防止来自其他处理器的并发访问。Linux支持多处理器,因此,内核代码一般都需要获取某种锁,防止来自其他处理器对共享数据的并发访问。获取这些锁的同时也伴随禁止本地中断。

    用于禁止或激活当前处理器上的本地中断的语句为:

    local_irq_disable() 和 local_irq_enable()

    直接使用这两个语句禁止或激活中断都存在潜在危险,因为它们将无条件地禁止或激活中断,而不管中断在开始时的状态。因此,在禁止中断之前保存中断系统的状态会更加安全一些。相反,在准备激活中断时,只要把中断恢复到它们原来的状态。

    在某些情况下,只禁止整个系统中一条特定的中断线就够了,Linux提供了四个接口:

    void disable_irq(unsigned int irq);

    void disable_irq_nosync(unsigned int irq);

    void enable_irq(unsigned int irq);

    void synchronize_irq(unsigned int irq);

    前两个函数禁止中断控制器上指定的中断线,即禁止给定中断向系统中所有处理器的传递。另外,函数只有在当前正在执行的所有处理程序完成后,disable_irq()才能返回。函数disable_irq_nosync()不会等待当前中断处理程序执行完毕。

    函数synchronize_irq()等待一个特定的中断处理程序的退出。如果该处理程序正在执行,那么该函数必须退出后才能返回。

    这些函数调用可以嵌套,但要记住在一条指定的中断线上,对disable_irq()或disable_irq_nosync()的每次调用,都需要相应地调用一次enable_irq()。只有在对enable_irq()完成最后一次调用后,才真正激活了中断线。

    Linux提供两个宏,用来了解中断系统的状态:

    in_interrupt() 和 in_irq()

    in_interrupt():如果内核处于中断上下文中,它返回非0,说明内核此刻正在执行中断处理程序,或者正在执行下半部处理程序。in_irq()只有在内核确实正在执行中断处理程序时才返回非0。