1. Linux信号机制入门从CtrlC说起当你第一次在终端按下CtrlC终止程序时就已经和Linux信号机制打过交道了。这个看似简单的操作背后是操作系统与进程间通信的经典设计。信号本质上是一种软件中断它允许操作系统或用户进程通知目标进程发生了特定事件。我在排查线上服务问题时经常用kill -l命令查看系统支持的信号列表这个习惯帮助我快速定位了很多进程异常退出的问题。信号机制最神奇的地方在于它的异步特性。就像突然接到电话通知进程可能正在执行任何操作时被打断处理信号。记得我第一次写守护进程时就因为没处理好SIGHUP信号导致服务异常退出。后来发现很多网络工具如wget、nginx都专门处理了这个信号使得它们在终端关闭后仍能继续工作。信号分为标准信号和实时信号两大类。标准信号1-31源自UNIX传统存在信号丢失的可能而实时信号32以上支持排队更可靠。不过在日常开发中我们接触最多的还是那些经典信号# 查看所有信号 $ kill -l 1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2 ...2. 常见信号深度解析2.1 SIGINT优雅退出的艺术SIGINT信号值2可能是开发者最熟悉的信号了。在终端按下CtrlC时前台进程就会收到这个中断信号。但很多人不知道的是正确处理SIGINT能让程序实现优雅退出。我曾在数据库服务中实现过这样的处理逻辑#include signal.h #include stdio.h void sigint_handler(int sig) { printf(\n正在保存数据并安全退出...\n); // 执行清理操作 exit(0); } int main() { signal(SIGINT, sigint_handler); while(1) { // 主业务逻辑 } return 0; }这种处理方式避免了强制终止导致的数据损坏。不过要注意某些阻塞系统调用在收到信号后会返回EINTR错误需要特别处理。比如在socket编程中while ((n read(fd, buf, sizeof(buf))) -1 errno EINTR) continue; // 被信号中断后重试2.2 SIGUSR1/SIGUSR2自定义通信利器SIGUSR110和SIGUSR212是专门留给用户自定义用途的信号。我在日志系统开发中就用SIGUSR1实现了运行时日志级别切换的功能static int log_level LOG_INFO; void sigusr1_handler(int sig) { log_level (log_level LOG_DEBUG) ? LOG_INFO : LOG_DEBUG; syslog(LOG_NOTICE, 日志级别切换为%s, log_level LOG_DEBUG ? DEBUG : INFO); } // 注册信号处理器 signal(SIGUSR1, sigusr1_handler);使用时只需向进程发送信号kill -USR1 pid这种设计避免了重启服务就能修改配置的便利。SIGUSR2我通常用来触发核心业务逻辑的特定操作比如重新加载业务规则。3. 信号处理的高级技巧3.1 信号掩码与临界区保护在多线程环境下信号处理变得尤为复杂。我曾踩过这样的坑在信号处理函数中调用了非异步安全的函数导致随机崩溃。正确的做法是void safe_handler(int sig) { // 只做最简单的操作设置标志位 volatile sig_atomic_t flag 1; // 或者使用write这种异步安全函数 write(STDERR_FILENO, 信号收到\n, 12); }更安全的做法是使用sigaction替代signal函数struct sigaction sa; sa.sa_handler safe_handler; sigemptyset(sa.sa_mask); sa.sa_flags SA_RESTART; // 自动重启被中断的系统调用 if (sigaction(SIGINT, sa, NULL) -1) { perror(sigaction); exit(1); }3.2 信号与多进程协作在父子进程通信场景中SIGCHLD信号尤为重要。我曾见过因为没正确处理这个信号导致僵尸进程堆积的情况。正确的处理方式应该是void sigchld_handler(int sig) { int saved_errno errno; while (waitpid(-1, NULL, WNOHANG) 0) continue; errno saved_errno; } // 设置SA_NOCLDSTOP避免子进程停止时也收到信号 struct sigaction sa; sa.sa_handler sigchld_handler; sigemptyset(sa.sa_mask); sa.sa_flags SA_RESTART | SA_NOCLDSTOP;4. 实战案例构建可靠的信号处理系统4.1 日志轮转的信号实现很多日志系统支持接收信号后重新打开日志文件这在日志轮转时特别有用。下面是我在某个项目中实现的方案static int log_fd -1; void reopen_log(int sig) { int new_fd open(/var/log/service.log, O_WRONLY|O_APPEND|O_CREAT, 0644); if (new_fd 0) { dup2(new_fd, log_fd); close(new_fd); } } int main() { log_fd open(/var/log/service.log, O_WRONLY|O_APPEND|O_CREAT, 0644); struct sigaction sa; sa.sa_handler reopen_log; sigemptyset(sa.sa_mask); sa.sa_flags SA_RESTART; sigaction(SIGHUP, sa, NULL); // 主业务逻辑 }这样在需要轮转日志时管理员可以mv /var/log/service.log /var/log/service.log.old kill -HUP pid4.2 性能监控的信号方案利用SIGALRM可以实现简单的性能采样。比如每隔5秒输出当前请求数volatile sig_atomic_t request_count 0; void alarm_handler(int sig) { fprintf(stderr, 当前QPS: %d\n, request_count/5); request_count 0; alarm(5); // 重新设置定时器 } // 初始化 signal(SIGALRM, alarm_handler); alarm(5); // 在请求处理中 request_count;这种方案虽然简单但在很多场景下已经足够使用。需要注意的是alarm的精度是秒级如果需要更高精度可以考虑timer_create等机制。