Linux中的中断处理

中断处理例程运行方式的不同,它们所能执行的动作将会受到不同的限制。
内核维护了一个中断信号线的注册表,该注册表类似于I/O端口的注册表。
模块在使用中断前要先请求一个中断通道(或者中断请求IRQ),然后在使用后释放该通道。

在很多场合下,模块也希望可以和其他的驱动程序共享中断信号线。


接口实现在头文件<linux/sched.h>中声明:

int request_irq(unsigned int irq, irqreturn_t(*handler)(int, void *, 
struct pt_regs *), unsigned long flags, const char *dev_name, void *dev_id);
void free_irq(unsigned int irq, void *dev_id);

中断处理例程可在驱动程序初始化时或者设备第一次打开时安装。
在设备打开的时候申请中断,则可以共享这些有限的资源。

调用request_irq的正确位置应该是在第一次打开、硬件被告知产生中断之前。
调用free_irq的位置是最后一次关闭设备、硬件被告知不再中断处理器之后。

i386和x86_64体系架构定义了用于查询某个中断线是否可用的函数:
int can_request_irq(unsigned int irq, unsigned long flags);


1. 自动检测IRQ号
(1)Linux内核提供了一个底层设施来探测中断号。
在头文件<linux/interrupt.h>中声明:
unsigned long probe_irq_on(void);
这个函数返回一个未分配中断的位掩码。
int probe_irq_off(unsigned long);
在请求设备产生中断之后,驱动程序调用这个函数,并将前面probe_irq_on返回的位掩码作为参数传递给它。如果没有中断发生,就返回0.如果产生了多次中断,会返回一个负值。

程序员要注意在调用probe_irq_on之后启用设备上的中断,并在调用probe_irq_off之前禁用中断。在probe_irq_off之后,需要处理设备上待处理的中断。

(2)DIY探测
探测也可以由驱动程序自己实现。如果装载时指定probe=2,模块将对IRQ信号线进行DIY探测。


快速和慢速处理例程
快速中断执行时,当前处理器上的其他所有中断都被禁止。

最底层的中断处理代码可见entry.S文件,该文件是一个汇编语言文件,完成了许多机器级的工作。这个文件利用几个汇编技巧及一些宏,将一段代码用于所有可能的中断。
在所有情况下,这段代码将中断编号压入栈,然后跳转到一个公共段,而这个公共段会调用在irq.c中定义的do_IRQ函数。

do_IRQ做的第一件事是应答中断,这样中断控制器就可以继续处理其他的事情了。

然后该函数对于给定的IRQ号获得一个自旋锁,这样就阻止了任何其他的CPU处理这个IRQ。
接着清除几个状态位,
然后寻找这个特定IRQ的处理例程,如果没有处理例程,就什么也不做;自旋锁被释放,处理任何待处理的软件中断,最后do_IRQ返回。

IRQ的探测是通过为每个缺少中断处理例程的IRQ设置IRQ_WAITING状态位来完成的。

当中断产生时,因为没有注册处理例程,do_IRQ清除该位然后返回。当probe_irq_off被一个驱动程序调用的时候,只需要搜索那些没有设置IRQ_WAITING的IRQ。

2. 实现中断处理例程
处理例程不能像用户空间发送或者接收数据;
处理例程不能做任何可能发生休眠的操作;
处理例程不能调用schedule函数。

中断处理例程的功能就是将有关中断接收的信息反馈给设备,并根据正在服务的中断的不同含义对数据进行相应的读或写。

中断处理例程的一个典型任务是:如果中断通知进程所等待的事件已经发生,比如新的数据到达,就会唤醒在该设备上休眠的进程。

禁用单个中断
内核提供了三个函数,这些函数均在<asm/irq.h>中声明。这些函数是内核API的一部分。
void disable_irq(int irq);
void disable_irq_nosync(int irq);
void enabel_irq(int irq);
调用这些函数中的任何一个都会更新可编程中断控制器(Programmable Interrupt Controller, PIC)中指定中断的掩码。

禁用所有的中断
在<asm/system.h>中
void local_irq_save(unsigned long flags);
把当前中断状态保存在flags中。
void local_irq_disable(void);
只有我们知道中断并未在其他地方被禁用的情况下才能使用这个版本。

打开中断
void local_irq_restore(unsigned long flags);
void local_irq_enable(void);

3. 顶半部和底半部
Linux(连同很多其他的系统)将中断处理例程分成两部分:
“顶半部”的部分是实际响应中断的例程,也就使用request_irq注册的中断例程;
“底半部”是一个被顶半部调用,并在稍后更安全的时间内执行的例程。

典型的情况是顶半部保存设备的数据到一个设备特定的缓冲区并调度它的底半部,然后退出。

几乎每一个严格的中断处理例程都是以这种方式分成两部分的。
例如,当一个网络接口报告有新数据包到达时,处理例程仅仅接收数据并将它推到协议层上,而实际的数据包处理过程是在底半部执行的。

Linux内核有两种不同的机制可以用来实现底半部处理:
tasklet和工作队列
tasklet通常是底半部处理的优选机制,但所有的tasklet代码必须是原子的。
工作队列具有更高的延迟,但允许休眠。

(1)tasklet
tasklet是一个可以在由系统决定的安全时刻在软件中断上下文被调度运行的特殊函数。
tasklet的调度并不会累积。不会有同一个tasklet的多个实例并行地运行。
tasklet可确保和第一次调度它们的函数运行在同样的CPU上。
tasklet在中断处理例程结束前并不会开始运行。
必须使用宏DECLARE_TASKLET声明tasklet:
DECLARE_TASKLET(name, function, data);

(2)工作队列
工作队列会在将来的某个时间、在某个特殊的工作者进程上下文中调用一个函数。
工作队列函数运行在进程上下文中,可在必要时休眠。
但是不能从工作队列向用户空间复制数据,除非使用内存映射和DMA。
工作者进程无法访问其他任何进程的地址空间。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值