中断和中断处理程序
- 中断随时可能产生,打断CPU的执行,CPU转而处理中断。
- 不同的设备对应的中断不同,每个中断都通过一个唯一的数字标志。
- 这些中断值称为中断请求(IRQ)线,每个irq线关联一个数值。
- 中断处理程序
- 响应中断时,内核会执行一个函数,中断处理程序/中断服务例程ISR, 一个设备的中断处理程序是他的设备驱动的一部分。
- IO资源包括 : 中断,I/O端口,共享RAM,DMA。驱动程序需要管理注册释放这些资源。
上半部:接收到中断就立即执行,只做有严格时限的工作,如对中断应答或复位硬件。
下半部 : 能够被允许稍后完成的工作推迟到下半部执行。
- 注册中断处理程序
- Request_irq(uint irq, irq_handlet_t handler, ulong flasgs,void* dev)注册中断处理程序,激活给定的中断线;这个函数可能睡眠,不能在中断上下文/其他不允许阻塞的代码中执行。
- 卸载驱动程序时
- 注销相应中断处理程序,释放中断线。 void free_irq(uint irq, void* dev);
- Linux的中断处理程序是无需重入的,给定的中断处理程序在执行时,相应中断线在所有处理器上都会被屏蔽
中断上下文:没有后备进程,不可睡眠。中断处理程序打断了其他的代码。
中断处理机制的实现
- 设备产生中断,通过总线把电信号发送给中断控制器
- 如果中断线是激活的,中断控制器发送中断到处理器(处理器特定管脚)
- 如果处理器没有禁止该中断,处理器会停止正在做的事关闭中断系统,调到预定义的中断处理程序入口。
- 每条中断线调到一个唯一位置。初始入口点保存这个中断线号,存放寄存器的值调用do_irq()
- 计算出中断号,对中断应答,禁止这条线上的中断传递
- 确保这条中断线上有一个有效的处理程序,已经启动,但没有执行。
- 调用handle_IRQ_event()调用中断线安装的中断处理程序。
- 将中断禁止,返回到do_IRQ。
- 做清理工作,返回初始入口点,跳到ret_from_intr()
- 检查是否调度挂起;恢复寄存器;内核恢复到中断的点。
禁止当前处理器中断和激活中断,
Local_irq_disable();local_irq_enable();
Unsigned long flags; local_irq_save(flags);local_irq_restore(flags);
禁止指定中断
Disable_irq(int);禁止中断向所有处理器的中断。
中断处理程序上、下半步处理逻辑分配原则:
- 上半部:
- 任务对时间非常敏感
- 任务和硬件相关
- 任务保证不被其他中断打断,不并发,不阻塞
- 下半部:
- 对时间不敏感
- 和硬件无关
- 可以被其他中断打断,可以睡眠,可以并发
Linux的上半部就是中断处理程序,下半部有多种机制:
软中断
软中断是一组静态定义的下半部接口,有32个,可以在所有处理器上同时执行,类型相同也可以;在编译时静态注册。
实现:
struct softirq_action{
void (*action)(struct softirq_action*);
}
32个目前用了6个。
static struct softirq_action soft_irq_vec[NR_SOFTIRQS];
- 中断处理程序:内核运行软中断处理程序的时候,执行action函数。
一个软中断不会抢占另外一个软中断。唯一可以抢占软中断的是中断处理程序。其他的软中断甚至同类型的可以在其他处理器上同时执行
- 执行软件中断:一个注册的软件中断在标记后才会执行,这称作触发中断。
中断处理程序在返回前标记软中断。
在:硬件中断代码返回时;在ksoftirq内核线程中;显示检查执行软中断 处,待处理的软中断会被检查和执行
软中断在do_softirq中执行
u32 pending;
pending = local_sofqirq_pending();
if(pending){
struct softirq_action* h;
set_softirq_pending(0);
h = softirq_vec;
do{
if(pending & 1){
h->action();
}
h++;
pending >>=1;
}while(pending);
}
使用软中断
软中断留给对时间要求最严格及最重要的下半部使用。目前只有网络,scsi使用内核定时器和tasklet都建立在软中断上。
- 通过枚举类型静态声明软中断,并分配索引
- 注册处理程序
- open_softirq(NET_TX_SOFTIRQ,net_tx_action);软中断处里程序执行时,允许响应中断,但不能睡眠。由于只禁止当前处理器上的运行,其他处理器可以同时运行处理程序,需要加锁保护。
- rase_softirq(NET_TX_SOFTIRQ)将软中断设置为挂起状态,下次再调用do_softirq时执行。
Tasklet
基于软件中断实现的,灵活性强,动态创建的下半部实现机制。两个不同类型的tasklet可以在不同处理器上运行,但相同的不可以。可以通过代码动态注册。
实现:基于软中断
struct tasklet_struct {
struct tasklet_struct *next;
unsigned long sate;
atomic_t count;
void (*func)(unsigned long);
unsigned long data;
};
调度:每个处理器有tasklet_vec和tasklet_hi_vec结构,分别为低、高优先级tasklet_strucu链表
由tasklet_schedule()和tasklet_hi_schdule()进行调度
- 检查tasklet是否为TASKLET_STATE_SCHED.如果是返回
- 调用_tasklet_schedule
- 保存中断状态,禁止本地中断
- 把需要调度的处理器加到tasklet_vec 或tasklet_hi_vec链表的头上
- 唤起TASKLET_SOFTIRQ或TASKLET_HI_SOFTIRQ软中断
- 恢复中断状态并返回
软中断处理程序:
tasklet_action(),tasklet_hi_action()的操作
- 禁止中断
- 将当前处理器置为null,清空链表
- 允许中断
- 循环遍历链表每一个待处理的tasklet
- 如果是多处理器系统,判断是否TASKLET_STATE_SCHED,如果在运行,跳过。
- 如果未在执行,设置TASKLET_STATE_RUN。
- 检查count==0,否则tasklet被禁止,跳过。
- 执行tasklet,清空TASKLET_STATE_RUN标志
- 执行下一个tasklet
使用tasklet
- 声明tasklet:
- DECLARE_TASKLET(name,func,data)DECLARE_TASKLET_DISABLED(.)
tasklet_init(t, tasklet_handler, dev);
- 编写处理程序:因为是依靠软中断实现的,处理程序不能睡眠。Tasklet允许响应中断。
- 调用tasklet_schedule(&my_tasklet);调度tasklet,实际上是标记/挂起,只要有机会,my_tasklet就会尽快执行。
tasklet_disable(&my_tasklet); tasklet_enable(&my_tasklet);禁止和激活
折衷
频繁中断或tasklet频繁发生的时候:尽快处理,用户进程得不到响应;滞后执行,中断处理也不快。
==》》使用低优先级核心进程专门处理软中断ksoftirqd/n
for(;;){
if(!softirq_pending(cpu)){
schedule();
}
set_current_state(TASK_RUNNING);
while(softirq_pending(cpu)){
do_softirq();
if(need_schdule())schedule();
}
}
工作队列:
将下半部功能交由内核线程执行,有着线程上下文环境,可以睡眠。
提供创建worker threads的接口,提供接口把需要推后执行的任务排到队列里,提供默认的工作者线程处理排到队列里的下半部工作。
实现:
数据结构
- 每种工作者线程有一个workqueue_struct结构
- 里面有NR_CPUS个cpu_work_queue_strcut对应于每个处理器的一个工作者线程
- 工作用work_struct表示,含有一个执行函数fuc,每个cpu的工作线程都对应一个work_struct链表。
worker_thread()的核心,是一个死循环
- 线程将自己放置为休眠状态
- 若链表为空,休眠
- 不为空,调用run_workqueue函数执行工作。
- 链表不为空时,循环执行
- 选取下一个节点对象,获取执行函数和参数
- 待处理标志清0
- 调用函数
- 重复执行
使用:
- 创建推后的工作
DECLARE_WORK(name, void(func)(void), void*data);静态
INIT_WORK(strut work_struct* task,…);动态
- 工作队列处理函数
运行于进程上下文,允许响应中断,不持有锁,可以睡眠。不能访问用户空间
- 对工作进行调度
schedule_work(&work);提交给工作者进程
schedule_delay_work(&work,delay)
- 刷新操作
flush_scheduled_work(),函数等待队列中所有对象都被执行以后返回
- 创建新的工作队列
如果缺省的队列不能满足你的需要,你应该创建新的工作队列和与之相对应的工作线程。
各种机制的比较
下半部 |
上下文 |
顺序执行保障 |
软中断 |
中断 |
没有 |
Tasklet |
中断 |
同类型不能同时执行 |
工作队列 |
进程 |
没有(和进程上下文一样,被调度) |
如果任务需要推后到进程上下文完成,有休眠的需要 工作队列
任务队列接口简单,同种类型不能同时执行 tasklet
软中断提供的执行序列化的保障最少,必须格外小心采取一些步骤确保共享数据