技术交流

好好学习,天天向上。

0%

第十章——中断处理

中断注册

中断,传统的中断信号线式的中断需要注册才可以用。信号线是各设备分时复用或者共享的,具体哪个设备使用哪个信号线是硬件连线决定的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 注册中断信号线,返回非0值表示成功
int request_irq(unsigned int irq, irqreturn_t (*handler)(int, void *, struct pt_regs *),
unsigned long flags, const char *dev_name, void *dev_id); // TODO 主动还是被动
// irq,中断号
// handler,中断处理函数
// flag,见下文
// flags,中断管理掩码
// dev_name,设备名称,中断的拥有者
// dev_id,自定义数据,驱动可以用dev_id区分设备,当flag含有SA_SHIRQ时该字段必须填充值

// 释放中断号
void free_irq(unsigned int irq, void *dev_id);

// x86架构还提供下面这接口,用于查询某个中断是否可以被注册。
// 但是注意,查询返回和真正注册不是原子的,换句话说,查询哪怕返回OK,但是到注册的时候可能情况已经变成不OK了
int can_request_irq(unsigned int irq, unsigned long flags);

推荐在打开设备的时候注册中断,而不是模块初始化的时候,这样可以提高中断信号线的复用效率。同时,还应在设备关闭时反注册中断。

flag可以有多个不同的取值,列表如下

含义
SA_INTERRUPT 表明这个是一个“快速”中断处理例程,快速中断处理例程会被置于中断禁用状态下运行,一般情况下不要用
SA_SHIRQ 表示中断可以在设备之间共享
SA_SAMPLE_RANDOM 设置该位表示要不要将该设备的中断作为/dev/random等的输入

/proc

/proc/interrupts文件表征了当前OS的中断注册情况。第一列是中断号,有些不连续的情况,说明该中断号没有注册中断处理例程或者说没有目标设备。以CPUx为标题的列,表示某个CPU上中断处理次数情况。后面几列是处理中断的PIC信息和设备信息,共享中断号的设备会在此并列。

还有个/proc/stat文件,该文件intr行统计各号中断触发的次数。第一列是总中断触发次数,后面各列是不同中断号触发中断的次数。

中断探测

中断线形式触发的中断,必须告知驱动设备的中断号。这个事情可以让驱动用户来做,也可以让驱动编写者来做。但事实上他们都可能不知道具体某个设备的中断号。LDD3提到一种探测技术,其思路就是将现阶段所有未被使用的中断号都打开,然后静候设备触发中断,最后看设备用的是几号中断。内核提供了API来完成这件事(但是在最新的内核中没有找到),其实也可以手动实现该功能(将可能的中断号,注册上自定义的中断处理例程)。但是注意,这种探测对于那种共享中断信号线的设备时无效的。

1
2
3
4
5
// 返回当前未被使用的中断的掩码
unsigned long probe_irq_on(void);

// 将probe_irq_on的返回值作为入参,返回从调用probe_irq_on时到现在触发过得中断号。
int probe_irq_off(unsigned long);

处理例程

中断处理例程有三个参数,irq、dev_id以及regs

1
2
3
4
5
6
7
8
9
irqreturn_t (*handler)(int irq, void * dev_id, struct pt_regs *regs)
// 入参
// irq, 中断号
// dev_id,驱动定义的数据,也就是request_irq时的dev_id参数
// regs,保存CPU进入中断上下文之前的寄存器上下文
//
// 返回值
// irqreturn_t,内核会读取返回值以统计中断处理情况,染回IRQ_HANDLED、IRQ_NONE或者IRQ_WAKE_THREAD

启用和禁用中断

内核提供禁用单个或者全部中断的接口,使用这些接口时应有以下注意事项。

  • 共享的中断线是不能禁用的
  • 部分接口禁用和开启可以嵌套,禁用接口调用了多少次,对应的开启接口也需要调用相同次数才可以复原状态。
1
2
3
4
5
6
7
8
9
10
11
void disable_irq(int irq); // 禁用某个中断同时,等待该中断处理例程结束,如果调用者持有中断例程需要的资源,系统会发生死锁
void disable_irq_nosync(int irq); // 禁用某个中断,会立即返回,可能让系统进入竞争状态
void enable_irq(int irq); // 开启某个中断

// 保存中断状态的处理器本地全部中断禁用和开启接口
void local_irq_save(unsigned long flags);
void local_irq_restore(unsigned long flags);

// 不保存中断状态的处理器本地全部中断禁用和开启接口,这套接口不维护嵌套状态
void local_irq_disable(void);
void local_irq_enable(void);

中断顶半部和底半部(Top and Bottom Halves)

中断处理例程需要做的事情可以分为两类,快速的控制面处理和耗时的数据面处理。内核将这两类事务分成两个部分处理,分别叫顶半部和底半部。一般而言,顶半部主要负责填装调度任务然后启用底半部,底半部可以运行在workqueue、tasklet等机制中。tasklet和workqueu的工作机制和相关接口,阅读第七章笔记。