要处理中断,需要有一个中断处理函数。定义如下:
irqreturn_t (*irq_handler_t)(int irq, void * dev_id);
/**
* enum irqreturn
* @IRQ_NONE interrupt was not from this device or was not handled
* @IRQ_HANDLED interrupt was handled by this device
* @IRQ_WAKE_THREAD handler requests to wake the handler thread
*/
enum irqreturn {
IRQ_NONE = (0 << 0),
IRQ_HANDLED = (1 << 0),
IRQ_WAKE_THREAD = (1 << 1),
};
其中,irq 是一个整数,是中断信号。dev_id 是一个 void * 的通用指针,主要用于区分同一个中断处理函数对于不同设备的处理。
这里的返回值有三种:IRQ_NONE 表示不是我的中断,不归我管;IRQ_HANDLED 表示处理完了的中断;IRQ_WAKE_THREAD 表示有一个进程正在等待这个中断,中断处理完了,应该唤醒它。
很多中断处理程序将整个中断要做的事情分成两部分,称为上半部和下半部,或者成为关键处理部分和延迟处理部分。在中断处理函数中,仅仅处理关键部分,完成了就将中断信号打开,使得新的中断可以进来,需要比较长时间处理的部分,也即延迟部分,往往通过工作队列等方式慢慢处理。
有了中断处理函数,接下来要调用 request_irq 来注册这个中断处理函数。request_irq 有这样几个参数:
- unsigned int irq 是中断信号;
- irq_handler_t handler 是中断处理函数;
- unsigned long flags 是一些标识位;
- const char *name 是设备名称;
- void *dev 这个通用指针应该和中断处理函数的 void *dev 相对应。
对于每一个中断,都有一个对中断的描述结构 struct irq_desc。它有一个重要的成员变量是 struct irqaction,用于表示处理这个中断的动作。每一个中断处理动作的结构 struct irqaction,都有以下成员:
- 中断处理函数 handler;
- void *dev_id 为设备 id;
- irq 为中断信号;
- 如果中断处理函数在单独的线程运行,则有 thread_fn 是线程的执行函数,thread 是线程的 task_struct。
在 request_threaded_irq 函数中,irq_to_desc 根据中断信号查找中断描述结构。如何查找呢?这就要区分情况。一般情况下,所有的 struct irq_desc 都放在一个数组里面,我们直接按下标查找就可以了。如果配置了 CONFIG_SPARSE_IRQ,那中断号是不连续的,就不适合用数组保存了。
真正中断的发生还是要从硬件开始。这里面有四个层次。
- 第一个层次是外部设备给中断控制器发送物理中断信号。
- 第二个层次是中断控制器将物理中断信号转换成为中断向量 interrupt vector,发给各个 CPU。
- 第三个层次是每个 CPU 都会有一个中断向量表,根据 interrupt vector 调用一个 IRQ 处理函数。
- 第四个层次是在 IRQ 处理函数中,将 interrupt vector 转化为抽象中断层的中断信号 irq,调用中断信号 irq 对应的中断描述结构里面的 irq_handler_t。
对于每一个 CPU,都要求有一个 idt_table,里面存放了不同的中断向量的处理函数。中断向量表中已经填好了前 32 位,外加一位 32 位系统调用,其他的都是用于设备中断。
硬件中断的处理函数是 do_IRQ 进行统一处理,在这里会让中断向量,通过 vector_irq 映射为 irq_desc。
irq_desc 是一个用于描述用户注册的中断处理函数的结构,为了能够根据中断向量得到 irq_desc 结构,会把这些结构放在一个基数树里面,方便查找。
irq_desc 里面有一个成员是 irqaction,指向设备驱动程序里面注册的中断处理函数。
此文章为11月Day13学习笔记,内容来源于极客时间《趣谈Linux操作系统》,推荐该课程。