C 库函数 – sigaction()(手把手讲解)

更新时间:

💡一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

  • 新项目:《从零手撸:仿小红书(微服务架构)》 正在持续爆肝中,基于 Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...点击查看项目介绍 ;
  • 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/ ;

截止目前, 星球 内专栏累计输出 82w+ 字,讲解图 3441+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 2900+ 小伙伴加入学习 ,欢迎点击围观

前言:信号处理的基石

在 C 语言编程中,信号(Signal)是操作系统与程序之间的重要通信机制。它们如同程序运行时的“紧急电话”,用于通知程序发生特定事件,例如用户按下 Ctrl+C 终止程序,或内存访问越界等异常情况。而 sigaction() 函数正是处理这类信号的核心工具。相较于传统的 signal() 函数,sigaction() 提供了更精细的控制和更可靠的信号处理能力,因此成为现代 C 程序员必须掌握的技能之一。本文将从信号的基础概念出发,逐步解析 sigaction() 的用法与原理,帮助读者在实际开发中灵活应用这一功能。


信号的基础概念:理解程序的“紧急电话”

什么是信号?

信号是操作系统向进程发送的异步事件通知。例如:

  • SIGINT:用户按下 Ctrl+C 发送的中断信号。
  • SIGSEGV:程序访问非法内存地址时触发。
  • SIGTERM:系统要求程序终止的请求。

这些信号如同电话铃声,当程序接收到信号后,需要通过特定的“接电话逻辑”(即信号处理函数)来响应。如果不处理,系统会按默认方式处置,例如直接终止程序。

信号处理的挑战

直接使用 signal() 函数可能存在以下问题:

  1. 不可重入性:某些函数在信号处理期间不可调用,可能导致程序崩溃。
  2. 行为不确定性:在部分系统中,signal() 可能会重置信号处理函数。
  3. 信号屏蔽:无法灵活控制信号的递达顺序。

sigaction() 函数通过结构体参数提供了对信号行为的全面控制,解决了上述问题。


sigaction() 函数详解:结构与参数解析

函数原型与结构体

#include <signal.h>

int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

该函数通过 struct sigaction 结构体定义信号行为,其关键字段如下:

字段名作用说明
sa_handler指向信号处理函数的指针,或预定义的 SIG_IGN(忽略信号)、SIG_DFL(默认行为)
sa_sigactionSA_SIGINFO 标志启用时,指向扩展信号处理函数的指针
sa_mask在信号处理期间临时阻塞的信号集合
sa_flags控制信号行为的标志位集合

核心参数详解

  1. sa_handlersa_sigaction

    • sa_handler 用于传统信号处理,仅接收信号编号参数。
    • sa_sigaction 需配合 SA_SIGINFO 标志,可接收额外的 siginfo_t 结构体,包含信号来源等信息。

    比喻
    sa_handler 比作接电话时只记录来电号码,而 sa_sigaction 则能获取来电者的详细信息(如姓名、来电时间)。

  2. sa_mask:信号屏蔽机制
    当处理某个信号时,程序可能暂时阻塞其他信号的递达。例如,若处理 SIGINT 时屏蔽 SIGTERM,可避免两者同时执行导致的竞态条件。

  3. sa_flags 标志位
    常用标志包括:

    • SA_RESTART:允许系统调用自动恢复,而非返回错误。
    • SA_NODEFER:在处理信号期间不自动屏蔽该信号。
    • SA_SIGINFO:启用 sa_sigaction 的扩展功能。

使用示例:从简单到复杂场景

示例1:捕获 Ctrl+C 信号

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

void interrupt_handler(int sig) {
    printf("Received signal %d, program exiting...\n", sig);
    exit(EXIT_SUCCESS);
}

int main() {
    struct sigaction act;
    act.sa_handler = interrupt_handler;
    sigemptyset(&act.sa_mask); // 不屏蔽其他信号
    act.sa_flags = 0;
    
    if (sigaction(SIGINT, &act, NULL) == -1) {
        perror("sigaction");
        return 1;
    }

    while(1) {
        printf("Running... Press Ctrl+C to exit.\n");
        sleep(1);
    }
    return 0;
}

运行效果
程序循环输出信息,按下 Ctrl+C 后触发 interrupt_handler,程序优雅退出。

示例2:获取信号来源信息

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

void detailed_handler(int sig, siginfo_t *info, void *context) {
    printf("Signal %d received from PID %d, UID %d\n",
           sig, info->si_pid, info->si_uid);
}

int main() {
    struct sigaction act;
    act.sa_sigaction = detailed_handler;
    sigemptyset(&act.sa_mask);
    act.sa_flags = SA_SIGINFO; // 启用扩展处理
    
    if (sigaction(SIGUSR1, &act, NULL) == -1) {
        perror("sigaction");
        return 1;
    }

    pause(); // 等待信号
    return 0;
}

使用场景
通过另一个终端发送信号:

kill -SIGUSR1 <PID>

程序将输出发送信号的进程和用户信息。


常见问题与注意事项

问题1:信号处理函数中能调用哪些函数?

在信号处理函数中,只能调用异步信号安全的函数(如 write()signal() 等)。若需执行复杂操作,可设置标志位后退出处理函数,再在主程序中响应。

问题2:如何避免信号丢失?

通过合理设置 sa_masksa_flags,例如:

sigaddset(&act.sa_mask, SIGTERM); // 阻塞 SIGTERM 信号
act.sa_flags |= SA_RESTART; // 允许系统调用自动恢复

问题3:sigaction()signal() 的区别?

  • 功能范围sigaction() 支持更复杂的配置(如 sa_masksa_sigaction)。
  • 行为一致性sigaction() 在不同系统中的表现更可预测。
  • 重置问题signal() 在某些系统中可能重置处理函数,导致信号无法再次触发。

进阶技巧:信号排队与实时信号

信号排队机制

当多个相同信号连续到达时,系统默认仅保留一个。若启用 SA_NODEFER 标志:

act.sa_flags |= SA_NODEFER; // 允许同一信号多次触发

此时,每个信号都会被独立处理,适用于需要记录所有触发次数的场景。

实时信号的应用

实时信号(如 SIGRTMINSIGRTMAX)允许携带附加数据。通过 sigqueue() 函数发送带数据的信号:

#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>

int main() {
    int data = 42;
    if (sigqueue(getpid(), SIGRTMIN, (union sigval){.sival_int = data}) == -1) {
        perror("sigqueue");
    }
    // 处理函数通过 info->si_value 获取数据
    return 0;
}

结论:掌握信号处理的核心工具

sigaction() 函数如同程序的“信号管理中枢”,通过灵活配置 struct sigaction 结构体,开发者能够精确控制信号的响应逻辑、屏蔽策略和行为模式。从基础的中断处理到复杂的多信号协作场景,这一函数提供了坚实的底层支持。建议读者通过实际编写示例代码加深理解,例如尝试实现一个能同时响应多个信号并记录日志的程序。掌握 sigaction() 不仅能提升代码的健壮性,更是迈向系统级编程的重要一步。

最新发布