ISR Interrupt Service Routines(中断服务程序)
INTC Interrupt controller(中断控制器)
Linux中通常分为外部中断(又叫硬件中断)和内部中断(又叫异常)
软件对硬件进行配置后,软件期望等待硬件的某种状态(比如,收到了数据),这里有两种方式,
一种是轮询(polling): CPU 不断的去读硬件状态。另一种是当硬件完成某种事件后,给 CPU
一个中断,让 CPU 停下手上的事情,去处理这个中断。很显然,中断的交互方式提高了系统的吞吐。
当 CPU 收到一个中断 (IRQ)的时候,会去执行该中断对应的处理函数(ISR)。普通情况下,
会有一个中断向量表,向量表中定义了 CPU 对应的每一个外设资源的中断处理程序的入口,当
发生对应的中断的时候, CPU 直接跳转到这个入口执行程序。也就是中断上下文。(注意:中断上下文中,不可阻塞睡眠)。
在Linux中希望尽快完成ISR,但是有些ISR事务繁重,会消耗很多时间,导致反应速度变差,linux针对这种情况,分了上半部,底半部。
1. 上半部(top half):收到一个中断,立即执行,有严格的时间限制,只做一些必要的工作,比如:应答,复位等。这些工作都是在所有中断被禁止的情况下完成的。
2. 底半部(bottom half):能够被推迟到后面完成的任务会在底半部进行。在适合的时机,下半部会被开中断执行。
相关API:
申请注册一个中断处理函数:
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev)
参数 含义
irq 表了该中断的中断号,一般 CPU 的中断号都会事先定义好。
handler 中断发生后的 ISR
flags 中断标志( IRQF_DISABLED / IRQFSAMPLE_RANDOM / IRQF_TIMER / IRQF_SHARED)
标志 含义
IRQF_DISABLED 设置这个标志的话,意味着内核在处理这个 ISR 期间,要禁止其他中断(多数情况不使用这个)
IRQFSAMPLE_RANDOM 表明这个设备产生的中断对内核熵池有贡献
IRQF_TIMER 为系统定时器准备的标志
IRQF_SHARED 表明多个中断处理程序之间共享中断线。同一个给定的线上注册每个处理程序,必须设置这个
name 中断相关的设备 ASCII 文本,例如 "keyboard",这些名字会在 /proc/irq 和 /proc/interrupts 文件使用
dev 用于共享中断线,传递驱动程序的设备结构。非共享类型的中断,直接设置成为 NULL
return:0 success -EBUSY表示中断线已使用或者没有指定IRQF_SHARED
注意:request_irq 函数可能引起睡眠,所以不允许在中断上下文或者不允许睡眠的代码中调用。
释放中断
const void *free_irq(unsigned int irq, void *dev_id)
实现底半部的三种方法:tasklet,等待队列,软中断
软中断:很少用于实现下半部,但是tasklet是软中断实现的。软中断就是软件实现的异步中断,优先级低于硬件中断,高于普通进程。
软中断是在编译时候静态分配的,要用软中断就要修改内核代码。
实现软中断的相关结构体和函数:
/*include/linux/interrupt.h*/
struct softirq_action
{
void (*action)(struct softirq_action *); //软中断处理函数
};
/*include/linux/interrupt.h*/
enum //软中断的优先级别
{
HI_SOFTIRQ=0, //用于tasklet的软中断,优先级最高,为0
TIMER_SOFTIRQ, //定时器的下半部
NET_TX_SOFTIRQ, //发送网络数据的软中断
NET_RX_SOFTIRQ, //接受网络数据的软中断
BLOCK_SOFTIRQ,
TASKLET_SOFTIRQ, //也是用于实现tasklet
SCHED_SOFTIRQ,
HRTIMER_SOFTIRQ,
RCU_SOFTIRQ, /* Preferable RCU should always be the last softirq */
XIAOBAI_SOFTIRQ, //这是自添加软中断的一个中断号,优先级最低
NR_SOFTIRQS, //这个就是上面所说的软中断结构体数组成员个数
};
/*kernel/softirq.c*/
static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp; //声明软中断的一个表
void open_softirq(int nr, void (*action)(struct softirq_action *)) //申请注册一个软中断(中断号绑定执行函数)
注意:如果想自添加软中断,需要导出符将open_softirq导出,在其他驱动中可以调用。EXPORT_SYMBOL(open_softirq);
void raise_softirq(unsigned int nr) //触发对应的软中断,参数为枚举的中断号XIAOBAI_SOFTIRQ
注意:如果想自添加软中断,需要导出符将raise_softirq导出,在其他驱动中可以调用。EXPORT_SYMBOL(raise_softirq);
实现流程为:
1.在include/linux/interrupt.h中添加自己的软中断号XIAOBAI_SOFTIRQ(在RCU_SOFTIRQ后添加)
2.将kernel/softirq.c中open_softirq和raise_softirq两个函数使用导出符EXPORT_SYMBOL导出。
3.申请一个软中断并绑定处理函数 open_softirq(XIAOBAI_SOFTIRQ,handler_function);
4.在中断处理函数中执行下半部,也就是软中断。raise_softirq(XIAOBAI_SOFTIRQ);
软中断补充:所谓的触发软中断,并不是立刻触发,他只是告诉内核,下次执行软中断的时候,记得执行我的软中断处理函数。
软中断优先级最高的中断号就是tasklet。
tasklet:
和软中断一样,触发函数执行后并不是立刻执行,只是告诉内核,下次执行软中断的时候执行我的处理函数。
实现方法:
1. struct tasklet_struct test_tasklet; //定义tasklet结构体
2. tasklet_init(&test_tasklet, test_func, (unsigned long)123); //test_func为处理函数,123为给处理函数传的参数
3. tasklet_schedule(&test_tasklet);或者tasklet_hi_schedule(&test_tasklet);//中断返回前调度tasklet
前者使用TASKLET_SOFTIRQ优先级,后者使用HI_SOFTIRQ优先级。
4.移除tasklet的API:void tasklet_kill(struct tasklet_struct *t)//如果tasklet在运行,程序会休眠,等到它执行完毕。
还有禁止和激活tasklet的函数:(禁止后,tasklet就不能用了,直到激活)
static inline void tasklet_disable(struct tasklet_struct *t) //禁止
static inline void tasklet_enable (struct tasklet_struct *t) //激活
补充:软中断实现tasklet的中断号优先级有TASKLET_SOFTIRQ和HI_SOFTIRQ两种。
一般使用tasklet_schedule会使用TASKLET_SOFTIRQ优先级5 (include/linux/interrupt.h中查看优先级)
使用tasklet_hi_schedule 会使用HI_SOFTIRQ优先级0
总结:软中断的优点:优先级比普通的进程高,调度速度快。
软中断的缺点:处于中断上下文,所以不能睡眠
工作队列:
工作队列的执行上下文是内核线程,所以可以调度和睡眠。这就意味着需要获取大量内存时、需要获取信号量时、
阻塞IO时,工作队列比较有效。
实现方法:
1.struct workqueue_struct test_wq; //定义工作队列
2.实现工作队列处理函数test_func();
3.INIT_WORK(&test_wq,test_func);//初始化工作队列
DECLARE_WROK(name,func);//定义并初始化工作队列
4.schedule_work(&test_wq); //调度工作队列,成功返回1
schedule_delayed_work(&test_wq,delay); //延迟调度
总结:
1,需要睡眠,阻塞的,只能用工作队列。
2,短时间内中断数量很多的,任务队列,软中断会更好。例如网络。
3,对性能要求很高的话,软中断最好。
4,使用任务队列时,应该注意同一个任务被多次调用,同一个函数被多个任务队列注意。
5,软中断要注意SMP,函数的重入。
------------------------------------------------------------------------------------------------------------
| |tasklet | 软中断 | 工作队列
|-----------------------------------------------------------------------------------------------------------
|SMP |向一个CPU注册 |多个CPU并发 | 默认同一个CPU上或指定CPU
| | | |(queue_delayed_work_on)
|-----------------------------------------------------------------------------------------------------------
|上下文 |中断 |中断 |进程
|函数 |1.同类型只能执行一个 |1.不能阻塞,睡眠 |1.可阻塞,睡眠
| |2.不能阻塞睡眠 |2.不带参数 |2.不带参数
| |3.可带参数 |3.ksoftirqd内核线程 | 3.events/0内核线程
| |4.ksoftirqd内核线程 | |
|-----------------------------------------------------------------------------------------------------------
|执行 |1.单个tasklet串行 |1.同一个CPU上串行 |1.进程调度内,可重新调度
| |2.同一个tasklet,只响应最后一次| 2.不同的CPU并行 |
| | |3.不能抢占另一软中断 |
| |3.多个之间为并行 | |
|-----------------------------------------------------------------------------------------------------------
|atomic_context |原子模式执行 |原子模式执行 |不是
|-----------------------------------------------------------------------------------------------------------
|响应时间 |1.tasklet_hi_schedule更早 |1.最迟在下一个time tick时, | 可指定延后时间,jiffies单位(1ms-百ms)
| |2.最迟在下一个time tick时, |中断中调用,就是中断完成 |内核线程,上下文切换
| |中断中调用,就是中断完成 |后立即执行 |
| |后立即执行 | |
|-----------------------------------------------------------------------------------------------------------
|开销 |次之(与软中断接近) |最小 |开销最大