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

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

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

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

  1. 通知进程外部事件发生(最常见)
  • Ctrl+C → SIGINT
  • Ctrl+\ → SIGQUIT
  • kill -9 → SIGKILL
  • 父进程结束 → SIGHUP(常用于守护进程重读配置)
  1. 实现进程间简单同步/通知
  • SIGUSR1 / SIGUSR2(用户自定义信号)
  • SIGCHLD(子进程退出/停止/继续)
  1. 处理异常和错误
  • SIGSEGV(段错误)
  • SIGFPE(浮点异常 / 除零)
  • SIGBUS(总线错误)
  • SIGILL(非法指令)
  1. 实现软终止 / 优雅退出
  • SIGTERM(请求终止,默认行为是退出)
  • SIGINT(键盘中断)
  1. 资源限制与超时控制
  • 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 进程等),可以告诉我,我可以给你更针对性的信号处理方案和代码模板。

文章已创建 4298

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部