在 Linux 内核中,信号(Signal)是进程间通信(IPC)中最古老、最基本的异步通知机制。不同于文件系统通过 file_struct 管理文件描述符,或网络栈通过 socket 组织数据,信号机制紧密耦合在进程控制块 task_struct 之中,是进程调度和执行流中不可或缺的一部分。
本文将从底层数据结构出发,剖析信号的组织形式、屏蔽逻辑以及核心 API 的底层交互。
一、 内核数据结构:信号的存储基础
信号并非独立的模块,其状态直接维护在每个进程的 task_struct 中(位于 include/linux/sched.h)。
1. 核心成员变量
sigset_t blocked:信号屏蔽字(Mask)。一个位图(Bitmap),记录当前进程拒绝立即处理的信号集合。
struct sigpending pending:私有未决信号集。记录已到达但尚未被处理的信号。
struct signal_struct *signal:共享信号集。用于处理发送给整个线程组的信号。
struct sighand_struct *sighand:信号处理函数表。包含一个 action[] 数组,每个元素对应一个信号的具体处理策略(struct k_sigaction)。
2. 未决信号的组织:位图与链表
信号的存储采用了位图 + 双向链表的混合结构:
二、 核心配置结构:struct sigaction 详解
struct sigaction 是用户态程序与内核交互、定义信号处理行为的核心载体。当调用 sigaction() 系统调用时,内核会将此结构体的信息拷贝至 task_struct->sighand->action[] 中。
1. 结构体定义
struct sigaction { void (*sa_handler)(int); // 传统处理函数 void (*sa_sigaction)(int, siginfo_t *, void *); // 增强版处理函数 sigset_t sa_mask; // 处理该信号时的临时屏蔽集 int sa_flags; // 行为标志位 void (*sa_restorer)(void); // 内部使用(通常忽略)};
2. 核心成员功能
三、 核心函数及其对内核状态的影响
理解信号函数的关键在于观察它们如何操作 task_struct 中的成员。
1. sigaction:策略配置
2. sigprocmask:全局开关
3. sigsuspend:原子同步
| 函数 | 操作的底层本质 | 影响范围 |
|---|
kill / raise | 点亮目标进程的 pending 位图,挂载 sigqueue | 产生信号,触发内核检查逻辑 |
sigprocmask | 直接修改 task_struct->blocked 位图 | 长期控制进程对特定信号的“屏蔽”状态 |
sigsuspend | 原子性临时替换 blocked 并进入休眠 | 解决“解除屏蔽”与“进入睡眠”之间的竞态条件 |
四、 信号的处理流程:内核态返回用户态的瞬间
信号的检测与处理并非实时发生,而是存在于状态切换的节点。
产生(Generation):kill() 系统调用触发内核搜索目标 PID。内核点亮目标进程 pending 位图,并设置 TIF_SIGPENDING 标志位。
检测(Detection):当系统调用、中断或异常处理结束,CPU 准备从内核态返回用户态之前,会检查 TIF_SIGPENDING 标志。
递送(Delivery):内核计算 pending & ~blocked。若有有效信号,内核会在用户态栈上构建信号栈帧(Signal Frame),并将 EIP/RIP 指向用户定义的 sa_handler 地址。
返回(Return):sa_handler 执行完毕后调用 sigreturn 系统调用再次进入内核,内核清理栈帧并恢复进程原本的执行上下文。
五、 综合实践:信号控制全流程示例
以下 C 程序演示了如何完整地应用上述机制:配置处理策略、利用掩码保护临界区、以及使用 sigsuspend 进行安全等待。
#include<stdio.h>#include<stdlib.h>#include<signal.h>#include<unistd.h>/** * 信号处理回调 */voidhandle_sigint(int sig){ printf("\n[Handler] 捕获信号 %d (SIGINT),准备退出等待。\n", sig);}intmain(){ struct sigaction sa; sigset_t new_mask, old_mask, wait_mask; // 1. 配置信号处理动作 (sigaction) sa.sa_handler = handle_sigint; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; if (sigaction(SIGINT, &sa, NULL) == -1) { perror("sigaction"); exit(1); } // 2. 阻塞信号以保护临界区 (sigprocmask) sigemptyset(&new_mask); sigaddset(&new_mask, SIGINT); // 将 SIGINT 加入阻塞集,防止在处理关键逻辑时被中断 sigprocmask(SIG_BLOCK, &new_mask, &old_mask); printf("[Main] 关键逻辑开始,SIGINT 已被屏蔽。此时 Ctrl+C 不会响应。\n"); for (int i = 1; i <= 3; i++) { sleep(1); printf("...执行中 (%d/3)\n", i); } // 3. 原子性等待信号 (sigsuspend) printf("[Main] 进入 sigsuspend 等待。临时解除屏蔽,等待 SIGINT...\n"); sigemptyset(&wait_mask); // 不屏蔽任何信号 /* * sigsuspend 会完成以下原子操作: * (1) 设置当前屏蔽字为 wait_mask * (2) 挂起进程直到信号到达 * (3) 信号处理完后返回,并恢复屏蔽字为 new_mask */ sigsuspend(&wait_mask); // 4. 恢复初始状态 sigprocmask(SIG_SETMASK, &old_mask, NULL); printf("[Main] 信号处理完毕,恢复原始屏蔽配置,程序正常退出。\n"); return 0;}