Interrupts
中断是hardware device用来通知CPU的一种机制。在系统上,连接着很多的外设,这些外设速度很慢,并且随时会产生数据需要CPU处理,CPU作为高速运行的部件,不能一直等着外设产生数据,为了提高效率,采用了中断这种机制。当外设需要CPU处理数据时,就向CPU发送电信号,CPU收到电信号以后,就知道哪个设备需要处理了,这个电信号就是中断。
中断并不是直接发送给CPU,而是先发送到interrupt controller,也就是中断控制器(一般是8259A),所有的外设都把中断发送到这里,再由中断控制器把中断信号发送给CPU,CPU收到中断信号,就会停下正在执行的任务,开始执行中断处理函数。
CPU收到的中断,都有对应的中断号,每个外设的中断号都是有区别的,这样CPU根据中断号就能直到是哪些设备产生了中断,进而可以调用对应的中断处理程序。这个中断号一般称为IRQ number。
这里提到了异常,异常和中断非常类似,实际上,异常就是软中断,是CPU在执行指令的过程中发生了异常事件,比如除0错误,或者遇到了page fault,此时都需要中断当前程序的执行,并上报错误。异常和硬件中断的一个很大的区别是,异常是同步产生的,因为执行CPU在执行指令的过程中产生异常,因此是和软件指令同步产生,而硬件中断是异步的,CPU此时可能在执行任何代码。
Interrupt Handlers
用来处理IRQ的处理函数通常称为ISR,也就是interrupt service routine,每个会产生中断的hardware device都有对应的ISR,在kernel中,ISR是device driver的一部分。ISR和普通的kernel code有相同之处,也有不同之处,相同之处在于都是C写的function,不同之处在于ISR只有中断产生时才会执行,并且运行在interrupt context,这是特殊的context,执行时不允许block,即不允许被调度。
因为硬件的中断是异步的,当中断产生时,CPU可能在执行任何代码,所以ISR一定是执行的越快越好,否则被抢占的代码会等待很长时间。因此,ISR中就不能处理很多东西,可以把这些费时的操作放到将来的某个时刻再做,ISR尽快返回。
Top Halves Versus Bottom Halves
接上文,ISR中可能要处理很多事情,但是又不能占用太多CPU的时间,所以OS中一般把ISR分为上下两个部分,即top half和bottom half。top half只处理一些紧急的事情,比如从hardware copy数据,然后调度别的task,ISR就可以结束返回;bottom half里就可以处理剩下的比较费时的操作。
Registering an Interrupt Handler
上面提到过,interrupt handler,也即ISR,是device driver的一部分,应当由device driver负责实现。
driver可以注册interrtupt handler,kernel提供了接口来实现:
static inline int __must_check
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
const char *name, void *dev)
{
return request_threaded_irq(irq, handler, NULL, flags, name, dev);
}
第一个参数,irq,指定要给哪个IRQ line注册handler,对于kernel的timer或者键盘来说,这个irq是固定死的,其他比如PCI设备等,都是动态分配的;第二个参数,handler,是一个函数指针,指向driver自己的interrupt handler函数,当中断发生时,kernel就会调用这个handler;这个handler的类型如下:
typedef irqreturn_t (*irq_handler_t)(int, void *);
第三个参数是flag,要么是0,要么是一些bitmask,这些bitmask定义在<linux/interrupt.h>。下面介绍一下这些flag,注意,下面对flag的描述只适用于kernel 2.6,在kernel4.15中已经发生了明显的变化。
Interrupt Handler Flags
IRQF_DISABLED
如果设置了这个flag,那么这个handler在执行的时候,所有的中断都被disable。一般情况下,在注册handler时都不会设置这个flag,除非handler对performance特别敏感,必须马上执行。在kernel 4.15中,已经没有这个 flag 了。
IRQF_SAMPLE_RANDOM
如果设置这个flag,说明这个device产生的中断是随机的,那么可以作为随机池的熵参与随机值的生成。在kernel 4.15中,已经没有这个flag了。
IRQF_TIMER
如果设置这个flag,说明这个handler是一个处理timer中断的handler。
IRQF_SHARED
如果设置这个flag,说明这个IRQ line是share的。也就说,要处理的IRQ line是share的,可能有多个device共享了这个IRQ line,因此在kernel中,同一个IRQ line可能存在多个handler。
这本书只列举了上面这几个flag。在kernel 4.15中,还有别的几个flag,这里也列举一下,但是不做深入说明:
/*
* These flags used only by the kernel as part of the
* irq handling routines.
*
* IRQF_SHARED - allow sharing the irq among several devices
* IRQF_PROBE_SHARED - set by callers when they expect sharing mismatches to occur
* IRQF_TIMER - Flag to mark this interrupt as timer interrupt
* IRQF_PERCPU - Interrupt is per cpu
* IRQF_NOBALANCING - Flag to exclude this interrupt from irq balancing
* IRQF_IRQPOLL - Interrupt is used for polling (only the interrupt that is
* registered first in an shared interrupt is considered for
* performance reasons)
* IRQF_ONESHOT - Interrupt is not reenabled after the hardirq handler finished.
* Used by threaded interrupts which need to keep the
* irq line disabled until the threaded handler has been run.
* IRQF_NO_SUSPEND - Do not disable this IRQ during suspend. Does not guarantee
*