1. Linux信号机制概述信号是Linux系统中进程间通信的一种基本方式它允许一个进程向另一个进程发送异步通知。当某个事件发生时比如用户按下CtrlC内核会向目标进程发送相应的信号进程可以捕获并处理这些信号也可以选择忽略或执行默认操作。信号本质上是一个软中断它打断了进程的正常执行流程。与硬件中断类似信号提供了一种异步事件处理机制使得进程不必主动轮询事件状态。这种设计在系统编程中极为重要特别是在处理异常情况、进程控制和资源管理等方面。2. 信号的产生与递送2.1 信号的产生来源信号可以由多种事件触发键盘输入如CtrlC产生SIGINT硬件异常如除零错误产生SIGFPE系统调用如kill()函数软件条件如管道破裂产生SIGPIPE内核为每个进程维护两个信号位图pending位图记录待处理信号blocked位图记录被阻塞的信号。当信号产生时内核会在目标进程的task_struct结构中设置相应位。2.2 信号的递送过程信号递送delivery指内核将信号传递给目标进程的过程。这个过程不是立即发生的而是在进程从内核态返回用户态时内核会检查pending信号并处理。这种延迟处理机制确保了信号处理的原子性和一致性。信号处理的核心数据结构包括struct signal_struct { atomic_t count; struct k_sigaction action[_NSIG]; sigset_t blocked; sigset_t real_blocked; // ... };3. 信号的保存机制3.1 信号集sigset_tLinux使用sigset_t类型表示信号集本质上是一个位掩码。每个信号对应一个bit位1表示信号存在0表示信号不存在。常见的信号集操作包括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.2 信号阻塞Blocking进程可以暂时阻塞某些信号使其暂时不被递送。这通过sigprocmask()系统调用实现int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);how参数指定操作类型SIG_BLOCK将set中的信号加入阻塞集SIG_UNBLOCK从阻塞集中移除set中的信号SIG_SETMASK直接设置阻塞集为set被阻塞的信号会保持在pending状态直到解除阻塞。特别要注意的是SIGKILL和SIGSTOP信号不能被阻塞、捕获或忽略。3.3 信号处理函数注册使用sigaction()函数可以为信号注册处理程序struct sigaction { void (*sa_handler)(int); void (*sa_sigaction)(int, siginfo_t *, void *); sigset_t sa_mask; int sa_flags; // ... }; int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);关键参数说明sa_handler简单的信号处理函数sa_sigaction支持附加信息的处理函数sa_mask在执行处理函数期间要阻塞的信号集sa_flags控制信号处理行为的标志位4. 信号处理的实际应用4.1 可靠信号处理模式传统signal()函数存在不可靠问题现代程序应使用sigaction()。可靠信号处理需要注意处理函数应尽可能简单避免调用非异步安全函数使用volatile sig_atomic_t类型声明全局标志正确处理信号中断的系统调用使用SA_RESTART标志4.2 常见信号处理场景SIGCHLD处理避免僵尸进程void sigchld_handler(int sig) { while (waitpid(-1, NULL, WNOHANG) 0); } // 注册处理程序 struct sigaction sa; sa.sa_handler sigchld_handler; sigemptyset(sa.sa_mask); sa.sa_flags SA_RESTART | SA_NOCLDSTOP; sigaction(SIGCHLD, sa, NULL);SIGTERM/SIGINT处理优雅退出volatile sig_atomic_t shutdown_flag 0; void term_handler(int sig) { shutdown_flag 1; } // 注册处理程序 struct sigaction sa; sa.sa_handler term_handler; sigemptyset(sa.sa_mask); sa.sa_flags 0; sigaction(SIGTERM, sa, NULL); sigaction(SIGINT, sa, NULL);5. 信号处理的高级话题5.1 实时信号Linux支持32个实时信号SIGRTMIN到SIGRTMAX相比标准信号具有以下特点支持排队不会丢失携带附加信息通过siginfo_t有优先级顺序使用示例union sigval value; value.sival_int 1234; sigqueue(pid, SIGRTMIN3, value);5.2 信号与线程在多线程环境中信号处理更加复杂每个线程有独立的信号掩码信号可以发给特定线程pthread_kill()未处理的信号会递送给任意一个不阻塞该信号的线程最佳实践是主线程设置信号掩码并派生子线程指定一个线程专门处理信号通过pthread_sigmask()避免在多线程程序中使用信号进行复杂同步6. 信号处理常见问题与调试6.1 常见陷阱信号竞争条件在检查标志和处理之间可能收到信号解决方案使用sigprocmask()原子地检查和等待不可重入函数信号处理函数中调用printf等非异步安全函数解决方案只使用异步安全函数或设置全局标志信号丢失标准信号不支持排队解决方案使用实时信号或其它IPC机制6.2 调试技巧使用strace观察信号传递strace -e tracesignal -p pid通过/proc文件系统查看信号状态cat /proc/pid/status | grep Sig使用gdb调试信号处理gdb -p pid (gdb) handle SIGINT nostop print pass (gdb) break handler_function信号处理是Linux系统编程中的重要主题理解其保存和处理机制对于编写健壮的应用程序至关重要。在实际开发中建议遵循最小化原则信号处理尽可能简单复杂逻辑放到主循环中处理。同时要注意多线程环境下的特殊考虑避免微妙的竞态条件。