处理 Linux 信号:进程控制与异常管理的核心

信号(signal)是 Linux/Unix 系统中最古老、最基础、最重要的进程间通信机制之一,同时也是现代多进程/多线程程序中处理异常、中断、终止、资源限制等场景的核心手段。

一、信号最本质的五种用途(按重要性排序)

  1. 通知进程外部事件发生(最常见)

    • Ctrl+C → SIGINT
    • Ctrl+\ → SIGQUIT
    • kill -9 → SIGKILL
    • 父进程结束 → SIGHUP(常用于守护进程重读配置)
  2. 实现进程间简单同步/通知

    • SIGUSR1 / SIGUSR2(用户自定义信号)
    • SIGCHLD(子进程退出/停止/继续)
  3. 处理异常和错误

    • SIGSEGV(段错误)
    • SIGFPE(浮点异常 / 除零)
    • SIGBUS(总线错误)
    • SIGILL(非法指令)
  4. 实现软终止 / 优雅退出

    • SIGTERM(请求终止,默认行为是退出)
    • SIGINT(键盘中断)
  5. 资源限制与超时控制

    • SIGXCPU(CPU 时间超限)
    • SIGXFSZ(文件大小超限)
    • SIGALRM(alarm() 定时器到期)

二、信号处理方式对比(最重要的分类)

处理方式 行为描述 是否可自定义 是否可靠 典型场景 是否阻塞其他信号
默认动作 操作系统预定义的行为 不可 大多数信号的初始状态
忽略(SIG_IGN) 直接丢弃该信号 后台进程忽略 SIGCHLD
捕获(用户函数) 调用用户注册的信号处理函数 中~高 清理资源、日志、优雅退出 看 sigaction 设置
默认不捕获 SIGKILL / SIGSTOP 永远不能被捕获或忽略 不可 强制杀死 / 强制暂停

三、现代推荐做法:使用 sigaction 而非 signal

// 强烈不推荐(古老且行为不可预测)
signal(SIGINT, handler);

// 推荐写法(POSIX 标准,可控性强)
struct sigaction sa;
sa.sa_handler = handler;           // 普通函数指针
// sa.sa_sigaction = handler_sig;  // 如果需要 SA_SIGINFO
sa.sa_flags = SA_RESTART | SA_SIGINFO;  // 常用组合
sigemptyset(&sa.sa_mask);          // 或 sigaddset 屏蔽其他信号

if (sigaction(SIGINT, &sa, NULL) == -1) {
    perror("sigaction");
}

常用 sa_flags 组合解释

标志位 含义 推荐场景
SA_RESTART 被信号打断的慢速系统调用(如 read、write)会自动重启 大多数网络/文件 IO 服务程序
SA_SIGINFO 传递更多信息(si_signo, si_code, si_addr 等) 调试段错误、非法地址访问
SA_NOCLDSTOP 不产生子进程停止/继续时的 SIGCHLD(只在退出时产生) 父进程只关心子进程死亡
SA_NOCLDWAIT 子进程退出时不产生僵尸进程(wait 不需要) 不需要回收子进程状态的场景
SA_NODEFER 在信号处理函数执行期间自动屏蔽本信号(允许信号嵌套) 极少数场景(慎用)

四、最常被捕获的信号一览表(2025 视角)

信号 默认行为 是否可捕获/忽略 常见捕获目的 是否推荐捕获
SIGINT 终止 优雅退出、保存数据、打印统计 强烈推荐
SIGTERM 终止 服务平滑关闭(最礼貌的 kill) 强烈推荐
SIGQUIT 终止+core dump 调试时产生 core 文件 视情况
SIGCHLD 忽略 回收子进程、waitpid 非阻塞 几乎必捕获
SIGPIPE 终止 忽略(网络编程常见)或记录日志 常忽略
SIGSEGV 终止+core dump 记录崩溃信息、生成更好诊断日志 推荐(谨慎)
SIGUSR1/2 终止 自定义行为(重载配置、打印状态、切换日志) 非常常用
SIGALRM 终止 超时控制(配合 alarm 或 setitimer) 常用
SIGKILL 强制杀死 不可
SIGSTOP 强制暂停 不可

五、经典代码模式(生产级)

volatile sig_atomic_t quit = 0;

static void graceful_shutdown(int sig) {
    quit = 1;
    // 可以在这里记录日志,但不要做耗时操作
}

int main() {
    struct sigaction sa;
    sa.sa_handler = graceful_shutdown;
    sa.sa_flags = SA_RESTART;
    sigemptyset(&sa.sa_mask);
    
    // 同时捕获 SIGINT 和 SIGTERM
    sigaction(SIGINT,  &sa, NULL);
    sigaction(SIGTERM, &sa, NULL);
    
    // 忽略 SIGPIPE(网络断开常见)
    signal(SIGPIPE, SIG_IGN);
    
    while (!quit) {
        // 主循环
        sleep(1);
    }
    
    printf("正在优雅退出...\n");
    // 清理资源、保存状态、通知其他模块
    cleanup();
    return 0;
}

六、生产环境常见信号处理策略总结

场景 推荐信号处理策略 备注
长连接服务器 捕获 SIGINT/SIGTERM → 优雅关闭 设置 quit 标志,逐步关闭连接
守护进程 捕获 SIGHUP → 重读配置文件 经典做法
批处理/命令行工具 捕获 SIGINT → 打印进度或部分结果后退出 用户体验更好
高性能服务 SIGUSR1 → 打印内部状态/连接数/延迟统计 运维常用
SIGCHLD 处理 使用 SA_NOCLDWAIT 或非阻塞 waitpid 避免僵尸进程
崩溃诊断 SIGSEGV/SIGABRT → 记录栈、环境、寄存器信息 生成自定义 crash report

如果你现在正在写某一类程序(网络服务、命令行工具、守护进程、容器 init 进程等),可以告诉我,我可以给你更针对性的信号处理方案和代码模板。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐