Linux 的进程信号与中断的关系
在深入学习 Linux 时,我们会接触到几个概念: 硬中断
、软中断
、进程信号
,但要搞清楚它们之间的关系并不容易。
本文将尝试通过介绍,客户端数据包如何一步步达到用户进程,阐述 中断
与 进程信号
是如何协作的。
1 什么是进程信号
进程信号
是一种特殊的 进程间通信
(IPC
,Inter-Process Communication
)机制,是一种由操作系统提供的 异步通知机制
,用来 在进程运行的任意时刻,将某些重要事件传递给它。
事件可能来自内核,如:
- 父进程在子进程退出时接收
SIGCHLD
,以便回收资源; - 非法内存访问。
也可能来自外部操作,如:
- 用户按下
Ctrl+C
,终端会向前台进程组发送SIGINT
,要求它中断执行; - 系统管理员执行
kill -HUP pid
,常用于通知守护进程重新加载配置。
收到信号的进程需要立即做出相应的处理:有些信号会终止进程,有些可以触发用户自定义的处理函数,还有一些可以被忽略。
通过信号,操作系统能够在关键时刻打断进程的正常执行,使进程具备 响应外部环境变化、实现进程间控制与协作 的能力。
2 中断
2.1 硬中断
硬中断
由硬件设备主动发起,是硬件向 CPU 传递 “紧急事件”,如数据接收完成、故障告警等的核心机制。
当硬件设备(如网卡、磁盘、键盘)完成操作或出现异常时,会通过硬件引脚向 CPU 发送中断请求信号(IRQ
, Interrupt Request
);一旦收到该信号,CPU 会立即暂停当前正在执行的任务 —— 无论此时运行的是用户态进程(如应用程序)还是内核态逻辑(如进程调度),并优先跳转至该中断对应的中断服务程序(ISR
,Interrupt Service Routine
) 执行。
由于 硬中断
的触发往往伴随硬件的 “即时需求
”,ISR
的设计遵循 “极致精简” 原则:仅完成最核心的紧急操作,不涉及复杂逻辑,以此确保执行时间极短,避免长时间占用 CPU 而阻塞其他硬件的中断请求。
2.2 软中断
硬中断
的核心要求是 “快”, 中断服务程序 ISR
仅能完成诸如 数据搬运
、状态标记
等不耗时的简单工作。而 软中断
是操作系统内核中承接硬中断 后续复杂处理 的核心机制。
软中断
的作用是:
- 内核级的异步任务队列,用于在不阻塞硬件响应的前提下,高效处理
硬中断
触发的 非紧急但耗时 的业务逻辑; - 介于
硬中断
和普通内核
或用户进程
之间,平衡了系统的响应速度与处理效率。
3 硬中断、软中断 与 信号
接下来,我们将以 UDP 数据包到达用户进程的完整流程为切入点,深入剖析硬中断、软中断以及信号这三者之间是怎样协同工作的。
3.1 硬中断处理
当用户发送的数据包到达网络接口后,网络接口会先将数据包写入内存,随后触发硬中断,过程如下时序图:
- (1) 客户端发送数据包到服务器,服务器由网卡(
NIC
,Network Interface Card
)接收数据; - (2) 网卡接收数据包后,不需经过 CPU,直接通过
DMA
(Direct Memory Access
) 把数据写到内存; - (3) 在接收到一定的数据包后,网卡通过
硬中断
(IRQ
) 通知CPU,有数据达到。我们可以通过ethtool
命令,查看网卡发起硬中断
的阈值:
其中rx-usecs: 20
,表示当收到第一个包后,网卡会延迟 最多 20 微秒 再触发中断,rx-frames: 5
,表示如果累计 5 个数据包,无论时间是否达到rx-usecs
,都会立即触发一次中断; - (4) CPU 收到
硬中断
后,会停止当前在执行的内核进程或用户进程; - (5) CPU 及时执行网卡驱动注册在内核的 ISR;
- (6) ISR 发起软中断,之后,
软中断
处理函数将进行后续的处理,至此,硬中断
完成。
3.2 软中断与信号
硬中断
发生后,后续的处理工作会由 软中断
服务程序(ISR
)来完成。在软中断服务程序处理完毕后:
- 若用户进程正因等待数据而处于阻塞状态,例如执行了
recv ()
调用,内核会直接将其唤醒,使其可以继续运行并读取数据; - 若用户进程已通过
fcntl
函数配置了O_ASYNC
, 异步 I/O 模式 和F_SETOWN
, 指定信号接收进程,则内核会发送SIGIO
信号,以此通知进程 “数据已就绪”。
处理时序图如下(图中省略了部分细节,仅为展示整体处理流程):
- (7) 软中断由
ksoftirqd
(全称 Kernel Soft IRQ Daemon)进程处理。ksoftirqd
是内核的 daemon 进程,每个CPU核对应一个ksoftirqd
进程。如下图,服务器有 8 个核,则有 8 个ksoftirqd
进程与之绑定:
由于本次软中断
由网卡驱动发起,因此ksoftirqd
会调用网络模块的net_rx_action
(rx
为receive
,接收数据的意思) 函数; - (8)
net_rx_action
会调用网卡驱动的poll
函数; - (9)
poll
函数会读取网卡写到内存中的数据包,并把数据包转换为skb
格式。skb
是 Linux 内核中用于管理网络数据包的数据结构格式,其全称为Socket Buffer
, 套接字缓冲区; - (10) 网卡驱动模块调用
napi_gro_receive
函数,skb
数据包将进入网络协议栈; - (11)
skb
逐层经过2层协议
(以太网
)、3层协议
(IP
)、4层协议
(TCP
/UDP
) 协议栈,最终匹配到对应的用户态socket
,数据写入socket
接收缓冲区; - (12) 若
用户进程
因等待数据阻塞,如recv()
调用,内核直接唤醒进程;若用户进程
通过fcntl
配置了O_ASYNC
, 异步 I/O 和F_SETOWN
,指定信号接收进程 时,内核会发送SIGIO
信号,通知进程 “数据已就绪”; - (13)
用户进程
从 Socket 缓冲区读取数据,继续业务逻辑处理。
3.3 总结整个处理流程
网卡接收数据包 -> 网卡驱动处理硬中断并触发 NET_RX_SOFTIRQ
软中断 -> ksoftirqd
检测到软中断并执行 net_rx_action
函数 -> 从 softnet_data.input_pkt_queue
取出 skb
交由 TCP/IP
协议栈逐层解析 -> 协议栈将数据写入目标 socket 接收缓冲区 -> 若用户进程阻塞于 recv ()
则内核直接唤醒进程,若配置 O_ASYNC
+F_SETOWN
则内核信号子系统向用户进程发送 SIGIO
信号。