1、中断
设备的中断会打断内核进程中的正常调度和运行,系统对更高吞吐率的追求势必要求中断服务程序尽量短小精悍。但是,这个良好的愿望往往与现实并不吻合。在大多数真实的系统中,当中断到来时,要完成的工作往往并不会是短小的,它可能要进行较大量的耗时处理。为了在中断执行时间尽量短和中断处理需完成的工作尽量大之间找到一个平衡点,Linux将中断处理程序分解为两个半部:顶半部和底半部。
2、顶半部
顶半部用于完成尽量少的比较紧急的功能,它往往只是简单地读取寄存器中的中断状态,并在清除中断标志后就进行“登记中断”的工作。“登记中断”意味着将底半部处理程序挂到该设备的底半部执行队列中去
3、底半部
软中断:软中断( Softirq)也是一种传统的底半部处理机制,它的执行时机通常是顶半部返回的时候,
tasklet是基于软中断实现的,因此也运行于软中断上下文。
工作队列则运行于进程上下文。因此,在软中断和 tasklet处理函数中不允许睡眠,而在工作队列处理函数中允许睡眠。
一般地,有如下特征的任务放在上半部:
1、对时间非常敏感
2、与硬件相关的
3、不能被其他中断打断的工作
以上三点之外的,考虑放在下半部。
中断底半部实现的机制有:
1.tasklet
2.工作队列work queue
3.软中断softirq
其中tasklet由软中断实现。
软中断实现机制
软中断可以使内核延期执行某个任务,他们的运作方式和具体的硬件类似,甚至可以说这里就是模拟的硬件中断,所以称之为软件中断也不为过。既然提到软中断,那么自然就设计到几个点:
软中断的注册
软中断的触发
软中断的处理
内核版本中定义了10个软中断,并且系统不建议用户自己添加软中断,所以对于软中断基本用于已定义好的功用,而如果用户需要,可以使用其中的一个类型即TASKLET_SOFTIRQ
1、软中断的注册
软中断的核心机制是一张表,类似于IDT,包含32个softirq_vec结构,该结构很简单:就是一个函数地址,每个软中断对应其中的一个,所以现在也仅仅使用前10项。
open_softirq函数注册一个软中断,具体就是在softirq_vec数组中根据中断号设置其对应的处理例程。
softirq_init中初始化了TASKLET相关的处理函数。
open_softirq函数注册一个软中断,具体就是在softirq_vec数组中根据中断号设置其对应的处理例程。
softirq_init中初始化了TASKLET相关的处理函数。
而且,中断处理程序在响应中断时,可能还会临时关闭中断,这意味着,如果当前中断处理程序没有执行完之前,系统中其他的中断请求都无法被响应,也就说中断有可能会丢失,所以中断处理程序要短且快。
还是回到外卖的例子,小林到了晚上又点起了外卖,这次为了犒劳自己,共点了两份外卖,一份小龙虾和一份奶茶,并且是由不同的配送员来配送,那么问题来了,当第一份外卖送到时,配送员给我打了长长的电话,说了一些杂七杂八的事情,比如给个好评等等,但如果这时另一位配送员也想给我打电话。
很明显,这时第二位配送员因为我在通话中(相当于关闭了中断响应),自然就无法打通我的电话,他可能尝试了几次后就走掉了(相当于丢失了一次中断)。
Linux 系统为了解决中断处理程序执行过长和中断丢失的问题,将中断过程分成了两个阶段,分别是上半部和下半部分。
上半部用来快速处理中断,一般会暂时关闭中断请求,主要负责处理跟硬件紧密相关或者时间敏感的事情。
下半部用来延迟处理上半部未完成的工作,一般以内核线程的方式运行。
-
上半部直接处理硬件请求,也就是硬中断,主要是负责耗时短的工作,特点是快速执行;
-
下半部是由内核触发,也就说软中断,主要是负责上半部未完成的工作,通常都是耗时比较长的事情,特点是延迟执行;
-
硬中断(上半部)是会打断 CPU 正在执行的任务,然后立即执行中断处理程序,而软中断(下半部)是以内核线程的方式执行,并且每一个 CPU 都对应一个软中断内核线程,名字通常为「ksoftirqd/CPU 编号」,比如 0 号 CPU 对应的软中断内核线程的名字是 ksoftirqd/0
-
软中断不只是包括硬件设备中断处理程序的下半部,一些内核自定义事件也属于软中断,比如内核调度等、RCU 锁(内核里常用的一种锁)等。
在 Linux 系统里,我们可以通过查看 /proc/softirqs 的 内容来知晓软中断的运行情况,以及 /proc/interrupts 的 内容来知晓硬中断的运行情况。
要注意第一列的内容,它是代表着软中断的类型。在我的系统里,软中断包括了 10 个类型,分别对应不同的工作类型,比如 NET_RX 表示网络接收中断,NET_TX 表示网络发送中断、TIMER 表示定时中断、RCU 表示 RCU 锁中断、SCHED 表示内核调度中断。
要注意同一种类型的软中断在不同 CPU 的分布情况,正常情况下,同一种中断在不同 CPU 上的累计次数相差不多。比如我的系统里,NET_RX 在 CPU0 、CPU1、CPU2、CPU3 上的中断次数基本是同一个数量级,相差不多。
这些数值是系统运行以来的累计中断次数,数值的大小没什么参考意义,但是系统的中断次数的变化速率才是我们要关注的,我们可以使用 watch -d cat /proc/softirqs 命令查看中断次数的变化速率。
前面提到过,软中断是以内核线程的方式执行的,我们可以用 ps 命令可以查看到,下面这个就是在我的服务器上查到软中断内核线程的结果:
可以发现,内核线程的名字外面都有有中括号,这说明 ps 无法获取它们的命令行参数,所以一般来说,名字在中括号里到,都可以认为是内核线程。
而且,你可以看到有 4 个 ksoftirqd 内核线程,这是因为我这台服务器的 CPU 是 4 核心的,每个 CPU 核心都对应着一个内核线程。
上半部,对应硬中断,由硬件触发中断,用来快速处理中断;
下半部,对应软中断,由内核触发中断,用来异步处理上半部未完成的工作;