系统调用sigaction的工作流程
系统调用 sigaction()
的工作流程本质是用户态程序通过内核接口,将信号处理规则(封装在 struct sigaction
中)注册到内核,内核根据规则管理信号的递送与处理。以下是其详细工作流程:
1. 前置条件:用户态准备信号处理配置
在调用 sigaction()
前,用户态程序需要完成:
- 定义并初始化
struct sigaction
结构体,指定信号处理的核心参数:- 选择处理函数(
_sa_handler
简单函数 或_sa_sigaction
扩展函数); - 设置信号屏蔽集
sa_mask
(处理期间需要屏蔽的信号); - 配置标志位
sa_flags
(如SA_SIGINFO
、SA_RESTART
等)。
- 选择处理函数(
- 确定目标信号(如
SIGINT
、SIGTERM
等)。
2. 触发系统调用:从用户态进入内核态
用户态程序调用 sigaction()
函数( glibc 提供的封装函数),触发以下操作:
- 参数校验:检查信号编号是否合法(如是否为
SIGKILL
或SIGSTOP
,这两个信号无法自定义处理)、struct sigaction
指针是否有效。 - 陷入内核:通过系统调用指令(如 x86 的
syscall
)从用户态切换到内核态,内核会保存用户态上下文(如寄存器、栈指针)。
3. 内核处理:注册或查询信号处理规则
内核接收到 sigaction()
系统调用请求后,进入内核函数 sys_sigaction()
(不同架构实现可能有差异),执行核心逻辑:
(1)查找信号对应的内核数据结构
内核为每个进程维护一个 struct task_struct
结构体(进程描述符),其中包含信号处理的关键信息:
struct k_sigaction sigaction[NSIG]
:数组,每个元素对应一个信号的处理规则(内核态的信号配置,与用户态struct sigaction
对应)。- 内核根据传入的
signum
(信号编号),找到该进程sigaction
数组中对应的条目。
(2)处理“设置新规则”(act
参数非 NULL 时)
若用户态传入了新的 struct sigaction
(act
非 NULL),内核会:
- 校验处理函数合法性:
- 若使用
_sa_sigaction
扩展函数,需检查sa_flags
是否包含SA_SIGINFO
标志,否则返回错误。 - 验证处理函数指针是否指向用户空间(避免恶意程序传入内核空间地址)。
- 若使用
- 转换用户态配置到内核态:
将用户态struct sigaction
中的信息(处理函数、sa_mask
、sa_flags
)拷贝到内核态的struct k_sigaction
中(需处理指针地址转换,因为用户态与内核态地址空间独立)。 - 特殊标志处理:
例如,若设置SA_RESETHAND
标志,内核会记录“信号处理后恢复默认行为”;若设置SA_NODEFER
,则取消对当前信号的自动屏蔽。
(3)处理“保存旧规则”(oldact
参数非 NULL 时)
若用户态传入了 oldact
指针(用于保存旧配置),内核会:
- 将该信号当前的内核态配置(
struct k_sigaction
)转换为用户态的struct sigaction
格式。 - 拷贝到用户态的
oldact
指针指向的内存中(需确保用户态内存可写)。
4. 内核返回:从内核态回到用户态
- 清理与恢复:内核完成信号配置后,恢复用户态上下文(寄存器、栈等),从系统调用返回。
- 返回值处理:若成功,返回 0;若失败(如信号不合法、参数错误),返回 -1 并设置
errno
(如EINVAL
表示信号无效)。
5. 后续:信号触发时的处理流程
sigaction()
调用完成后,内核已记录该信号的处理规则。当信号触发时(如用户按 Ctrl+C 产生 SIGINT
),内核会:
- 检查该信号是否被屏蔽(通过进程的信号屏蔽集
blocked
),若未屏蔽则准备递送。 - 根据
sigaction
数组中记录的规则:- 若处理函数是用户自定义函数,内核会创建临时栈帧,切换到用户态执行该函数。
- 执行期间,自动屏蔽
sa_mask
中指定的信号,避免干扰。 - 若设置了
SA_RESTART
,函数执行后会重启被信号中断的系统调用(如read
、sleep
)。
总结
sigaction()
系统调用的核心流程是:
用户态准备配置 → 陷入内核 → 内核校验并更新信号处理规则 → 返回用户态。
其本质是用户程序向内核“注册”信号处理逻辑,后续信号的触发与处理则完全由内核根据注册的规则执行,体现了 Linux 中“用户态定义规则,内核负责执行”的信号处理模型。