文章目录

C 语言信号处理:signal() 函数与信号捕获

发布于 2026-04-01 23:45:12 · 浏览 10 次 · 评论 0 条

C 语言信号处理:signal() 函数与信号捕获

在 Linux 或类 Unix 系统中运行 C 程序时,程序可能会收到来自操作系统的“信号”(Signal),比如用户按下 Ctrl + C 发送的中断信号。如果不做处理,程序会直接终止。使用 signal() 函数可以捕获这些信号,并指定自定义的处理方式,让程序优雅地响应或忽略它们。


1. 理解信号的基本概念

信号是操作系统通知进程发生了某种事件的一种机制。常见的信号包括:

  • SIGINT:由用户按下 Ctrl + C 触发,表示“中断”。
  • SIGTERM:请求程序终止(比如执行 kill 命令)。
  • SIGKILL:强制终止程序(无法被捕获或忽略)。
  • SIGUSR1 / SIGUSR2:用户自定义信号,可用于进程间通信。

注意:不是所有信号都能被捕获。例如 SIGKILLSIGSTOP 是不能被 signal() 处理的。


2. 使用 signal() 函数注册信号处理器

signal() 函数的原型在 <signal.h> 头文件中定义:

#include <signal.h>

sighandler_t signal(int signum, sighandler_t handler);

其中:

  • signum 是要捕获的信号编号(如 SIGINT)。
  • handler 是一个函数指针,指向你自定义的处理函数。

编写步骤如下

  1. 包含头文件:在代码开头添加 #include <signal.h>
  2. 定义处理函数:该函数必须返回 void,且接受一个 int 参数(即信号编号)。
  3. 调用 signal():将信号和处理函数关联起来。
  4. 编写主程序逻辑:确保程序不会立即退出,以便测试信号处理。

下面是一个完整示例,捕获 SIGINT(即 Ctrl + C):

#include <stdio.h>
#include <signal.h>
#include <unistd.h>

void handle_sigint(int sig) {
    printf("\n收到 SIGINT 信号 (%d),程序不会立即退出。\n", sig);
    printf("再按一次 Ctrl+C 才退出。\n");
    signal(SIGINT, SIG_DFL); // 恢复默认行为
}

int main() {
    signal(SIGINT, handle_sigint);

    printf("程序运行中... 按 Ctrl+C 测试信号捕获。\n");
    while (1) {
        sleep(1);
    }

    return 0;
}

编译并运行

gcc -o sigtest sigtest.c
./sigtest

第一次按 Ctrl + C 时,程序会打印提示信息但继续运行;第二次按 Ctrl + C 才会真正退出,因为此时已恢复默认行为(SIG_DFL)。


3. signal() 的局限性与注意事项

虽然 signal() 简单易用,但它存在一些问题:

  • 不可靠:在某些系统上,信号处理函数执行后,信号的处理方式可能自动重置为默认(SIG_DFL),导致后续相同信号无法被捕获。
  • 可移植性差:不同 Unix 系统对 signal() 的实现不一致。
  • 功能有限:无法设置额外选项(如是否重启被中断的系统调用)。

因此,在生产代码中,更推荐使用 sigaction() 函数,它提供了更精确、可靠和可移植的信号处理机制。但对于学习或简单脚本,signal() 足够直观。


4. 常见信号及其默认行为

下表列出了常用信号及其默认动作,帮助你判断哪些信号适合捕获:

信号名 编号 默认行为 是否可捕获
SIGINT 2 终止进程
SIGQUIT 3 终止进程并生成 core 文件
SIGTERM 15 终止进程
SIGKILL 9 强制终止(无条件)
SIGSTOP 19 暂停进程
SIGUSR1 10 无默认行为
SIGUSR2 12 无默认行为

注意:信号编号在不同系统上可能略有差异,应始终使用符号常量(如 SIGINT)而非硬编码数字。


5. 实战:让程序安全退出

很多后台程序需要在收到终止信号时清理资源(如关闭文件、释放内存)。下面是一个安全退出的示例:

#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>

volatile sig_atomic_t keep_running = 1;

void cleanup_handler(int sig) {
    keep_running = 0; // 设置标志,主循环将退出
}

int main() {
    signal(SIGINT, cleanup_handler);
    signal(SIGTERM, cleanup_handler);

    FILE *log = fopen("app.log", "w");
    if (!log) {
        perror("无法创建日志文件");
        exit(1);
    }

    fprintf(log, "程序启动\n");
    fflush(log);

    while (keep_running) {
        fprintf(log, "工作进行中...\n");
        fflush(log);
        sleep(2);
    }

    fprintf(log, "收到终止信号,正在清理...\n");
    fclose(log);
    printf("程序已安全退出。\n");

    return 0;
}

关键点

  • 使用 volatile sig_atomic_t 类型的全局变量作为退出标志,确保信号处理函数与主线程之间的可见性。
  • 在信号处理函数中只做最简单的操作(如设置标志),避免调用 printfmalloc 等非异步信号安全函数。
  • 主循环定期检查标志,决定是否退出。

6. 避免在信号处理函数中做复杂操作

信号处理函数是在“中断上下文”中执行的,此时主程序可能处于任意状态。因此,只能调用“异步信号安全”的函数。根据 POSIX 标准,以下函数是安全的(部分):

  • _exit()
  • write()
  • read()
  • kill()
  • signal()
  • sigaction()

而以下函数不安全,禁止在信号处理函数中使用:

  • printf()
  • malloc()
  • free()
  • exit()
  • 大多数标准 I/O 函数

因此,最佳实践是:在信号处理函数中仅设置一个全局标志,由主程序在安全时机处理


7. 忽略信号

除了自定义处理,你还可以选择忽略某个信号:

signal(SIGINT, SIG_IGN); // 忽略 SIGINT

此后,即使用户按下 Ctrl + C,程序也不会中断。这在某些守护进程或批处理任务中有用。


8. 恢复默认行为

如果之前修改了信号处理方式,可以通过以下方式恢复:

signal(SIGINT, SIG_DFL); // 恢复默认行为(通常是终止进程)

这在临时屏蔽信号后再恢复时很有用。


评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文