linux如何创建信号
- Linux
- 2025-08-11
- 11
在 Linux 操作系统中,信号(Signal)是一种软中断机制,用于通知进程发生了特定事件(如用户输入、硬件异常或外部请求),它是进程间通信的核心手段之一,广泛应用于进程控制、调试和错误处理等场景,以下是关于“如何在 Linux 中创建信号”的完整指南,涵盖命令行操作、系统调用、编程实践及关键细节。
信号的基础概念与分类
1 什么是信号?
信号本质是操作系统内核向进程传递的异步通知,类似于硬件中断,每个信号对应唯一的整型编号(如 SIGINT=2, SIGKILL=9),并关联预定义的行为(终止进程、暂停执行等)。
| 信号名称 | 编号 | 默认动作 | 典型触发场景 |
|---|---|---|---|
| SIGHUP | 1 | 终止进程 | 终端挂断 |
| SIGINT | 2 | 终止进程 | Ctrl+C 组合键 |
| SIGQUIT | 3 | 核心转储+终止 | Ctrl+ 组合键 |
| SIGILL | 4 | 核心转储+终止 | 非法指令 |
| SIGFPE | 8 | 核心转储+终止 | 算术错误(除零溢出) |
| SIGKILL | 9 | 强制终止 | kill -9 命令 |
| SIGUSR1 | 10 | 忽略 | 用户自定义逻辑 |
| SIGUSR2 | 11 | 忽略 | 用户自定义逻辑 |
| SIGALRM | 14 | 终止进程 | alarm() 定时器到期 |
| SIGTERM | 15 | 终止进程 | kill 默认信号 |
| SIGBUS | 100+ | 核心转储+终止 | 总线错误 |
注:编号小于 32 的信号为标准信号,32~64 为实时信号(可通过 man 7 signal 查看完整列表)。
2 信号的生命周期
- 生成:由键盘事件、系统调用或其它进程发起。
- 递送:内核将信号插入目标进程的信号队列。
- 处理:进程按以下顺序选择一种响应方式:
- 执行关联的处理函数(Handler);
- 忽略信号;
- 执行默认动作(如终止进程)。
创建与发送信号的方法
1 命令行工具:kill 命令
kill 命令并非仅用于终止进程,而是通用的信号发送工具,其语法为:
kill [选项] <进程ID> <信号编号>
常用场景示例:
- 向当前终端发送 SIGHUP(重启前台作业): kill -HUP $$ # $$ 表示当前Shell的PID
- 优雅终止后台进程(替代 Ctrl+C): kill -TERM $(pgrep myapp) # 根据进程名获取PID
- 强制杀死顽固进程: kill -KILL 12345 # 直接终止PID为12345的进程
| 选项 | 含义 | 示例 |
|---|---|---|
| -l | 列出所有信号名称 | kill -l → 显示编号与名称映射 |
| -L | 列出所有信号编号 | kill -L |
| -NAME | 按名称发送信号 | kill -SIGINT 1234 |
| -sig | 同上 | kill -SIGQUIT 1234 |
| -PIPE | 向同一进程组的所有成员发送 | kill -USR1 -PGRP |
注意:普通用户只能向自己拥有的进程发送非特权信号(如 SIGTERM),而 SIGKILL 可绕过此限制。

2 系统调用:kill() 与 raise()
在 C/C++ 程序中,可通过以下两种方式主动发送信号:
raise(int sig):向自身发送信号
#include <signal.h> #include <stdio.h> #include <unistd.h> void handler(int signum) { printf("Received signal %dn", signum); } int main() { signal(SIGUSR1, handler); // 注册处理函数 raise(SIGUSR1); // 向自身发送信号 pause(); // 等待信号到来 return 0; }
编译运行:gcc test.c -o test && ./test → 输出 “Received signal 10″。
kill(pid_t pid, int sig):向指定进程发送信号
#include <signal.h> #include <stdio.h> #include <sys/types.h> #include <unistd.h> int main() { pid_t child_pid = fork(); if (child_pid == 0) { // 子进程 while (1) { printf("Child running... PID=%dn", getpid()); sleep(1); } } else { // 父进程 sleep(5); // 等待子进程启动 kill(child_pid, SIGTERM); // 发送终止信号 printf("Sent SIGTERM to child PID=%dn", child_pid); } return 0; }
此示例演示了父子进程间的信号通信。
信号处理程序的开发
1 简单模型:signal() 函数
#include <signal.h> #include <stdio.h> #include <unistd.h> void custom_handler(int signum) { printf("Custom handler triggered by signal %dn", signum); } int main() { if (signal(SIGTSTP, custom_handler) == SIG_ERR) { // STTY停止字符 perror("signal"); return 1; } while (1) pause(); // 挂起等待信号 return 0; }
运行时按下 Ctrl+Z(生成 SIGTSTP),将触发自定义逻辑而非暂停进程。

2 可靠模型:sigaction() 结构体
相较于 signal(),sigaction() 提供更精细的控制能力:
#include <signal.h> #include <stdio.h> #include <string.h> #include <unistd.h> struct sigaction sa; memset(&sa, 0, sizeof(sa)); // 清零结构体 sa.sa_handler = handler; // 设置处理函数 sa.sa_flags = SA_RESTART; // 自动重启被中断的系统调用 sigemptyset(&sa.sa_mask); // 不屏蔽任何额外信号 sigaction(SIGHUP, &sa, NULL); // 安装新处理器
关键字段解析:
| 字段 | 作用 |
|—————-|—————————————-|
| sa_handler | 指向处理函数的指针 |
| sa_mask | 信号处理期间临时屏蔽的信号集合 |
| sa_flags | 控制标志(如 SA_RESTART, SA_NOCLDSTOP)|
| sa_sigaction | 替代 sa_handler,支持带参回调 |
优势对比:
| 特性 | signal() | sigaction() |
|——————–|———————|———————|
| 可靠性 | 低(易被覆盖) | 高(原子性操作) |
| 支持复杂行为 | | (如带参回调) |
| 可移植性 | 部分平台不一致 | POSIX标准 |
| 信号集管理 | 有限 | 完整 |
特殊场景与最佳实践
1 守护进程的信号管理
守护进程通常脱离终端运行,需显式处理以下信号:
- SIGHUP:重新加载配置文件(常见于 Nginx/Apache);
- SIGUSR1/SIGUSR2:执行日志轮转或性能统计;
- SIGCHLD:回收子进程资源。
示例配置片段:

void hup_reload(int signum) { printf("Reloading config files...n"); load_config(); // 重新读取配置文件 } void init_daemon() { struct sigaction sa; sa.sa_handler = hup_reload; sigaction(SIGHUP, &sa, NULL); }
2 避免竞态条件
多线程环境中,若主线程正在修改共享数据时收到信号,可能导致数据不一致,解决方案:
- 在信号处理函数中仅设置标志位,主循环检查标志位后执行操作;
- 使用 pthread_sigmask() 动态调整线程的信号掩码。
3 实时信号的使用
实时信号(编号≥32)允许自定义行为且不会互相干扰。
union sigval { int sival_int; // 整数值 void sival_ptr; // 指针值 }; void realtime_handler(int signum, siginfo info, void ucontext) { printf("Realtime signal %d received with value %dn", signum, info->si_value.sival_int); } int main() { struct sigaction sa; sa.sa_flags = SA_SIGINFO; // 启用附加信息传递 sa.sa_sigaction = realtime_handler; sigaction(SIRTMIN+1, &sa, NULL); // 使用第一个实时信号 // 发送信号并附带数据 union sigval val; val.sival_int = 42; sigqueue(getpid(), SIRTMIN+1, val); // 发送带数据的实时信号 pause(); return 0; }
常见错误与调试技巧
| 现象 | 原因 | 解决方案 |
|---|---|---|
| 信号未被接收 | 目标进程已退出或不存在 | 检查PID有效性 |
| 处理函数未生效 | signal() 被后续调用覆盖 | 改用 sigaction() |
| 死锁(无限递归) | 处理函数内再次触发同名信号 | 在入口添加静态变量去重 |
| 多线程环境下崩溃 | 非异步信号安全函数被中断 | 标记为 volatile sigatomic_t |
| SIGSEGV 频繁发生 | 野指针或栈溢出 | 使用 gdb 进行内存分析 |
调试工具推荐:
- strace:跟踪进程的信号传递过程;
- gdb:单步调试信号处理函数;
- dmesg:查看内核日志中的信号相关记录。
相关问答FAQs
Q1: 我的程序收到了意外的 SIGSEGV,如何定位原因?
A: SIGSEGV(段错误)通常由非法内存访问引起,解决方法:
- 使用 gdb 运行程序:gdb ./your_program → run → 崩溃时输入 bt 查看调用栈;
- 启用编译器的保护选项:-fsanitize=address;
- 检查数组越界、空指针解引用或堆栈溢出。
Q2: 为什么我的 SIGCHLD 处理函数没有被调用?
A: 可能原因及排查步骤:
- 子进程已自动回收:若子进程退出时父进程未阻塞,waitpid() 会立即回收它,导致 SIGCHLD 未被递送,可在父进程中添加 wait(&status) 确保同步;
- 信号被忽略:默认情况下 SIGCHLD 不会被忽略,但若曾显式调用过 signal(SIGCHLD, SIG_IGN),需恢复默认处理;
- 命名空间隔离:容器或 namespaces 可能限制了信号的传播,需检查宿主机与容器内的进程关系