Linux 信号详解

Linux 信号详解 目录前言一、什么是信号1.信号的特点二、信号的基本概念1.信号的生命周期2.信号编号与名称3.信号的来源三、常见信号列表1.标准信号1-312.重要信号详解1SIGINT (2) - 中断信号2SIGKILL (9) - 强制终止3SIGTERM (15) - 正常终止4SIGSEGV (11) - 段错误5SIGCHLD (17) - 子进程状态改变四、信号的产生方式1. 终端按键产生2. 硬件异常产生3. 系统调用产生1kill() 函数2raise() 函数3abort() 函数4. 软件条件产生1alarm() 函数2setitimer() 函数五、信号的处理方式1.三种处理方式2.signal() 函数传统方式3.sigaction() 函数推荐方式1sigaction 使用示例2使用 SA_SIGINFO 获取详细信息六、信号集操作1.信号集类型2.信号集操作函数3.信号集操作示例七、信号的阻塞与未决1.基本概念2.信号阻塞相关函数3.信号阻塞示例4.信号处理流程图八、实时信号1.实时信号特点2.实时信号操作4.实时信号示例九、信号处理的最佳实践1. 信号处理函数的安全要求2. 使用自管道Self-Pipe技巧3. 优雅的信号处理模式4. 子进程信号处理5. 信号安全日志记录九、常用命令参考1.发送信号2.查看信号信息3.信号调试十、总结前言信号是Linux/Unix系统中的异步事件通知机制用于进程间通信和异常处理。本文系统介绍了信号的基本概念、常见信号类型如SIGINT、SIGKILL、SIGTERM等、信号产生方式终端按键、硬件异常、系统调用等以及信号处理方法signal/sigaction。重点阐述了信号阻塞、实时信号特性并提供了信号处理的最佳实践包括安全函数使用、自管道技巧和子进程处理方案。文章还包含常用命令参考可帮助开发者们正确使用信号机制进行进程管理和异常处理。一、什么是信号信号Signal是 Linux/Unix 系统中一种异步事件通知机制用于通知进程发生了某个特定事件。信号是软件中断的一种形式它提供了一种处理异步事件的方法。1.信号的特点异步性信号可以在任何时候发送给进程进程无法预知信号何时到达简单性信号携带的信息量很少仅表示某种事件的发生优先级信号的处理会打断进程的正常执行流程可处理性进程可以选择捕获、忽略或执行默认操作二、信号的基本概念1.信号的生命周期信号产生 → 信号注册 → 信号处理2.信号编号与名称每个信号都有一个唯一的整数编号1-31 为标准信号34-64 为实时信号和一个符号名称如SIGINT、SIGKILL。3.信号的来源来源类型说明示例终端输入用户通过键盘发送CtrlC, Ctrl\硬件异常硬件错误产生除零错误、非法内存访问系统调用通过函数发送kill(), raise(), alarm()软件条件特定软件条件定时器到期、管道断开三、常见信号列表1.标准信号1-31信号编号默认动作说明SIGHUP1终止挂起信号终端断开时发送SIGINT2终止中断信号CtrlC 产生SIGQUIT3终止核心转储退出信号Ctrl\ 产生SIGILL4终止核心转储非法指令SIGTRAP5终止核心转储跟踪/断点陷阱SIGABRT6终止核心转储异常终止abortSIGBUS7终止核心转储总线错误SIGFPE8终止核心转储浮点运算异常SIGKILL9终止强制终止不可捕获/忽略SIGUSR110终止用户自定义信号1SIGSEGV11终止核心转储段错误非法内存访问SIGUSR212终止用户自定义信号2SIGPIPE13终止管道破裂写入无读端管道SIGALRM14终止定时器信号alarmSIGTERM15终止正常终止信号SIGCHLD17忽略子进程状态改变SIGCONT18继续继续执行暂停的进程SIGSTOP19暂停暂停进程不可捕获/忽略SIGTSTP20暂停终端暂停信号CtrlZ 产生SIGTTIN21暂停后台进程读终端SIGTTOU22暂停后台进程写终端2.重要信号详解1SIGINT (2) - 中断信号# 按 CtrlC 发送 # 默认行为终止进程 # 常用于优雅地终止前台进程2SIGKILL (9) - 强制终止kill -9 pid # 特点 # - 无法被捕获、阻塞或忽略 # - 立即终止进程 # - 进程无法进行清理操作 # 建议仅在无法正常终止时使用3SIGTERM (15) - 正常终止kill pid # 默认发送 SIGTERM kill -15 pid # 特点 # - 可以被捕获和处理 # - 允许进程进行清理操作 # - 推荐的首选终止方式4SIGSEGV (11) - 段错误// 常见原因 // - 访问未分配的内存 // - 访问已释放的内存 // - 数组越界访问 // - 解引用空指针 int *p NULL; *p 10; // 触发 SIGSEGV5SIGCHLD (17) - 子进程状态改变// 子进程终止或停止时发送给父进程 // 默认行为忽略 // 常用于防止僵尸进程四、信号的产生方式1. 终端按键产生CtrlC → SIGINT (中断) Ctrl\ → SIGQUIT (退出) CtrlZ → SIGTSTP (暂停)2. 硬件异常产生// 除零错误 → SIGFPE int a 10 / 0; // 非法内存访问 → SIGSEGV int *p (int *)0x12345; *p 100;3. 系统调用产生1kill() 函数#include signal.h #include sys/types.h int kill(pid_t pid, int sig); // 功能向指定进程发送信号 // 参数 // pid 0: 发送给指定进程 // pid 0: 发送给同组所有进程 // pid -1: 发送给所有有权限的进程 // pid -1: 发送给进程组 |pid| 中的所有进程 // 返回值成功返回0失败返回-1 // 示例 kill(1234, SIGTERM); // 向PID为1234的进程发送SIGTERM kill(0, SIGUSR1); // 向同组所有进程发送SIGUSR12raise() 函数#include signal.h int raise(int sig); // 功能向当前进程自身发送信号 // 等价于kill(getpid(), sig) // 示例 raise(SIGTERM); // 自我终止3abort() 函数#include stdlib.h void abort(void); // 功能异常终止程序产生 SIGABRT 信号 // 特点会生成核心转储文件如果允许4. 软件条件产生1alarm() 函数#include unistd.h unsigned int alarm(unsigned int seconds); // 功能设置定时器seconds秒后发送 SIGALRM 信号 // 返回值返回上次定时器剩余时间无上次定时器返回0 // 注意每个进程只能有一个alarm定时器 // 示例 #include stdio.h #include unistd.h #include signal.h void alarm_handler(int sig) { printf(定时器到期\n); } int main() { signal(SIGALRM, alarm_handler); alarm(5); // 5秒后发送SIGALRM printf(等待定时器...\n); pause(); // 等待信号 return 0; }2setitimer() 函数#include sys/time.h int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value); // 功能设置高精度定时器 // which参数 // ITIMER_REAL: 真实时间发送 SIGALRM // ITIMER_VIRTUAL: 用户态CPU时间发送 SIGVTALRM // ITIMER_PROF: 用户态内核态CPU时间发送 SIGPROF struct itimerval { struct timeval it_interval; // 间隔时间周期性定时 struct timeval it_value; // 首次到期时间 }; struct timeval { time_t tv_sec; // 秒 suseconds_t tv_usec; // 微秒 };五、信号的处理方式1.三种处理方式默认动作Default执行系统预设的操作忽略信号Ignore丢弃信号不执行任何操作捕获信号Catch执行自定义的信号处理函数2.signal() 函数传统方式#include signal.h typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler); // 参数 // signum: 信号编号 // handler: 处理方式 // SIG_DFL: 默认处理 // SIG_IGN: 忽略信号 // 自定义函数指针: 捕获信号 // 示例代码 #include stdio.h #include signal.h #include unistd.h // 自定义信号处理函数 void sigint_handler(int sig) { printf(\n捕获到 SIGINT 信号编号%d\n, sig); printf(正在执行清理工作...\n); // 执行清理操作 _exit(0); // 安全退出 } int main() { // 设置 SIGINT 的处理方式 if (signal(SIGINT, sigint_handler) SIG_ERR) { perror(signal); return 1; } printf(按 CtrlC 测试信号处理PID: %d\n, getpid()); while (1) { printf(工作中...\n); sleep(2); } return 0; }3.sigaction() 函数推荐方式#include signal.h int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact); // 功能更强大的信号处理设置函数 // 参数 // signum: 信号编号 // act: 新的处理方式NULL表示不设置 // oldact: 保存旧的处理方式NULL表示不保存 struct sigaction { void (*sa_handler)(int); // 信号处理函数 void (*sa_sigaction)(int, siginfo_t *, void *); // 高级处理函数 sigset_t sa_mask; // 信号屏蔽字 int sa_flags; // 标志位 void (*sa_restorer)(void); // 已废弃 }; // sa_flags 常用标志 #define SA_RESTART // 自动重启被中断的系统调用 #define SA_NODEFER // 不自动阻塞该信号 #define SA_RESETHAND // 执行后恢复默认处理 #define SA_SIGINFO // 使用 sa_sigaction 处理函数1sigaction 使用示例#include stdio.h #include signal.h #include unistd.h #include string.h void sigint_handler(int sig) { write(STDOUT_FILENO, \n收到 SIGINT优雅退出\n, 25); _exit(0); } int main() { struct sigaction sa; // 清空结构体 memset(sa, 0, sizeof(sa)); // 设置处理函数 sa.sa_handler sigint_handler; // 设置标志自动重启被中断的系统调用 sa.sa_flags SA_RESTART; // 清空信号屏蔽字 sigemptyset(sa.sa_mask); // 安装信号处理器 if (sigaction(SIGINT, sa, NULL) -1) { perror(sigaction); return 1; } printf(按 CtrlC 退出PID: %d\n, getpid()); while (1) { printf(运行中...\n); sleep(2); } return 0; }2使用 SA_SIGINFO 获取详细信息#define _GNU_SOURCE #include stdio.h #include signal.h #include unistd.h void advanced_handler(int sig, siginfo_t *info, void *context) { printf(\n 信号详细信息 \n); printf(信号编号: %d\n, sig); printf(发送进程PID: %d\n, info-si_pid); printf(发送进程UID: %d\n, info-si_uid); printf(信号值: %d\n, info-si_value.sival_int); printf(\n); } int main() { struct sigaction sa; sa.sa_sigaction advanced_handler; sa.sa_flags SA_SIGINFO; // 使用三参数处理函数 sigemptyset(sa.sa_mask); sigaction(SIGUSR1, sa, NULL); printf(等待 SIGUSR1 信号PID: %d\n, getpid()); while (1) { pause(); } return 0; }六、信号集操作1.信号集类型#include signal.h typedef struct { unsigned long sig[(64 8 * sizeof(unsigned long) - 1) / (8 * sizeof(unsigned long))]; } sigset_t; // 用于表示一组信号的位图结构2.信号集操作函数#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); // 检查信号是否在信号集中 int sigismember(const sigset_t *set, int signum);3.信号集操作示例#include stdio.h #include signal.h int main() { sigset_t set; // 清空信号集 sigemptyset(set); // 添加 SIGINT 和 SIGTERM sigaddset(set, SIGINT); sigaddset(set, SIGTERM); // 检查 SIGINT 是否在集合中 if (sigismember(set, SIGINT)) { printf(SIGINT 在信号集中\n); } // 检查 SIGKILL 是否在集合中 if (!sigismember(set, SIGKILL)) { printf(SIGKILL 不在信号集中\n); } // 删除 SIGINT sigdelset(set, SIGINT); // 填充所有信号 sigfillset(set); printf(信号集已填充所有信号\n); return 0; }七、信号的阻塞与未决1.基本概念信号阻塞Blocked信号被阻塞后不会立即递达而是保持未决状态信号未决Pending信号已产生但尚未被处理的状态信号递达Delivered信号被处理的时刻2.信号阻塞相关函数#include signal.h // 检查或修改进程的信号屏蔽字 int sigprocmask(int how, const sigset_t *set, sigset_t *oldset); // how参数 // SIG_BLOCK: 将set中的信号添加到屏蔽字 // SIG_UNBLOCK: 从屏蔽字中移除set中的信号 // SIG_SETMASK: 将屏蔽字设置为set // 获取当前未决信号集 int sigpending(sigset_t *set);3.信号阻塞示例#include stdio.h #include signal.h #include unistd.h void handler(int sig) { printf(处理信号: %d\n, sig); } int main() { sigset_t block_set, pending_set; // 设置 SIGINT 处理函数 signal(SIGINT, handler); // 创建要阻塞的信号集 sigemptyset(block_set); sigaddset(block_set, SIGINT); printf(阻塞 SIGINT 5秒请在这5秒内按 CtrlC\n); // 阻塞 SIGINT sigprocmask(SIG_BLOCK, block_set, NULL); sleep(5); // 检查未决信号 sigpending(pending_set); if (sigismember(pending_set, SIGINT)) { printf(SIGINT 处于未决状态\n); } printf(解除阻塞信号将被处理\n); // 解除阻塞 sigprocmask(SIG_UNBLOCK, block_set, NULL); sleep(1); printf(程序结束\n); return 0; }4.信号处理流程图┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ 信号产生 │ ──→ │ 信号未决 │ ──→ │ 信号递达 │ │ (Generated)│ │ (Pending) │ │ (Delivered) │ └─────────────┘ └─────────────┘ └─────────────┘ ↑ │ 信号被阻塞 ┌─────────────┐ │ 信号阻塞 │ │ (Blocked) │ └─────────────┘八、实时信号1.实时信号特点特性标准信号实时信号编号范围1-3134-64排队不支持相同信号只保留一个支持多个相同信号排队携带数据不能可以通过 sigqueue优先级无编号小的优先用途系统预定义用户自定义2.实时信号操作#include signal.h #include unistd.h // 发送带数据的实时信号 int sigqueue(pid_t pid, int sig, const union sigval value); union sigval { int sival_int; // 整型数据 void *sival_ptr; // 指针数据仅同一进程内有效 };4.实时信号示例#define _GNU_SOURCE #include stdio.h #include signal.h #include unistd.h void rt_handler(int sig, siginfo_t *info, void *context) { printf(收到实时信号 %d数据: %d\n, sig, info-si_value.sival_int); } int main() { struct sigaction sa; pid_t pid getpid(); sa.sa_sigaction rt_handler; sa.sa_flags SA_SIGINFO; sigemptyset(sa.sa_mask); // 注册实时信号处理函数 sigaction(SIGRTMIN, sa, NULL); printf(进程 PID: %d\n, pid); printf(向自己发送3个实时信号...\n); // 发送带数据的实时信号 union sigval value; for (int i 1; i 3; i) { value.sival_int i * 100; sigqueue(pid, SIGRTMIN, value); } sleep(1); return 0; }九、信号处理的最佳实践1. 信号处理函数的安全要求信号处理函数中只能调用异步信号安全函数// ✅ 安全的函数 write(), _exit(), kill(), sigaction() getpid(), getppid(), sleep() // ❌ 不安全的函数避免使用 printf(), malloc(), free(), exit() fopen(), fread(), fwrite() pthread_mutex_lock(), 大部分库函数2. 使用自管道Self-Pipe技巧#include stdio.h #include signal.h #include unistd.h #include fcntl.h #include sys/select.h static int pipefd[2]; void signal_handler(int sig) { // 只写入一个字节到管道 write(pipefd[1], sig, 1); } int main() { fd_set readfds; char sig; // 创建管道 pipe(pipefd); fcntl(pipefd[0], F_SETFL, O_NONBLOCK); fcntl(pipefd[1], F_SETFL, O_NONBLOCK); // 设置信号处理 signal(SIGINT, signal_handler); signal(SIGTERM, signal_handler); printf(使用 select 等待信号PID: %d\n, getpid()); while (1) { FD_ZERO(readfds); FD_SET(pipefd[0], readfds); select(pipefd[0] 1, readfds, NULL, NULL, NULL); if (FD_ISSET(pipefd[0], readfds)) { read(pipefd[0], sig, 1); printf(主循环中处理信号: %d\n, (int)sig); if (sig SIGINT || sig SIGTERM) { printf(退出程序\n); break; } } } close(pipefd[0]); close(pipefd[1]); return 0; }3. 优雅的信号处理模式#include stdio.h #include signal.h #include unistd.h #include stdatomic.h // 使用原子变量作为信号标志 static atomic_int signal_received 0; void signal_handler(int sig) { // 只设置标志不做复杂操作 atomic_store(signal_received, sig); } void process_work() { // 模拟工作 static int counter 0; printf(工作进行中... %d\n, counter); sleep(1); } void cleanup() { printf(执行清理操作...\n); // 关闭文件、释放资源等 } int main() { struct sigaction sa; sa.sa_handler signal_handler; sa.sa_flags SA_RESTART; sigemptyset(sa.sa_mask); sigaction(SIGINT, sa, NULL); sigaction(SIGTERM, sa, NULL); printf(程序运行中PID: %d\n, getpid()); while (1) { process_work(); // 在主循环中检查信号 int sig atomic_load(signal_received); if (sig ! 0) { printf(\n收到信号 %d准备退出\n, sig); cleanup(); break; } } return 0; }4. 子进程信号处理#include stdio.h #include signal.h #include unistd.h #include sys/wait.h void sigchld_handler(int sig) { int status; pid_t pid; // 使用 WNOHANG 非阻塞等待所有子进程 while ((pid waitpid(-1, status, WNOHANG)) 0) { printf(子进程 %d 已退出\n, pid); } } int main() { struct sigaction sa; // 设置 SIGCHLD 处理函数防止僵尸进程 sa.sa_handler sigchld_handler; sa.sa_flags SA_RESTART | SA_NOCLDSTOP; sigemptyset(sa.sa_mask); sigaction(SIGCHLD, sa, NULL); // 创建子进程 for (int i 0; i 3; i) { pid_t pid fork(); if (pid 0) { // 子进程 printf(子进程 %d 启动\n, getpid()); sleep(2 i); printf(子进程 %d 退出\n, getpid()); _exit(0); } } printf(父进程等待子进程...\n); // 父进程继续工作 for (int i 0; i 10; i) { printf(父进程工作中...\n); sleep(1); } return 0; }5. 信号安全日志记录#include stdio.h #include signal.h #include unistd.h #include string.h // 信号安全的日志函数 void safe_log(const char *msg) { // 使用 write 而不是 printf write(STDOUT_FILENO, msg, strlen(msg)); write(STDOUT_FILENO, \n, 1); } void handler(int sig) { const char *msg; switch (sig) { case SIGINT: msg 收到 SIGINT; break; case SIGTERM: msg 收到 SIGTERM; break; case SIGALRM: msg 收到 SIGALRM; break; default: msg 收到未知信号; break; } safe_log(msg); } int main() { signal(SIGINT, handler); signal(SIGTERM, handler); signal(SIGALRM, handler); alarm(3); safe_log(程序启动3秒后收到SIGALRM); while (1) { pause(); } return 0; }九、常用命令参考1.发送信号# 发送 SIGTERM默认 kill pid # 发送特定信号 kill -SIGINT pid kill -2 pid # 数字形式 kill -9 pid # 强制终止 # 向进程组发送信号 kill -TERM -pgid # 使用 pkill 按名称发送 pkill -INT process_name pkill -f pattern # 使用 killall killall -TERM process_name2.查看信号信息# 列出所有信号 kill -l # 查看信号编号对应的名称 kill -l 9 # 输出: KILL kill -l 15 # 输出: TERM # 查看信号帮助 man 7 signal man sigaction3.信号调试# 使用 strace 跟踪信号 strace -e tracesignal ./program # 查看进程挂起的信号 cat /proc/pid/status | grep Sig # SigPnd: 线程组共享的未决信号 # SigBlk: 阻塞的信号 # SigIgn: 忽略的信号 # SigCgt: 捕获的信号十、总结要点说明信号本质软件中断异步事件通知机制可靠信号SIGKILL(9) 和SIGSTOP(19) 不可捕获/忽略推荐终止优先使用SIGTERM(15)允许进程清理推荐API使用sigaction()替代signal()处理安全信号处理器中只使用异步信号安全函数实时信号支持排队和携带数据用于应用自定义