Linux内核中的软中断、tasklet和工作队列详解

软中断、tasklet和工作队列并不是Linux内核中一直存在的机制 , 而是由更早版本的内核中的“下半部”(bottom half)演变而来 。 下半部的机制实际上包括五种 , 但2.6版本的内核中 , 下半部和任务队列的函数都消失了 , 只剩下了前三者 。介绍这三种下半部实现之前 , 有必要说一下上半部与下半部的区别 。上半部指的是中断处理程序 , 下半部则指的是一些虽然与中断有相关性但是可以延后执行的任务 。 举个例子:在网络传输中 , 网卡接收到数据包这个事件不一定需要马上被处理 , 适合用下半部去实现;但是用户敲击键盘这样的事件就必须马上被响应 , 应该用中断实现 。两者的主要区别在于:中断不能被相同类型的中断打断 , 而下半部依然可以被中断打断;中断对于时间非常敏感 , 而下半部基本上都是一些可以延迟的工作 。 由于二者的这种区别 , 所以对于一个工作是放在上半部还是放在下半部去执行 , 可以参考下面4条:

  1. 如果一个任务对时间非常敏感 , 将其放在中断处理程序中执行 。
  2. 如果一个任务和硬件相关 , 将其放在中断处理程序中执行 。
  3. 如果一个任务要保证不被其他中断(特别是相同的中断)打断 , 将其放在中断处理程序中执行 。
  4. 其他所有任务 , 考虑放在下半部去执行 。有写内核任务需要延后执行 , 因此才有的下半部 , 进而实现了三种实现下半部的方法 。 这就是本文要讨论的软中断、tasklet和工作队列 。
下表可以更直观的看到它们之间的关系 。
Linux内核中的软中断、tasklet和工作队列详解文章插图
软中断软中断作为下半部机制的代表 , 是随着SMP(share memory processor)的出现应运而生的 , 它也是tasklet实现的基础(tasklet实际上只是在软中断的基础上添加了一定的机制) 。 软中断一般是“可延迟函数”的总称 , 有时候也包括了tasklet(请读者在遇到的时候根据上下文推断是否包含tasklet) 。 它的出现就是因为要满足上面所提出的上半部和下半部的区别 , 使得对时间不敏感的任务延后执行 , 而且可以在多个CPU上并行执行 , 使得总的系统效率可以更高 。 它的特性包括:
  • 产生后并不是马上可以执行 , 必须要等待内核的调度才能执行 。 软中断不能被自己打断(即单个cpu上软中断不能嵌套执行) , 只能被硬件中断打断(上半部) 。
  • 可以并发运行在多个CPU上(即使同一类型的也可以) 。 所以软中断必须设计为可重入的函数(允许多个CPU同时操作) , 因此也需要使用自旋锁来保其数据结构 。
相关数据结构
  • 软中断描述符 struct softirq_action{ void (*action)(struct softirq_action *);}; 描述每一种类型的软中断 , 其中void(*action)是软中断触发时的执行函数 。
  • 软中断全局数据和类型
static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;enum{HI_SOFTIRQ=0, /*用于高优先级的tasklet*/TIMER_SOFTIRQ, /*用于定时器的下半部*/NET_TX_SOFTIRQ, /*用于网络层发包*/NET_RX_SOFTIRQ, /*用于网络层收报*/BLOCK_SOFTIRQ,BLOCK_IOPOLL_SOFTIRQ,TASKLET_SOFTIRQ, /*用于低优先级的tasklet*/SCHED_SOFTIRQ,HRTIMER_SOFTIRQ,RCU_SOFTIRQ, /* Preferable RCU should always be the last softirq */NR_SOFTIRQS};相关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)