中断
中断让硬件在需要的时候向内核发出信号,实现变内核主动为硬件主动。
经典的PC机上IRQ0为时钟中断,IRQ1为键盘中断。对于连接在PCI总线上的设备,中断是动态分配的;其他非PC的体系结构也有动态分配可用中断的特性。
异常
异常的产生必须考虑与处理器时钟同步,异常也常常成为同步中断。发生在
中断处理程序
一个设备的中断处理程序是该设备驱动的一部分
中断程序中有可能需要处理一些数据,如网络设备中的中断处理程序出了要对硬件应答还需要将数据包拷贝到内存。为了协调中断程序运行速度和完成的工作量,将中断处理切分为两部分
上半部
接收到一个中断后立即执行,但只做有严格时限的共走,例如对中断应答和复位硬件,此时中断被禁止。
下半部
允许稍后完成的工作,选择合适的时机执行。
上半部和下半部的协调
网卡接收到数据包立即通知内核数据包到了,网卡立即触发中断,内核通过执行已注册的中断处理程序作出应答:从而优化吞吐量和传输周期避免超时。
中断开始执行后,通知硬件,拷贝最新的网络数据包到内存,然后读取网卡更多的数据包。
内核需要快速的拷贝网络数据包到内存,因为网卡上接收网络数据包的缓存大小固定,而且相比系统内存也小得多。
上述拷贝动作一旦被延迟,必然造成内存溢出。
当网络数据包被拷贝到系统内存,中断的任务完成,此时将控制权交还给系统被中断前运行的程序。处理和操作数据包的其他工作在随后的下半部中执行。
中断的具体实现
注册中断处理程序
驱动程序通过request_irq()函数注册一个中断处理程序,并且激活给定的中断线
int request_irq(unsigned int irq,irq_handler_t handler,unsigned long flags,const char * name,void* dev);
第一个参数 irq 要分配的中断号,对于传统PC设备上的系统时钟或键盘,该值预先确定。对于大多数其他设备,该值通过探测获取或通过编程动态确定
第二个参数handler 是一个指针,指向处理这个中断的实际中断处理程序。只要操作系统接收到中断,该函数就被调用
handler函数原型:
typedef irqreturn_t (*irq_handler_t)(int,void *);
第三个参数flags可以为0,也可以为下列位掩码。
第四个参数name为中断名的ascii文本表示
第五个参数dev用于共享中断线,当一个中断处理程序需要释放时,dev将提供唯一的标志信息(cookie),以便从共享中断线的诸多中断处理程序中删除指定的某一个。
返回值:成功执行则返回0,如果返回非0值则代表有错误发生,这种情况下指定的中断处理程序不会被注册。
注意request_irq()函数可能会引起睡眠,所以不能在不允许阻塞的代码中调用该函数,具体原因见12章。
request_irq(irqn,my_interrupt,IRQF_SHARED,"my_device",my_dev);
释放中断程序
void free_irq(unsigned int irq,void *dev)
中断服务程序
static irqreturn_t intr_handler(int irq,void *dev)
中断服务程序扮演什么样的角色取决于产生中断的设备和该设备为什么中断
中断处理程序重入
linux中的中断处理程序无需重入,当一个给定的中断处理程序执行时,相应中断线在所有处理器上都会被屏蔽掉,以防止在同一个中断线接收另一个新的中断。极大的简化了中断处理程序的编写
共享的中断处理程序
中断处理程序共享与否在注册和运行上较为相似,差异:
基于RTC的中断实例
注册中断处理程序
中断处理程序的具体内容
中断上下文
因为没有后备进程,所以中断上下文不可以休眠,否则无法重新调度。什么样的函数可以在终端上下文中调用需要有所限制!
中断上下文打断了其他代码,有较为严格的时间限制,代码应该迅速,简介,尽量不要用循环去处理繁重的工作。所有的工作尽量从中断处理程序中分离出来放到下半部来执行,因为下半部程序可以在更合适的时间执行。
中断处理程序的栈
曾经的中断处理程序没有自己的栈而是共享所中断进程的内核栈,内核栈的大小为两页,因为是共享别人的栈所以使用时需要特别节约。
2.6早期的内核中增加了一个选项把栈大小从两页减到一页,减轻了内存的压力,为了应对栈的减小,中断处理程序有了自己的栈,每个处理器一个,大小为一页,称为中断栈。
中断处理机制的实现
总线把电信号发给中断控制器,如果中断线是激活状态,中断控制器会把中断发给处理器,处理器通过中断内核来处理。
对于每条中断线,处理器都会跳到一个唯一确定的位置,从而内核就可以知道中断的IRQ号。初始入口点在栈中保存这个号并存放当前寄存器的值,然后内核调用函数do_IRQ()
entry.S中,标志了当触发中断时的入口函数
ENTRY(common_interrupt)
interrupt do_IRQ
说明了中断入口点会存储被调用的寄存器,进入中断函数后会关闭系统中断
do_IRQ的声明为:
unsigned int do_IRQ(struct pt_regs regs)
C的调用惯例是要把函数参数放在栈的顶部,所以pt_regs结构包含原始寄存器的值。
do_IRQ会调用handle_IRQ_event()方法来运行为这条中断线所安装的中断处理程序。
中断控制
控制中断系统归根结底是需要提供同步,通过控制中断,可以确保某个中断处理程序不会抢占当前的代码。
无论禁止中断还是禁止内核抢占,都没有提供任何保护机制来防止来自其他处理器的并发访问,linux支持多处理器,因此内核代码一般都需要获取某种锁来防止其他处理器对共享数据的并发访问。获取这些锁的同时也伴随着禁止本地中断。
禁止和激活中断
禁止当前处理器的中断
local_irq_disalbe();
local_irq_enable();
禁止指定的中断线
disable_irq(unsigned int irq);
disable_irq_nosync(unsigned int irq);
enable_irq(unsigned int irq);
等待特定的中断处理程序的退出,如果该处理程序正在执行,那么该函数必须退出后才能返回
snchronize_irq(unsigned int irq);