在多核CPU中,中断分配通常由Linux内核处理。内核负责将中断分配给每个CPU核心,并监控每个核心的状态。内核还负责协调任务和中断之间的调度,以确保任务可以及时得到处理。
软中断
软中断子系统
软中断是一个内核子系统:
1、每个 CPU 上会初始化一个 ksoftirqd
内核线程,负责处理各种类型的 softirq 中断事件;
用 cgroup ls 或者 ps -ef
都能看到:
$ systemd-cgls -k | grep softirq # -k: include kernel threads in the output
├─ 12 [ksoftirqd/0]
├─ 19 [ksoftirqd/1]
├─ 24 [ksoftirqd/2]
...
2、软中断事件的 handler 提前注册到 softirq 子系统, 注册方式 open_softirq(softirq_id, handler)
例如,注册网卡收发包(RX/TX)软中断处理函数:
// net/core/dev.c
open_softirq(NET_TX_SOFTIRQ, net_tx_action);
open_softirq(NET_RX_SOFTIRQ, net_rx_action);
3、软中断占 CPU 的总开销:可以用 top
查看,里面 si
字段就是系统的软中断开销(第三行倒数第二个指标):
$ top -n1 | head -n3
top - 18:14:05 up 86 days, 23:45, 2 users, load average: 5.01, 5.56, 6.26
Tasks: 969 total, 2 running, 733 sleeping, 0 stopped, 2 zombie
%Cpu(s): 13.9 us, 3.2 sy, 0.0 ni, 82.7 id, 0.0 wa, 0.0 hi, 0.1 si, 0.0 st
三种推迟执行方式(softirq/tasklet/workqueue)
前面提到,Linux 中的三种推迟中断执行的方式:
- softirq
- tasklet
- workqueue
其中,
- softirq 和 tasklet 依赖软中断子系统,运行在软中断上下文中;
- workqueue 不依赖软中断子系统,运行在进程上下文中。
softirq
前面已经看到, Linux 在每个 CPU 上会创建一个 ksoftirqd 内核线程。
softirqs 是在 Linux 内核编译时就确定好的,例外网络收包对应的 NET_RX_SOFTIRQ
软中断。因此是一种静态机制。如果想加一种新 softirq 类型,就需要修改并重新编译内核。
tasklet
如果对内核源码有一定了解就会发现,softirq 用到的地方非常少,原因之一就是上面提到的,它是静态编译的, 靠内置的 ksoftirqd 线程来调度内置的那 9 种 softirq。如果想新加一种,就得修改并重新编译内核, 所以开发成本非常高。
实际上,实现推迟执行的更常用方式 tasklet。它构建在 softirq 机制之上, 具体来说就是使用了上面提到的两种 softirq:
HI_SOFTIRQ
TASKLET_SOFTIRQ
tasklet 在内核中的使用非常广泛。不过,后面又出现了第三种方式:workqueue。
workqueue
这也是一种推迟执行机制,与 tasklet 有点类似,但也有很大不同。
- tasklet 是运行在 softirq 上下文中;
- workqueue 运行在内核进程上下文中;这意味着 wq 不能像 tasklet 那样是原子的;
- tasklet 永远运行在指定 CPU,这是初始化时就确定了的;
- workqueue 默认行为也是这样,但是可以通过配置修改这种行为。
kworker 线程调度 workqueues,原理与 ksoftirqd 线程调度 softirqs 一样。但是我们可以为 workqueue 创建新的线程,而 softirq 则不行。