Linux 进程间通信(IPC)之信号
文章目录
信号(Signal)是 Linux 系统中最基础、最核心的进程间通信(IPC)机制之一,本质是内核向进程发送的软中断,用于实现异步事件通知。本文将从 IPC 通信特点与进程 / 线程对比入手,深入讲解信号的核心 API、使用场景,并通过多个可运行的实战示例,帮助你彻底掌握 Linux 信号的使用方式。
一、详解 IPC 通信特点,进程与线程的核心对比
要理解信号的设计初衷和使用场景,首先需要明确进程间通信的本质,以及进程与线程在通信维度的核心差异。
1.1 进程间通信(IPC)的核心概念与特点
进程间通信(Inter-Process Communication,IPC)是指两个或多个独立进程之间交换数据、传递信息、同步行为的机制。Linux 系统中,IPC 机制包括信号、管道、消息队列、共享内存、套接字等,而信号是其中最轻量、最异步的一种。
IPC 的核心特点可总结为以下 6 点:
-
进程独立性:每个进程拥有独立的虚拟地址空间,用户态下无法直接访问其他进程的内存,因此所有 IPC 机制都需要内核介入(内核作为中间层完成数据转发或事件通知)。
-
通信异步性:信号是典型的异步 IPC 机制 —— 进程无需主动轮询事件,信号到达时内核会中断进程的正常执行流程,触发预设的处理逻辑;而管道、共享内存等则偏向同步通信(进程需主动读写)。
-
数据传输能力
:不同 IPC 机制的传输能力差异显著:
- 信号:仅传递 “事件标识”(整数),无法携带自定义数据(实时信号可携带少量数据,但非核心能力);
- 管道 / 套接字:可传输流式数据,但有内核拷贝开销;
- 共享内存:可传输大量数据,无内核拷贝,但需手动处理同步互斥。
-
同步与互斥:IPC 需解决多进程竞态问题(如同时读写共享资源),但信号本身不提供同步互斥能力,需配合锁、信号量等机制使用。
-
开销与效率:信号的内核开销极低(仅传递一个整数),但功能简单;共享内存效率最高,但开发复杂度高;管道 / 套接字居中。
-
可靠性
:信号分为
不可靠信号
(1-31 号,非实时信号)和
可靠信号
(34-64 号,实时信号):
- 不可靠信号:不排队,若同一信号多次触发且未处理,可能丢失;
- 可靠信号:排队机制,确保所有信号都能被递达,不会丢失。
1.2 进程与线程的核心对比
进程和线程是操作系统中两个核心的执行单元,二者的设计目标不同,导致通信方式和特性差异巨大,具体对比如下表:
表格
| 维度 | 进程通信 | 线程通信 |
|---|---|---|
| 地址空间 | 完全独立,必须通过 IPC 机制通信 | 共享进程地址空间,可直接访问全局变量 |
| 通信方式 | 信号、管道、共享内存、套接字等 | 全局变量、互斥锁、条件变量、信号量等 |
| 通信开销 | 高(需内核介入,有上下文切换) | 低(用户态即可完成,无内核拷贝) |
| 数据安全性 | 天然隔离,数据泄露风险低 | 共享数据易出现竞态,需同步机制保护 |
| 异步通知 | 信号是主要实现方式 | 可通过条件变量、信号量模拟异步通知 |
| 错误影响范围 | 单个进程崩溃不影响其他进程 | 线程崩溃可能导致整个进程退出 |
| 信号作用范围 | 信号默认发给进程,所有线程共享处理函数 | 可通过pthread_kill发送给指定线程 |
关键补充:
- 进程是 “资源分配的基本单位”,线程是 “CPU 调度的基本单位”;
- 线程间通信无需内核介入(直接访问共享内存),但需处理同步问题;
- 信号是少数能跨进程、跨线程的通用通知机制,也是进程异常处理的核心手段(如段错误触发 SIGSEGV 信号)。
二、Linux 信号 API 与典型使用场景
信号的生命周期分为四个阶段:产生(生成)→ 注册(未决)→ 递送(触发)→ 处理。Linux 提供了一套完整的 API 来操作信号的全生命周期,下面逐一讲解核心 API 及使用场景。
2.1 信号的基础认知
2.1.1 信号的本质与默认动作
信号是内核向进程发送的 “异步事件通知”,常见的信号来源包括:
- 用户操作:如
Ctrl+C触发 SIGINT(2 号)、Ctrl+\触发 SIGQUIT(3 号); - 硬件异常:如段错误触发 SIGSEGV(11 号)、浮点错误触发 SIGFPE(8 号);
- 进程主动发送:如
kill命令、kill()函数发送信号; - 内核事件:如子进程退出触发 SIGCHLD(17 号)、定时器到期触发 SIGALRM(14 号)。
每个信号都有预设的默认动作,核心类型包括:
Term:终止进程(如 SIGINT、SIGTERM(15 号));Core:终止进程并生成 core dump 文件(如 SIGSEGV、SIGABRT(6 号));Ign:忽略信号(如 SIGCHLD 默认忽略);Stop:暂停进程(如 SIGSTOP(19 号),不可捕获 / 忽略);Cont:恢复暂停的进程(如 SIGCONT(18 号))。
2.1.2 信号的分类
| 类型 | 编号范围 | 特点 | 典型信号 |
|---|---|---|---|
| 不可靠信号 | 1-31 | 不排队、可能丢失、无参数 | SIGINT、SIGTERM |
| 可靠信号 | 34-64 | 排队、不丢失、可带参数 | SIGRTMIN+0~30 |
2.2 核心信号 API 详解
2.2.1 信号处理函数注册
信号的核心操作是 “自定义处理逻辑”,Linux 提供了两个核心函数:signal()(简单但不健壮)和sigaction()(推荐、POSIX 标准)。
(1)signal ():简易注册函数
函数原型:
#include <signal.h>
// 定义信号处理函数类型
typedef void (*sighandler_t)(int);
// 注册信号处理函数
sighandler_t signal(int signum, sighandler_t handler);
参数说明:
-
signum:要处理的信号编号(如SIGINT=2); -
handler:处理逻辑,可选值:
SIG_IGN:忽略该信号;SIG_DFL:恢复默认动作;- 自定义函数指针:参数为信号编号,无返回值。
返回值:成功返回旧的处理函数指针,失败返回SIG_ERR。
缺点:
- 跨平台实现不一致(不同系统处理逻辑有差异);
- 信号处理函数执行后,部分系统会恢复为默认动作;
- 无法设置信号屏蔽集、无法获取信号详细信息。
(2)sigaction ():健壮注册函数(推荐)
函数原型:
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
核心结构体struct sigaction:
struct sigaction {
// 普通处理函数(同signal())
void (*sa_handler)(int);
// 高级处理函数(带信号详细信息)
void (*sa_sigaction)(int, siginfo_t *, void *);
// 处理信号时屏蔽的信号集
sigset_t sa_mask;
// 标志位(控制信号处理行为)
int sa_flags;
// 已废弃,无需关注
void (*sa_restorer)(void);
};
参数说明:
-
act:新的信号处理配置(NULL 表示不修改); -
oldact:输出旧的配置(NULL 表示不获取); -
sa_flags:核心标志位,常用值:
SA_RESTART:系统调用被信号中断后自动重启;SA_SIGINFO:启用sa_sigaction(高级处理函数);SA_NODEFER:处理信号时不屏蔽自身(默认会屏蔽)。
返回值:成功返回 0,失败返回 - 1。
优点:跨平台兼容、可配置屏蔽集、可获取信号详细信息(如发送方 PID)。
2.2.2 信号发送函数
用于主动向进程 / 进程组发送信号,核心函数包括kill()、raise()、abort()、alarm()。
(1)kill ():向指定进程发送信号
函数原型:
#include <signal.h>
int kill(pid_t pid, int sig);
参数说明:
-
pid:目标进程 / 进程组 ID:
pid > 0:发送给 PID 为pid的进程;pid = 0:发送给当前进程组的所有进程;pid = -1:发送给所有有权限的进程;pid < -1:发送给进程组 ID 为-pid的所有进程;
-
sig:要发送的信号(sig=0时仅检测进程是否存在,不发送信号)。
返回值:成功返回 0,失败返回 - 1。
(2)raise ():向当前进程发送信号
等价于kill(getpid(), sig),原型:
#include <signal.h>
int raise(int sig);
(3)abort ():强制终止当前进程
向当前进程发送SIGABRT(6 号)信号,强制终止并生成 core dump 文件(无法忽略),原型:
#include <stdlib.h>
void abort(void);
(4)alarm ():设置定时器(触发 SIGALRM)
设置秒级定时器,到期后内核向当前进程发送SIGALRM(14 号)信号,原型:
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
参数:seconds=0表示取消定时器;
返回值:之前未到期的定时器剩余秒数。
2.2.3 信号集操作函数
信号集(sigset_t)是表示多个信号的集合,用于屏蔽信号、查询未决信号,核心操作函数:
#include <signal.h>
// 清空信号集
int sigemptyset(sigset_t *set);
// 填充所有信号到集合
int sigfillset(sigset_t *set);
// 添加指定信号到集合
int sigaddset(sigset_t *set, int signum);
// 从集合删除指定信号
int sigdelset(sigset_t *set, int signum);
// 判断信号是否在集合中(1=是,0=否)
int sigismember(const sigset_t *set, int signum);
2.2.4 信号屏蔽与未决信号函数
(1)sigprocmask ():设置进程信号屏蔽字
屏蔽指定信号(使其进入 “未决” 状态,暂时不处理),原型:
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
参数how:
SIG_BLOCK:将set中的信号添加到屏蔽集;SIG_UNBLOCK:从屏蔽集删除set中的信号;SIG_SETMASK:用set替换当前屏蔽集。
(2)sigpending ():获取未决信号集
查询当前被屏蔽、尚未处理的信号,原型:
#include <signal.h>
int sigpending(sigset_t *set);
2.2.5 信号等待函数
(1)pause ():挂起进程等待信号
暂停进程执行,直到收到一个信号并处理,原型:
#include <unistd.h>
int pause(void);
返回值:始终返回 - 1,errno=EINTR(被信号中断)。
(2)sigsuspend ():原子操作等待信号
解决pause()的竞态问题(先屏蔽信号再挂起),原型:
#include <signal.h>
int sigsuspend(const sigset_t *mask);
2.3 信号的典型使用场景
- 进程控制:终止 / 暂停 / 恢复进程(如
kill -9 PID发送 SIGKILL 终止进程、kill -STOP PID暂停进程); - 异常处理:捕获程序运行异常(如 SIGSEGV 段错误、SIGFPE 浮点错误),执行资源清理、日志记录;
- 超时控制:通过
alarm()+SIGALRM实现函数 / 网络请求超时终止; - 异步通知:进程间异步传递事件(如子进程退出触发 SIGCHLD,父进程捕获后回收僵尸进程);
- 终端交互:响应终端操作(如
Ctrl+C触发 SIGINT,终止前台进程); - 实时事件通知:实时信号(34+)用于工业控制、嵌入式场景,确保通知不丢失。
三、信号实战示例与深度解析
下面通过 5 个递进式示例,从基础到综合,讲解信号的实际使用方式,所有代码均可直接编译运行。
3.1 示例 1:基础信号捕获(signal ())
功能:捕获Ctrl+C触发的 SIGINT 信号,修改默认 “终止进程” 动作,改为自定义清理后退出。
完整代码(sigint_demo.c):
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
// 自定义SIGINT信号处理函数
void sigint_handler(int signum) {
// signum为触发的信号编号(SIGINT=2)
printf("\n[处理函数] 捕获到信号:%d(SIGINT)\n", signum);
// 模拟资源清理:关闭文件、释放内存等
printf("[处理函数] 执行清理操作:关闭日志文件、释放网络连接...\n");
// 主动退出进程
exit(EXIT_SUCCESS);
}
int main() {
// 注册SIGINT信号的处理函数
if (signal(SIGINT, sigint_handler) == SIG_ERR) {
perror("signal注册失败"); // 打印错误原因
exit(EXIT_FAILURE);
}
// 打印进程PID,方便调试
printf("程序启动,PID:%d\n", getpid());
printf("按下Ctrl+C触发SIGINT信号...\n");
// 无限循环等待信号(避免进程退出)
while (1) {
printf("程序正常运行中...\n");
sleep(1); // 每秒打印一次,避免CPU占用过高
}
return 0;
}
代码解析:
- 头文件:
signal.h(信号 API)、unistd.h(sleep()/getpid())、stdlib.h(exit()); - 处理函数:
sigint_handler接收信号编号,执行清理逻辑后退出; - 信号注册:
signal(SIGINT, sigint_handler)替换 SIGINT 的默认动作; - 主循环:
while(1)保持进程运行,sleep(1)降低 CPU 占用。
运行步骤:
# 编译
gcc sigint_demo.c -o sigint_demo
# 运行
./sigint_demo
输出结果:
程序启动,PID:12345
按下Ctrl+C触发SIGINT信号...
程序正常运行中...
程序正常运行中...
^C(按下Ctrl+C)
[处理函数] 捕获到信号:2(SIGINT)
[处理函数] 执行清理操作:关闭日志文件、释放网络连接...
3.2 示例 2:健壮信号处理(sigaction ())
功能:使用sigaction()注册 SIGUSR1 信号,配置屏蔽集、获取信号详细信息(发送方 PID)。
完整代码(sigaction_demo.c):
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
// 高级信号处理函数(带详细信息)
void sigusr1_handler(int signum, siginfo_t *info, void *context) {
printf("\n=== 捕获到SIGUSR1信号 ===\n");
printf("信号编号:%d\n", signum); // 信号编号(SIGUSR1=10)
printf("发送进程PID:%d\n", info->si_pid); // 发送方进程PID
printf("信号携带值:%d\n", info->si_value.sival_int); // 信号携带的自定义值
printf("=========================\n");
}
int main() {
struct sigaction act;
// 清空结构体(避免随机内存值导致异常)
memset(&act, 0, sizeof(act));
// 配置高级处理函数
act.sa_sigaction = sigusr1_handler;
// 标志位:启用sa_sigaction + 系统调用重启
act.sa_flags = SA_SIGINFO | SA_RESTART;
// 初始化屏蔽集:处理SIGUSR1时屏蔽SIGINT(Ctrl+C)
sigemptyset(&act.sa_mask);
sigaddset(&act.sa_mask, SIGINT);
// 注册SIGUSR1信号
if (sigaction(SIGUSR1, &act, NULL) == -1) {
perror("sigaction注册失败");
exit(EXIT_FAILURE);
}
printf("程序启动,PID:%d\n", getpid());
printf("测试方式:新开终端执行 kill -USR1 %d\n", getpid());
// 循环等待信号
while (1) {
printf("程序运行中...\n");
sleep(2);
}
return 0;
}
代码解析:
- 高级处理函数:
sigusr1_handler接收 3 个参数,siginfo_t *info包含信号详细信息; - 结构体初始化:
memset清空struct sigaction,避免野值; - 屏蔽集配置:处理 SIGUSR1 时,屏蔽 SIGINT(此时按 Ctrl+C 无反应);
- 标志位:
SA_SIGINFO启用高级处理函数,SA_RESTART保证系统调用不被中断。
运行步骤:
# 编译
gcc sigaction_demo.c -o sigaction_demo
# 运行
./sigaction_demo
# 新开终端发送信号
kill -USR1 12346(替换为实际PID)
输出结果:
程序启动,PID:12346
测试方式:新开终端执行 kill -USR1 12346
程序运行中...
程序运行中...
=== 捕获到SIGUSR1信号 ===
信号编号:10
发送进程PID:12347(发送方终端的bash PID)
信号携带值:0
=========================
程序运行中...
3.3 示例 3:父子进程间信号通信
功能:父进程创建子进程,子进程向父进程发送 SIGUSR1,父进程回复 SIGUSR2,子进程收到后退出。
完整代码(parent_child_sig.c):
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <string.h>
// 全局变量:存储父子进程PID
pid_t child_pid = 0;
pid_t parent_pid = 0;
// 父进程处理SIGUSR1的函数
void parent_sigusr1_handler(int signum) {
printf("[父进程 %d] 收到子进程 %d 的SIGUSR1信号\n", parent_pid, child_pid);
// 向子进程发送SIGUSR2信号
if (kill(child_pid, SIGUSR2) == -1) {
perror("kill发送SIGUSR2失败");
exit(EXIT_FAILURE);
}
printf("[父进程 %d] 已向子进程 %d 发送SIGUSR2信号\n", parent_pid, child_pid);
}
// 子进程处理SIGUSR2的函数
void child_sigusr2_handler(int signum) {
printf("[子进程 %d] 收到父进程 %d 的SIGUSR2信号,准备退出\n", getpid(), parent_pid);
exit(EXIT_SUCCESS);
}
int main() {
parent_pid = getpid();
printf("[父进程 %d] 启动,准备创建子进程\n", parent_pid);
// 创建子进程
child_pid = fork();
if (child_pid == -1) {
perror("fork失败");
exit(EXIT_FAILURE);
}
if (child_pid == 0) {
// 子进程逻辑
printf("[子进程 %d] 启动,父进程PID:%d\n", getpid(), parent_pid);
// 注册SIGUSR2处理函数
struct sigaction act;
memset(&act, 0, sizeof(act));
act.sa_handler = child_sigusr2_handler;
act.sa_flags = SA_RESTART;
if (sigaction(SIGUSR2, &act, NULL) == -1) {
perror("子进程sigaction注册失败");
exit(EXIT_FAILURE);
}
// 向父进程发送SIGUSR1信号
printf("[子进程 %d] 向父进程 %d 发送SIGUSR1信号\n", getpid(), parent_pid);
if (kill(parent_pid, SIGUSR1) == -1) {
perror("kill发送SIGUSR1失败");
exit(EXIT_FAILURE);
}
// 等待父进程回复
while (1) {
printf("[子进程 %d] 等待父进程回复...\n", getpid());
sleep(1);
}
} else {
// 父进程逻辑
// 注册SIGUSR1处理函数
struct sigaction act;
memset(&act, 0, sizeof(act));
act.sa_handler = parent_sigusr1_handler;
act.sa_flags = SA_RESTART;
if (sigaction(SIGUSR1, &act, NULL) == -1) {
perror("父进程sigaction注册失败");
exit(EXIT_FAILURE);
}
// 等待子进程退出(避免僵尸进程)
printf("[父进程 %d] 等待子进程退出...\n", parent_pid);
waitpid(child_pid, NULL, 0);
printf("[父进程 %d] 子进程已退出,父进程结束\n", parent_pid);
}
return 0;
}
代码解析:
- fork () 创建子进程:子进程返回 0,父进程返回子进程 PID;
- 子进程逻辑:注册 SIGUSR2 处理函数,向父进程发送 SIGUSR1,等待回复;
- 父进程逻辑:注册 SIGUSR1 处理函数,收到信号后回复 SIGUSR2,通过
waitpid()回收子进程; - 全局变量:存储父子 PID,方便互相发送信号。
运行步骤:
gcc parent_child_sig.c -o parent_child_sig
./parent_child_sig
输出结果:
[父进程 12348] 启动,准备创建子进程
[父进程 12348] 等待子进程退出...
[子进程 12349] 启动,父进程PID:12348
[子进程 12349] 向父进程 12348 发送SIGUSR1信号
[父进程 12348] 收到子进程 12349 的SIGUSR1信号
[父进程 12348] 已向子进程 12349 发送SIGUSR2信号
[子进程 12349] 收到父进程 12348 的SIGUSR2信号,准备退出
[父进程 12348] 子进程已退出,父进程结束
3.4 示例 4:信号屏蔽与未决信号
功能:屏蔽 SIGINT 信号,触发后查看未决信号,解除屏蔽后处理。
完整代码(sig_mask_pending.c):
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
// SIGINT处理函数
void sigint_handler(int signum) {
printf("\n[处理函数] 捕获到SIGINT信号(%d)\n", signum);
}
// 打印未决信号集
void print_pending_signals() {
sigset_t pending_set;
// 获取未决信号集
if (sigpending(&pending_set) == -1) {
perror("sigpending失败");
exit(EXIT_FAILURE);
}
printf("当前未决信号:");
// 遍历1-31号信号,检查是否未决
for (int i = 1; i <= 31; i++) {
if (sigismember(&pending_set, i)) {
printf("%d ", i);
}
}
printf("\n");
}
int main() {
sigset_t mask_set;
// 注册SIGINT处理函数
if (signal(SIGINT, sigint_handler) == SIG_ERR) {
perror("signal注册失败");
exit(EXIT_FAILURE);
}
// 初始化屏蔽集,添加SIGINT
sigemptyset(&mask_set);
sigaddset(&mask_set, SIGINT);
// 屏蔽SIGINT信号
printf("[主进程] 屏蔽SIGINT信号...\n");
if (sigprocmask(SIG_BLOCK, &mask_set, NULL) == -1) {
perror("sigprocmask屏蔽失败");
exit(EXIT_FAILURE);
}
printf("[主进程] PID:%d,5秒内按下Ctrl+C触发SIGINT...\n", getpid());
sleep(5); // 5秒内按Ctrl+C,信号进入未决状态
// 查看未决信号
print_pending_signals();
// 解除SIGINT屏蔽
printf("[主进程] 解除SIGINT屏蔽,信号将被处理...\n");
if (sigprocmask(SIG_UNBLOCK, &mask_set, NULL) == -1) {
perror("sigprocmask解除失败");
exit(EXIT_FAILURE);
}
// 再次查看未决信号(已处理)
sleep(1);
print_pending_signals();
printf("[主进程] 程序正常退出\n");
return 0;
}
代码解析:
- print_pending_signals():遍历 1-31 号信号,打印未决信号编号;
- sigprocmask(SIG_BLOCK):屏蔽 SIGINT,此时按 Ctrl+C 信号不会被处理;
- sigpending():获取未决信号集,验证 SIGINT 是否未决;
- sigprocmask(SIG_UNBLOCK):解除屏蔽,内核立即递送信号,触发处理函数。
运行步骤:
gcc sig_mask_pending.c -o sig_mask_pending
./sig_mask_pending
输出结果(5 秒内按 Ctrl+C):
[主进程] 屏蔽SIGINT信号...
[主进程] PID:12350,5秒内按下Ctrl+C触发SIGINT...
^C(按下Ctrl+C,无立即反应)
当前未决信号:2
[主进程] 解除SIGINT屏蔽,信号将被处理...
[处理函数] 捕获到SIGINT信号(2)
当前未决信号:
[主进程] 程序正常退出
3.5 示例 5:超时控制(alarm () + pause ())
功能:实现函数执行超时控制,5 秒内未完成则触发 SIGALRM 终止程序。
完整代码(sig_alarm_demo.c):
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
// 超时处理函数
void alarm_handler(int signum) {
printf("\n[超时] 函数执行超过5秒,强制终止!\n");
exit(EXIT_FAILURE);
}
// 模拟耗时函数
void time_consuming_func(int seconds) {
printf("[函数] 开始执行,预计耗时%d秒...\n", seconds);
sleep(seconds); // 模拟耗时操作
printf("[函数] 执行完成!\n");
}
int main() {
// 注册SIGALRM处理函数
if (signal(SIGALRM, alarm_handler) == SIG_ERR) {
perror("signal注册失败");
exit(EXIT_FAILURE);
}
// 设置5秒定时器
printf("[主进程] 设置5秒超时定时器...\n");
alarm(5);
// 调用耗时函数(测试:3秒=正常,6秒=超时)
time_consuming_func(6); // 改为3秒测试正常情况
// 取消定时器(若函数提前完成)
alarm(0);
printf("[主进程] 程序正常结束\n");
return 0;
}
代码解析:
- alarm(5):设置 5 秒定时器,到期发送 SIGALRM;
- time_consuming_func:模拟耗时操作(sleep 指定秒数);
- 超时逻辑:若函数执行 > 5 秒,SIGALRM 触发,执行
alarm_handler终止程序; - 正常逻辑:若函数执行 < 5 秒,调用
alarm(0)取消定时器,程序正常结束。
测试结果:
-
测试 1(超时)
time_consuming_func(6)[主进程] 设置5秒超时定时器... [函数] 开始执行,预计耗时6秒... [超时] 函数执行超过5秒,强制终止! -
测试 2(正常)
time_consuming_func(3)[主进程] 设置5秒超时定时器... [函数] 开始执行,预计耗时3秒... [函数] 执行完成! [主进程] 程序正常结束
四、总结
- 信号本质:内核向进程发送的异步软中断,是最轻量的 IPC 机制,仅传递事件标识,无复杂数据传输能力;
- 核心 API
- 注册函数:优先使用
sigaction()(健壮、可配置),避免signal()(兼容性差); - 发送函数:
kill()(跨进程)、alarm()(定时器)是最常用的信号发送方式; - 控制函数:
sigprocmask()(屏蔽信号)、sigpending()(查询未决信号)用于精细控制信号生命周期;
- 注册函数:优先使用
- 典型场景:进程控制、异常处理、超时控制、异步通知是信号最核心的使用场景,尤其适合需要 “异步事件触发” 的场景。
信号是 Linux 进程通信的基础,掌握信号的使用不仅能解决进程间异步通知问题,也是理解 Linux 内核进程调度、异常处理机制的关键。在实际开发中,需注意信号的可靠性(优先使用实时信号)、屏蔽集的配置(避免关键信号被中断),以及与其他 IPC 机制(如共享内存、锁)的配合使用。
更多推荐




所有评论(0)