聊聊中断与操作系统

532 阅读9分钟

我正在参加「掘金·启航计划」

1、什么是中断

中断(interrupt)是系统用来响应硬件设备请求的一种机制。

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

2、中断的好处

中断属于主动通知的机制,与之类似作用的就是轮询机制,但是效率较低。

中断是一种异步的事件处理机制,可以提高系统的并发处理能力:

  • 在硬件设备处理数据期间,内核可以调度CPU做其他事情
  • 等到硬件设备准备好数据,就触发中断告诉内核,内核就调度CPU去处理硬件的数据

3、中断的实现逻辑

中断分为硬中断和软中断,区别就是发起者不同。

并且中断也有很多种,每种中断有一个唯一的中断号,对应一个中断处理函数

内核维护了一张中断向量表(Interrupt Vector Table),记录了中断号与中断处理函数内存地址的对应关系

这些中断和中断处理函数都是内核提供的,中断处理函数一般会进行较重要的操作,需要内核态。

4、中断的缺陷

硬中断会暂停正在运行的进程,CPU转而执行中断处理函数。

这个中断处理函数不能执行太久,否则CPU一直处理各种中断,严重影响普通进程的运行。

5、硬中断与软中断

1、硬中断

硬中断由外部设备产生,比如磁盘、网卡、键盘、时钟等,用来通知操作系统外部设备有变化。

硬中断的处理流程:

  • 外部设备将中断请求发送给中断控制器
  • 中断控制器根据中断的优先级,有序地将中断发给CPU
  • CPU接收到中断,终止当前执行的线程,保存现场,将寄存器的所有值保存到栈中
  • CPU根据中断号,从中断向量表中查找到对应的中断处理函数的地址,执行中断处理函数
  • 执行完毕,CPU从栈中恢复寄存器的值,继续执行刚才的线程

其中,识别出对应的软中断处理函数之前的操作有专门的硬中断处理电路来完成。

2、软中断

1、概述

软中断是一条CPU指令,由当前正在运行的进程主动发出。

构成软中断机制的核心元素包括:

  • 软中断状态寄存器,表示软中断的类型
  • 软中断向量表,建立软中断类型和软中断处理函数的映射关系
  • 软中断守护 daemon 线程(ksoftirqd 线程),用来执行软中断处理函数

软中断的工作工程模拟了实际的中断处理过程:

  • CPU接收到中断,设置对应的软中断标记位,终止当前执行的线程,保存现场,将寄存器的所有值保存到栈中
  • 唤醒守护线程去检测中断状态寄存器,根据软中断类型,从软中断向量表中查找到对应的中断处理函数的地址,执行中断处理函数
  • 执行完毕,CPU从栈中恢复寄存器的值,继续执行刚才的线程
2、Linux中的软中断

在Linux中最多可以注册32个软中断,目前系统用了6个软中断,分别为:定时器处理、SCSI处理、网络收发处理以及Tasklet机制

这里的tasklet机制就是用来实现“中断下半部”的。

1、软中断守护线程 ksoftirqd

软中断守护daemon线程是软中断机制的实现核心。

它会不断轮询一组软中断状态来判断事件是否发生,如果发生,那么映射到软中断向量表,调用执行注册的中断处理action函数

软中断的处理函数就是在软中断守护daemon线程上执行的,即 ksoftirqd 线程。

每一个 CPU 都对应一个软中断内核线程,名字通常为「ksoftirqd/CPU 编号」,比如 0 号 CPU 对应的软中断内核线程的名字是 ksoftirqd/0。

这样的好处是,每个CPU核心对应一个软中断线程,就不需要考虑线程安全问题。

2、触发软中断事务

触发软中断事务通过raise_softirq()函数来实现。

这个函数就是在中断关闭的情况下设置软中断状态位,然后判断如果不在中断上下文,那么直接唤醒守护daemon。

3、软中断的特点
  • 软中断是进程控制CPU自行发出的,所以直达CPU,不会经过中断控制器
  • 一个软中断不会去抢占另一个软中断,只有硬件中断才可以抢占软中断,所以软中断能够保证对时间的严格要求

3、软中断与硬中断的处理区别

从中断号到中断处理函数的映射过程不同:

  • 在CPU的硬件中断发生之后,CPU需要将硬件中断请求通过中断向量表映射成具体的服务程序,这个过程是硬件自动完成的
  • 但是软中断需要守护线程去实现这一过程,这也就是软件模拟的中断,故称之为软中断。

执行方式不同:

  • 硬中断会打断 CPU 正在执行的任务,然后立即执行中断处理程序
  • 软中断是以内核线程的方式执行

6、硬中断处理的两个阶段

1、中断的丢失问题

中断处理程序在响应中断时,会临时屏蔽中断。这就存在一个问题:

当前中断处理程序执行时,系统中其他的中断请求都无法被响应,也就说中断有可能会丢失。

所以中断处理程序要短且快。

2、两阶段处理中断

Linux 系统为了解决中断处理程序执行过长导致中断丢失的问题,将中断过程分成了两个阶段:

  • 中断上半部:处理硬中断,期间暂时屏蔽中断,快速执行一些耗时短的操作,然后将需要执行的中断处理函数地址放入Tasklet任务列表中
  • 中断下半部:内核发起内核线程软中断daemon,执行硬中断阶段指定的中断处理函数,期间内核能够正常接收硬中断

这样的好处是,内核只需要在接收中断请求时屏蔽其他中断,然后自行进行中断处理函数的工作,此时又能继续接收新的中断请求了。

这种做法,在Windows中称为“中断延迟处理”,在Linux中称为“中断下半部”。

在Linux中,中断下半部的实现基于软中断机制,称为“Tasklet机制”。

3、详解 Tasklet 机制

Tasklet是一个软中断,考虑到优先级问题,分别占用了向量表中的0号和5号软中断。

当CPU收到硬中断后,硬中断处理函数会构建一个tasklet_struct结构,指明下半部在处理这个中断时需要调用哪个具体的函数。

通过 tasklet_schedule函数,将这个tasklet_struct挂入tasklet的tasklet_struct链表中。

之后硬中断处理函数就退出了,CPU设置软中断标记位,开始调度软中断daemon线程。

软中断daemon发现tasklet发生了事件,其会执行它的软中断处理函数,这个处理函数会扫描tasklet的任务列表tasklet_struct链表,执行硬中断阶段指定的具体处理函数。

注意:

  • 中断下半部的软中断获取中断处理函数的方式是扫描任务列表,而并不会去查询中断向量表。

7、定位软中断CPU使用率过高的问题

1、估算软中断的开销

查看软中断总耗时

首先用top命令可以看出每个核上软中断的开销占比,其中si是软中断,hi是硬中断,比例的含义是CPU耗费了百分之多少的时钟周期。

image-20220902160534585

如上图所示,CPU大约花费了1.2%的时钟周期在软中断上,也就是说每个核要花费12ms。

查看软中断次数

再用vmstat命令可以看到软中断的次数

image-20220902160721116

每秒大约有56000次左右的软中断

计算每次软中断的耗时

该机器是16核的物理实机,故可以得出每个软中断需要的CPU时间是=12ms/(56000/16)次=3.428us

2、解决方案

如果发现软中断很耗时,就分析是哪种软中断类型导致的

可以通过查看 /proc/softirqs 的 内容来知晓软中断的运行情况:

img

每一个 CPU 都有自己对应的不同类型软中断的累计运行次数。

  • 第一列的内容代表着软中断的类型,比如 NET_RX 表示网络接收中断,NET_TX 表示网络发送中断、TIMER 表示定时中断、RCU 表示 RCU 锁中断、SCHED 表示内核调度中断。
  • 要注意同一种类型的软中断在不同 CPU 的分布情况,正常情况下,同一种中断在不同 CPU 上的累计次数相差不多
  • 这些数值是系统运行以来的累计中断次数,数值的大小没什么参考意义,但是系统的中断次数的变化速率才是我们要关注的,我们可以使用 watch -d cat /proc/softirqs 命令查看中断次数的变化速率

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

如果发现 NET_RX 网络接收中断次数的变化速率过快,接下里就可以使用 sar -n DEV 查看网卡的网络包接收速率情况,然后分析是哪个网卡有大量的网络包进来。

img

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