字符设备中断
前面在学习文件读写的时候,如果是读取设备上的内容, 那么在读取完毕的时候,设备会发送一个中断告知CPU内容读取完毕。同样的,字符设备也是有中断来通知CPU处理事件, 比如鼠标设备就是通过中断来告知系统自己的位置和按键信息,传递给驱动程序。
注册中断处理函数
处理中断,需要一个中断的处理函数。
1 |
|
鼠标的中断处理函数就是logibm_interrupt
。通过request_irq
函数注册中断处理函数。
中断处理函数的返回值表示中断处理的结果。
1 |
|
这里使用void指针参数, 可以达到同一个函数处理不同设备的中断事件,根据中断号可以区分是什么设备,然后强转为特定设备的结构,这种实现是很普遍的用在通过流程的入口函数的。
这里的返回值有三种:IRQ_NONE 表示不是我的中断,不归我管;IRQ_HANDLED 表示处理完了的中断;IRQ_WAKE_THREAD 表示有一个进程正在等待这个中断,中断处理完了,应该唤醒它。
鼠标的中断处理相对比较简单,没有耗时长的操作,整体也不是很复杂。在中断处理的过程中,这个中断信号是被关闭的, 如果连续来几个相同的中断,后续的中断可能不被即时响应,显得系统很慢。对于其他复杂的中断,可能需要分成关键处理部分和延迟处理部分,在中断处理函数中,仅仅处理关键部分,完成了就将中断信号打开,使得新的中断可以进来,需要比较长时间处理的部分,也即延迟部分,往往通过工作队列等方式慢慢处理。
request_irq
函数注册中断处理函数。对于每一个中断,都有一个对中断的描述结构 struct irq_desc。它有一个重要的成员变量是 struct irqaction,用于表示处理这个中断的动作。处理动作可以有多个,通过 irqaction的next串起来。
1 |
|
这个desc结构可以通过中断号调用irq_to_desc函数来查找。当然这里的中断号也只是一个内核逻辑的中断编号,不同的设备实际发出的中断号可能不一样,这个有一个中断控制器控制。
request_threaded_irq 函数分配了一个 struct irqaction,并且初始化它,接着调用 __setup_irq。在这个函数里面,如果 struct irq_desc 里面已经有 struct irqaction 了,我们就将新的 struct irqaction 挂在链表的末端。如果设定了以单独的线程运行中断处理函数,setup_irq_thread 就会创建这个内核线程,wake_up_process 会唤醒它。
注册处理函数其实也不复杂,只是查找一下中断描述符,然后建立action链表。
触发中断处理
中断触发有几个阶段
- 外部设备给中断控制器发送物理中断信号
- 中断控制器将物理中断信号转换成为中断向量 interrupt vector,发给各个 CPU。
- 每个 CPU 都会有一个中断向量表,根据 interrupt vector 调用一个 IRQ 处理函数。
- 在 IRQ 处理函数中,将 interrupt vector 转化为抽象中断层的中断信号 irq,调用中断信号 irq 对应的中断描述结构里面的 irq_handler_t。
CPU 能够处理的中断总共 256 个,用宏 NR_VECTOR 或者 FIRST_SYSTEM_VECTOR 表示。
CPU 硬件要求每一个 CPU 都有一个中断向量表,通过 load_idt 加载,里面记录着每一个中断对应的处理方法,这个中断向量表定义在文件 arch/x86/kernel/traps.c 中。
1 | gate_desc idt_table[NR_VECTORS] __page_aligned_bss; |
0-31是系统异常的中断,在内核初始化的时候 trap_init函数设置了中断函数。在trap_init还设置了127系统调用的中断处理函数。同时给used_vectors全局变量对应的位置位。其他设备的中断初始化调用的是init_IRQ函数。
任何硬件中断向量处理最终都会调用到do_IRQ函数。do_IRQ就通过每个CPU对应的中断向量表获取最终全局的中断desc,然后调用对应的处理函数进行处理。

行动,才不会被动!
欢迎关注个人公众号 微信 -> 搜索 -> fishmwei,沟通交流。