-
上半部指的是中断处理程序,下半部则指的是一些虽然与中断有相关性但是可以延后执行的任务。
-
在网络传输中,网卡接收到数据包这个事件不一定需要马上被处理,适合用下半部去实现;但是用户敲击键盘这样的事件就必须马上被响应,应该用中断实现。
-
中断不能被相同类型的中断打断,而下半部依然可以被中断打断;中断对于时间非常敏感,而下半部基本上都是一些可以延迟的工作。由于二者的这种区别,所以对于一个工作是放在上半部还是放在下半部去执行,可以参考下面4条:
-
如果一个任务对时间非常敏感,将其放在中断处理程序中执行。
-
如果一个任务和硬件相关,将其放在中断处理程序中执行。
-
如果一个任务要保证不被其他中断(特别是相同的中断)打断,将其放在中断处理程序中执行
-
其他所有任务,考虑放在下半部去执行。
-
-
软中断
-
软中断作为下半部机制的代表,是随着SMP(share memory processor)的出现应运而生的,它也是tasklet实现的基础(tasklet实际上只是在软中断的基础上添加了一定的机制)
-
软中断一般是“可延迟函数”的总称,它的出现就是因为要满足上面所提出的上半部和下半部的区别,使得对时间不敏感的任务延后执行,而且可以在多个CPU上并行执行,使得总的系统效率可以更高。它的特性包括:
-
产生后并不是马上可以执行,必须要等待内核的调度才能执行。软中断不能被自己打断(即单个cpu上软中断不能嵌套执行),只能被硬件中断打断(上半部)。
-
可以并发运行在多个CPU上(即使同一类型的也可以)。所以软中断必须设计为可重入的函数(允许多个CPU同时操作),因此也需要使用自旋锁来保其数据结构。
-
-
相关数据结构
-
软中段描述符 struct softirq_action{ void (action)(struct softirq_action);};
-
描述每一种类型的软中断,其中void(*action)是软中断触发时的执行函数。
软中断全局数据和类型
-
相关API
-
注册软中断
-
*void open_softirq(int nr, void (*action)(struct softirq_action ))
-
注册对应类型的处理函数到全局数组softirq_vec中。例如网络发包对应类型为NET_TX_SOFTIRQ的处理函数net_tx_action.
-
触发软中断
-
void raise_softirq(unsigned int nr)
-
以软中断类型nr作为偏移量置位每cpu变量irq_stat[cpu_id]的成员变量__softirq_pending,这也是同一类型软中断可以在多个cpu上并行运行的根本原因.
-
软中断执行函数
-
do_softirq–>__do_softirq
-
执行软中断处理函数__do_softirq前首先要满足两个条件:
-
不在中断中(硬中断、软中断和NMI) 。
-
有软中断处于pending状态。
-
-
为了避免软件中断在中断嵌套中被调用,并且达到在单个CPU上软件中断不能被重入的目的。
-
实现原理和实例
-
软中断的调度时机:
-
do_irq完成I/O中断时调用irq_exit。
-
系统使用I/O APIC,在处理完本地时钟中断时。
-
local_bh_enable,即开启本地软中断时。
-
SMP系统中,cpu处理完被CALL_FUNCTION_VECTOR处理器间中断所触发的函数时。
-
ksoftirqd/n线程被唤醒时。
-
-
处理流程
-
-
首先调用local_softirq_pending函数取得目前有哪些位存在软件中断。
-
调用__local_bh_disable关闭软中断,其实就是设置正在处理软件中断标记,在同一个CPU上使得不能重入__do_softirq函数。
-
重新设置软中断标记为0,set_softirq_pending重新设置软中断标记为0,这样在之后重新开启中断之后硬件中断中又可以设置软件中断位。
-
调用local_irq_enable,开启硬件中断。
-
之后在一个循环中,遍历pending标志的每一位,如果这一位设置就会调用软件中断的处理函数。在这个过程中硬件中断是开启的,随时可以打断软件中断。这样保证硬件中断不会丢失。
-
之后关闭硬件中断(local_irq_disable),查看是否又有软件中断处于pending状态,如果是,并且在本次调用__do_softirq函数过程中没有累计重复进入软件中断处理的次数超过max_restart=10次,就可以重新调用软件中断处理。如果超过了10次,就调用wakeup_softirqd()唤醒内核的一个进程来处理软件中断。设立10次的限制,也是为了避免影响系统响应时间。
-
调用_local_bh_enable开启软中断。
-
tasklet
-
由于软中断必须使用可重入函数,这就导致设计上的复杂度变高,作为设备驱动程序的开发者来说,增加了负担。而如果某种应用并不需要在多个CPU上并行执行,那么软中断其实是没有必要的。因此诞生了弥补以上两个要求的tasklet。它具有以下特性:
-
一种特定类型的tasklet只能运行在一个CPU上,不能并行,只能串行执行。
-
多个不同类型的tasklet可以并行在多个CPU上。
-
软中断是静态分配的,在内核编译好之后,就不能改变。但tasklet就灵活许多,可以在运行时改变(比如添加模块时)。
-
-
tasklet是在两种软中断类型的基础上实现的,因此如果不需要软中断的并行特性,tasklet就是最好的选择。也就是说tasklet是软中断的一种特殊用法,即延迟情况下的串行执行。
-
相关数据结构
-
tasklet描述符
-
task链表
-
相关API
-
定义tasklet
-
tasklet操作
-
static inline void tasklet_disable(struct tasklet_struct *t)
- //函数暂时禁止给定的tasklet被tasklet_schedule调度,直到这个tasklet被再次被enable;若这个tasklet当前在运行,这个函数忙等待直到这个tasklet退出
-
static inline void tasklet_enable(struct tasklet_struct *t)
- //使能一个之前被disable的tasklet;若这个tasklet已经被调度,
它会很快运行。tasklet_enable和tasklet_disable必须匹配调用, 因为内核跟踪每个tasklet的"禁止次数"
- //使能一个之前被disable的tasklet;若这个tasklet已经被调度,
-
static inline void tasklet_schedule(struct tasklet_struct *t)
- //调度 tasklet 执行,如果tasklet在运行中被调度, 它在完成后会再次运行;
这保证了在其他事件被处理当中发生的事件受到应有的注意. 这个做法也允许一个 tasklet 重新调度它自己
- //调度 tasklet 执行,如果tasklet在运行中被调度, 它在完成后会再次运行;
-
tasklet_hi_schedule(struct tasklet_struct *t)
- //和tasklet_schedule类似,只是在更高优先级执行。当软中断处理运行时, 它处理高优先级 tasklet
在其他软中断之前,只有具有低响应周期要求的驱动才应使用这个函数, 可避免其他软件中断处理引入的附加周期.
- //和tasklet_schedule类似,只是在更高优先级执行。当软中断处理运行时, 它处理高优先级 tasklet
-
tasklet_kill(struct tasklet_struct *t)
- //确保了 tasklet 不会被再次调度来运行,通常当一个设备正被关闭或者模块卸载时被调用。如果 tasklet 正在运行,
这个函数等待直到它执行完毕。若 tasklet 重新调度它自己,则必须阻止在调用 tasklet_kill 前它重新调度它自己,如同使用
del_timer_sync
- //确保了 tasklet 不会被再次调度来运行,通常当一个设备正被关闭或者模块卸载时被调用。如果 tasklet 正在运行,
-
-
实现原理
-
调度原理
-
tasklet执行过程
-
TASKLET_SOFTIRQ对应执行函数为tasklet_action,HI_SOFTIRQ为tasklet_hi_action,以tasklet_action为例说明,tasklet_hi_action大同小异。