Linux 内核|中断

本文最后更新于:4 years ago

1.中断

中断是系统用来响应硬件设备请求的一种机制,操作系统收到硬件的中断请求,会打断正在执行的进程,然后调用内核中的中断处理程序来响应请求。

举例:敲击键盘时,键盘控制器会发送一个中断,通知操作系统有键按下。

  • 中断处理请求应该短且快。

    响应中断请求的程序,也就是中断处理程序,要尽可能快的执⾏完,这样可以减少对正常进程运⾏调度地影响。

  • 否则会造成 临时关闭中断。

    当前中断处理程序未执行完,系统内其他设备的中断请求无法被响应,即其他中断会丢失。

    举例:当一个电话打进来,如果不迅速沟通完,其他人打电话给你,你就无法接到。

    类比到系统中,例如网卡有数据包进来,系统需要迅速将数据包先拷到内存中,避免网卡因为缓存不够,发生丢包。

2.同步中断和异步中断

改变处理器执行指令的顺序的一个事件都是中断,分为同步中断和异步中断。

  • 同步中断(synchronous):即异常,一条指令终止后,CPU 才发生中断,所以是同步。异常通常可分为错误(Faults)、陷阱(Traps)和中止(Aborts)。

  • 异步中断(asynchronous):即通常所说的中断(interrupt),由其他硬件设备依照CPU时钟信号随机产生。

3.硬中断与软中断

中断实际上有两种:

  • 外部或硬件引起的中断,硬中断。
  • 软件引起的中断,软中断。

硬件引起的中断,由 Local APIC 或与 Local APIC 连接的处理器针脚接收。

软件引起的中断,由处理器自身的特殊情况引起(有时使用特殊架构的指令)。一个常见特殊情况的例子就是 除数为零,另一个例子就是使用 系统调用(system call)。

4.为何需要软中断

主要是为解决 中断处理程序执行时间过长 和 中断丢失 的问题,将中断分为两个阶段,分别是 上半部分(硬中断完成)下半部分(软中断完成)

  • 上半部,由硬件触发中断,用来快速处理中断。

    一般会暂时关闭中断请求,主要负责处理跟硬件紧密相关或者时间敏感的事情。硬中断处理程序完成耗时短的工作,快速执行。

  • 下半部,由内核触发中断,用来异步处理上半部未完成的工作。

    一般以「内核线程」的方式运行。软中断处理程序完成耗时长的工作,延迟执行。

硬中断会打断 CPU 正在执行的任务,然后立即执行中断处理程序;而软中断以内核线程的方式执行,每个 CPU 对应一个软中断内核线程,名称通常为 「ksoftirqd/CPU编号」,如 0 号 CPU 对应的软中断内核线程名称是 「ksoftirqd/0」。

软中断除了包含硬件设备中断处理程序的下半部分,一些内核自定义事件也是软中断,比如:内核调度、RCU锁(内核中常用的一种锁)。

5.查看软中断

  • Linux 中,在 /proc/softirqs 中可查看软中断的运行情况;

  • /porc/interrupts 中可查看硬中断的运行情况。

porc:一个指向内核数据结构的接口,通过它能够查看和改变各种系统属性。

  • 第一列表示软中断的类型, 比如 NET_RX 表示网络接收中断,NET_TX 表示网络发送中断,TIMER 表示
    定时中断、 RCU 表示 RCU 锁中断、SCHED 表示内核调度中断。
  • 数值显示的是每个 CPU 对应的不同类型的软中断的 累计运行次数。同一种类型的软中断在不同 CPU 的累计次数相差不多。
  • 累计次数无需关注,中断次数的变化速率 是重点。watch -d cat /proc/softirqs 查看中断次数的变化速率。

内核线程的名字外面都有有中括号,这说明 ps 无法获取它们的命令行参数。一般来说,名字在中括号里,都可以认为是内核线程。

6.软中断CPU使用率过高

top 查看 CPU 使用情况,按 1 即可显示 CPU 核心。倒数第 2 列,si 表示 CPU 在软中断上的使用率。

如何判断是由哪个类型软中断导致的 CPU 使用率过高?

使用 watch -d cat /proc/softirqs 查看每个软中断类型的中断次数的变化速率。

  1. 对于网络 I/O 比较高的 Web 服务器,NET_RX 网络接收中断的变化速率相比其他中断类型快很多。

  2. NET_RX 网络接收中断次数的变化速率过快,需要使用 sar -n DEV 查看网卡的网络包接收速率情况,然后分析是哪个网卡有大量的网络包进来。

  3. 再通过 tcpdump 抓包,分析这些包的来源,如果是非法的地址,可以考虑加防火墙,如果是正常流量,则要考虑硬件升级等。

7.中断的发生流程

假设每一个物理硬件都有一根中断线,设备可通过它对 CPU 发起中断信号,中断信号先通过中断控制器,然后发到 CPU 上执行。中断控制器:Advanced Programmable Interrupt Controller,简称 APIC。一个 APIC 包括两个独立的设备:

  • Local APIC
  • I/O APIC

Local APIC 存在于每个 CPU 核心中,Local APIC 负责处理特定于 CPU 的中断配置,常被用于管理来自 APIC 时钟(APIC-timer)、热敏元件和其他与 I/O 设备连接的设备的中断。

I/O APIC 提供了多核处理器的中断管理。它被用来在所有的 CPU 核心中分发外部中断。

中断的发生流程

  1. 外部设备给中断控制器发送物理中断信号。

  2. 中断控制器将物理中断信号转换成为中断向量 interrupt vector,发给各个 CPU。

  3. 每个 CPU 都会有一个中断向量表,根据 interrupt vector 调用一个 IRQ 处理函数。

  4. IRQ 处理函数中,将 interrupt vector 转化为抽象中断层的中断信号 irq,调用中断信号 irq 对应的中断描述结构(IDT)里面的 irq_handler_t

    IDT 表 :中断描述符表,CPU执行中断指令时,会去 IDT 表中查找对应的中断服务程序(interrupt service routine ,ISR)。

8.中断结构体

对于每一个中断,我们都有一个对应的描述结构体irq_desc,有一个重要的成员变量是 struct action *action`,用于表示处理这个中断的动作。

1
2
3
4
5
6
7
8
9
10
11
12
struct irq_desc {
struct irq_common_data irq_common_data;
struct irq_data irq_data;
unsigned int __percpu *kstat_irqs;
irq_flow_handler_t handle_irq;
......
struct irqaction *action; /* IRQ action list */
......
int parent_irq;
struct module *owner;
const char *name;
} ____cacheline_internodealigned_in_smp;

每一个中断处理动作的结构 struct irqaction,都有以下成员:

  • 中断处理函数 handler
  • 设备 id void *dev_id
  • 中断信号irq
  • 如果中断处理函数在单独的线程运行,则有 thread_fn 是线程的执行函数,thread 是线程的 task_struct

多个 irqaction 组成一个链表,对于这个中断的所有处理动作,都串在这个链表上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
* struct irqaction - per interrupt action descriptor
* @handler: interrupt handler function
* @name: name of the device
* @dev_id: cookie to identify the device
* @percpu_dev_id: cookie to identify the device
* @next: pointer to the next irqaction for shared interrupts
* @irq: interrupt number
* @flags: flags (see IRQF_* above)
* @thread_fn: interrupt handler function for threaded interrupts
* @thread: thread pointer for threaded interrupts
* @secondary: pointer to secondary irqaction (force threading)
* @thread_flags: flags related to @thread
* @thread_mask: bitmask for keeping track of @thread activity
* @dir: pointer to the proc/irq/NN/name entry
*/
struct irqaction {
irq_handler_t handler;
void *dev_id;
void __percpu *percpu_dev_id;
struct irqaction *next;
irq_handler_t thread_fn;
struct task_struct *thread;
struct irqaction *secondary;
unsigned int irq;
unsigned int flags;
unsigned long thread_flags;
unsigned long thread_mask;
const char *name;
struct proc_dir_entry *dir;
} ____cacheline_internodealigned_in_smp;

众多的中断 irq_desc 则采取类似于内存管理中所用到的基数树 radix tree 的方式进行管理。这种结构对于从某个整型 key 找到 value 速度很快,中断信号 irq 是这个整数。通过它,我们很快就能定位到对应的 irq_desc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#ifdef CONFIG_SPARSE_IRQ
static RADIX_TREE(irq_desc_tree, GFP_KERNEL);
struct irq_desc *irq_to_desc(unsigned int irq)
{
return radix_tree_lookup(&irq_desc_tree, irq);
}
#else /* !CONFIG_SPARSE_IRQ */
struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = {
[0 ... NR_IRQS-1] = {
}
};
struct irq_desc *irq_to_desc(unsigned int irq)
{
return (irq < NR_IRQS) ? irq_desc + irq : NULL;
}
#endif /* !CONFIG_SPARSE_IRQ */

Linux 内核|中断
https://www.aimtao.net/interrupt/
Posted on
2021-08-11
Licensed under