author: lunar
date: Wed 21 Oct 2020 04:41:06 PM CST
中断和中断处理
任何操作系统的核心任务,都包含了对于连接到计算机上的硬件设备进行管理。所谓管理,即与这些设备进行通信。然而,CPU的处理速度与这些硬件的速度不在一个数量级上。这就导致CPU在进行一次通信后,往往要等待多个时钟周期后才会接到下一次通信。
如果让CPU对这些设备进行轮询,将会严重拉低CPU的执行效率,因为大部分轮询都是做的无用功。
更好的办法是提供一种机制,让硬件设备在需要的时候向内核发出信号表示有数据需要处理。这就是中断机制。
中断
中断并不是软件层面的实现,而是在硬件层面都得到了支持。例如,在敲击键盘时,控制键盘的硬件设备就会发生一个中断,中断本质上是一种特殊的电信号,由硬件设备发送给处理器。处理器接收到中断信号后,就会马上报告给操作系统,然后由操作系统处理这些数据。
因此,操作系统随时可能因为到来的中断而被打断。
不同的设备对应的中断不同,而每个中断都有一个唯一的数字标志,这样操作系统才能区分不同硬件设备的中断。
中断处理程序
在响应一个特定中断的时候,内核会执行一个函数,称为中断处理程序。中断处理程序工作在中断上下文中。中断随时可能发生,因此中断处理程序随时可能执行。
上半部与下半部的对比
我们一般将中断处理分为两个部分,中断处理程序是上半部——接收到一个中断就立刻执行,但是只完成一些有严格时限的工作,例如对中断进行应答等。
以网卡为例。当网卡接收到来自网络的数据包时,需要通知内核数据包到了。因此,网卡发出中断,内核通过中断处理程序进行应答。
中断开始执行,通知硬件将网卡的数据拷贝到内存中。这些都是非常紧急的工作,如果不能及时执行,很容易造成网卡的缓存溢出。但是拷贝到内存之后并不代表已经完成,可能用户希望数据下载到磁盘,所以还要从内存写入到磁盘。但是这个并不是很紧急的事情,所以放到中断的下半部完成。
注册中断处理程序
驱动程序可以通过 request_irq()
函数注册一个中断处理程序:
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char* name, void* dev);
irq表示要分配的中断号,handler是一个指向处理这个中断处理程序的指针。
中断处理程序标志
在绑定中断处理程序时可以绑定以下标志:
-
IRQF_DISABLED
该标志被设置后,内核在处理中断处理程序本身期间,要禁止其它所有的中断;
-
IRQF_SAMPLE_RANDOM
表明这个设备产生的中断会对内核熵池(entropy pool)有贡献,内核熵池负责提供由各种随机事件导出的真正的随机数。
-
IRQF_TIMER
特别为系统定时器的中断处理而准备的
-
IRQF_SHARED
表明可以在多个中断处理程序之间共享中断线。在同一个给的你个线上注册的每个处理程序必须制定这个标志。
释放中断处理程序
卸载中断处理程序时,需要注销相应的中断处理程序,并释放中断线。需调用:
void free_irq(unsigned int irq, void* dev);
如果相应的中断线是共享的,那么仅仅注销中断处理程序。否则相应中断线也会被禁用。
中断上下文
中断上下文与进程上下文并不相同。进程上下文是内核的一种操作模式,此时内核代替进程执行,可以通过current宏关联到当前进程。进程上下文可以睡眠,也可以调用调度程序。
而中断上下文,没有后备进程,也不能进行睡眠,否则没法进行重新调度。所以在中断处理程序中不能出现睡眠函数。
中断处理程序栈的设置是一个配置选项。早期,中断处理程序并不具有自己的栈,它们必须共享所中断进程的内核栈。
中断处理机制的实现
图中的硬件设备发生一个中断,通过总线将电信号发送给中断控制器,如果中断线已被激活,这个工作实际上就是通过电信号给处理器的一个针脚发送一个信号。而处理器会立即停止正在做的事,跳到内存中预定义的位置开始执行代码,这个位置就是中断处理程序的入口点。