当前位置: 首页 > news >正文

UNIX下C语言编程与实践38-UNIX 信号操作:signal 函数与信号捕获函数的编写

从函数使用到捕获函数规范,掌握 UNIX 信号处理的基础核心

一、核心认知:signal 函数的功能与定位

在 UNIX 信号处理中,signal 函数是最基础、最常用的接口,定义在 <signal.h> 头文件中。其核心功能是“为指定信号设置处理动作”——通过该函数,进程可决定收到信号后的行为:执行系统默认动作、忽略信号,或调用自定义的信号捕获函数处理。

signal 函数是早期 UNIX 系统为简化信号处理设计的轻量级接口,虽存在兼容性和功能局限性,但因使用简单,至今仍是快速实现信号处理的首选工具(复杂场景需结合 sigaction 函数)。

二、signal 函数的完整解析:原型、参数与返回值

掌握 signal 函数的参数含义和返回值逻辑,是正确使用该函数的前提。其设计简洁但需注意细节(如参数取值范围、返回值判断)。

1. 函数原型

#include <signal.h>// 功能:为信号 sig 设置处理动作,返回之前的处理动作 
// 本质:修改内核中该信号的处理函数指针,实现信号行为的自定义
void (*signal(int sig, void (*f)(int)))(int);// 简化理解:函数返回值为"参数是 int、返回值是 void"的函数指针 
// 可通过 typedef 简化声明(推荐)
typedef void (*sig_handler_t)(int);  // 定义信号处理函数类型
sig_handler_t signal(int sig, sig_handler_t f);

2. 核心参数解析

signal 函数有两个参数,分别控制“目标信号”和“处理动作”,参数取值有严格规范:

参数取值范围功能说明典型示例
sig
(信号编号)
1~31(非可靠信号)系统预定义信号,如 SIGINT(2)、SIGTERM(15)、SIGUSR1(10),每个信号对应固定事件signal(SIGINT, ...)(处理 Ctrl+C 中断信号)
34~64(可靠信号)POSIX 标准扩展信号(SIGRTMIN~SIGRTMAX),支持信号排队,避免丢失signal(SIGRTMIN + 1, ...)(处理自定义可靠信号)
无效值(如 0、65+)函数调用失败,返回 SIG_ERR,设置 errno 为 EINVALsignal(0, ...)(无效信号,调用失败)
f
(处理动作)
SIG_DFLDefault:设置信号为系统默认处理动作(如 SIGINT 默认终止进程,SIGSEGV 默认终止并生成 core 文件)signal(SIGINT, SIG_DFL)(恢复 Ctrl+C 默认终止行为)
SIG_IGNIgnore:设置信号为忽略动作,收到信号后内核直接丢弃,不影响进程执行signal(SIGTSTP, SIG_IGN)(忽略 Ctrl+Z 暂停信号)
自定义函数地址Catch:设置信号为捕获动作,收到信号时调用自定义函数(需符合 void (*)(int) 类型)signal(SIGUSR1, my_handler)(调用 my_handler 处理 SIGUSR1 信号)

3. 返回值与错误处理

signal 函数的返回值是“之前的信号处理动作”,需通过返回值判断调用是否成功,以及恢复原处理动作:

  • 成功返回:返回调用前信号的处理动作(可能是 SIG_DFL、SIG_IGN 或之前注册的函数指针),可用于后续恢复原处理方式;
  • 失败返回:返回 SIG_ERR(本质是 (void (*)(int))-1),同时设置 errno 标识错误原因,常见错误码:
    • EINVAL:信号编号无效(如 0、65+)或信号不可捕获(如 SIGKILL、SIGSTOP);
    • EFAULT:处理函数地址 f 指向无效内存(如 NULL 或非法地址)。
实例:signal 函数返回值的使用(保存并恢复原处理动作)
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <errno.h>// 自定义 SIGINT 处理函数
void temp_sigint_handler(int sig) {printf("\n临时处理 SIGINT 信号,3 秒后恢复默认行为\n");sleep(3);// 恢复 SIGINT 默认处理动作signal(SIGINT, SIG_DFL);
}int main() {sig_handler_t old_handler;// 1. 保存 SIGINT 原处理动作,设置临时处理函数old_handler = signal(SIGINT, temp_sigint_handler);if (old_handler == SIG_ERR) {perror("signal 设置临时处理函数失败");return 1;}printf("已设置临时 SIGINT 处理函数,按下 Ctrl+C 测试(5 秒后自动恢复)\n");sleep(5);// 2. 手动恢复原处理动作(若 5 秒内未触发信号)if (signal(SIGINT, old_handler) == SIG_ERR) {perror("signal 恢复原处理函数失败");return 1;}printf("已恢复 SIGINT 原处理动作,按下 Ctrl+C 会默认终止程序\n");sleep(5);return 0;
}

编译与运行

gcc signal_return.c -o signal_return
./signal_return

输出示例

已设置临时 SIGINT 处理函数,按下 Ctrl+C 测试(5 秒后自动恢复)
^C
临时处理 SIGINT 信号,3 秒后恢复默认行为
已恢复 SIGINT 原处理动作,按下 Ctrl+C 会默认终止程序
^C
Terminated

关键价值:通过保存返回值,可在临时处理信号后恢复原动作(如临时忽略信号,处理完关键逻辑后恢复默认行为),确保程序行为的灵活性和正确性。

三、实战:signal 函数的三种核心使用方式

signal 函数的使用围绕“处理动作”展开,分为“默认处理”“忽略信号”“捕获信号”三种场景,每种场景对应不同的业务需求,以下通过实例详细演示。

方式 1:设置信号为默认处理(SIG_DFL)

适用场景:需恢复信号的系统默认行为(如之前修改过信号处理动作,后续需还原),或明确指定信号按默认逻辑处理。

实例:恢复 SIGINT 默认终止行为

代码格式化

#include <stdio.h>
#include <signal.h>
#include <unistd.h>// 临时忽略 SIGINT 的函数
void ignore_sigint() {printf("临时忽略 SIGINT 信号(10 秒内 Ctrl+C 无效)\n");signal(SIGINT, SIG_IGN);sleep(10);// 10 秒后恢复默认处理signal(SIGINT, SIG_DFL);printf("已恢复 SIGINT 默认行为,按下 Ctrl+C 终止程序\n");
}int main() {ignore_sigint();// 恢复后等待信号while (1) {sleep(1);}return 0;
}

运行输出示例

临时忽略 SIGINT 信号(10 秒内 Ctrl+C 无效)
^C^C// 10 秒内按下 Ctrl+C 无反应
已恢复 SIGINT 默认行为,按下 Ctrl+C 终止程序
^C Terminated

方式 2:忽略信号(SIG_IGN)

适用场景:需屏蔽特定信号对进程的影响(如后台服务忽略 Ctrl+Z 暂停信号、忽略 SIGCHLD 避免僵死进程),确保进程不受干扰地执行。

实例:忽略 SIGTSTP(Ctrl+Z)和 SIGCHLD 信号
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>int main() {// 1. 忽略 SIGTSTP 信号(Ctrl+Z 无效)if (signal(SIGTSTP, SIG_IGN) == SIG_ERR) {perror("signal 忽略 SIGTSTP 失败");return 1;}printf("已忽略 SIGTSTP 信号(Ctrl+Z 无效)\n");// 2. 忽略 SIGCHLD 信号(部分系统自动回收子进程,避免僵死)if (signal(SIGCHLD, SIG_IGN) == SIG_ERR) {perror("signal 忽略 SIGCHLD 失败");return 1;}printf("已忽略 SIGCHLD 信号(子进程终止后自动回收)\n");// 创建子进程,验证僵死预防pid_t pid = fork();if (pid == -1) {perror("fork 失败");return 1;}if (pid == 0) {printf("子进程 PID = %d,3 秒后终止\n", getpid());sleep(3);exit(EXIT_SUCCESS);}// 主进程不调用 wait,观察子进程是否僵死printf("父进程 PID = %d,等待 10 秒(可通过 ps 查看子进程状态)\n", getpid());sleep(10);printf("父进程退出\n");return 0;
}

运行输出:

已忽略 SIGTSTP 信号(Ctrl+Z 无效)
已忽略 SIGCHLD 信号(子进程终止后自动回收)
子进程 PID = 1235,3 秒后终止
父进程 PID = 1234,等待 10 秒(可通过 ps 查看子进程状态)
^Z                  // Ctrl+Z 无反应
父进程退出

验证:子进程终止后,执行 ps aux | grep 1235 | grep -v grep,无输出(子进程已被自动回收,未产生僵死进程)。

方式 3:捕获信号(自定义函数)

适用场景:需对信号进行自定义处理(如收到终止信号后清理资源、收到用户信号后执行特定任务),是 signal 函数最灵活的使用方式。

实例:捕获 SIGUSR1/SIGUSR2 信号并计数

代码示例

#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>// 全局变量:统计信号接收次数(需用 volatile 确保不被编译器优化)
volatile int sigusr1_count = 0;
volatile int sigusr2_count = 0;// 自定义信号捕获函数
void sig_handler(int sig) {switch (sig) {case SIGUSR1:sigusr1_count++;printf("捕获到 SIGUSR1 信号,累计次数:%d\n", sigusr1_count);break;case SIGUSR2:sigusr2_count++;printf("捕获到 SIGUSR2 信号,累计次数:%d\n", sigusr2_count);break;default:printf("捕获到未知信号:%d\n", sig);break;}
}int main() {// 注册 SIGUSR1 和 SIGUSR2 捕获函数(共享同一个处理函数)if (signal(SIGUSR1, sig_handler) == SIG_ERR || signal(SIGUSR2, sig_handler) == SIG_ERR) {perror("signal 注册捕获函数失败");return 1;}printf("程序启动,PID = %d\n", getpid());printf("发送 SIGUSR1:kill -10 %d\n", getpid());printf("发送 SIGUSR2:kill -12 %d\n", getpid());printf("按下 Ctrl+C 终止程序\n");// 主循环,持续运行等待信号while (1) {sleep(1);}return 0;
}

编译与运行步骤

  1. 编译程序

    gcc signal_catch.c -o signal_catch
    

  2. 启动程序

    ./signal_catch
    

  3. 发送信号
    在新终端执行以下命令(假设程序 PID 为 1234):

    kill -10 $(pgrep signal_catch)  # 发送 SIGUSR1
    kill -12 $(pgrep signal_catch)  # 发送 SIGUSR2
    kill -10 $(pgrep signal_catch)  # 再次发送 SIGUSR1
    

预期输出示例

程序启动,PID = 1234
发送 SIGUSR1:kill -10 1234
发送 SIGUSR2:kill -12 1234
按下 Ctrl+C 终止程序
捕获到 SIGUSR1 信号,累计次数:1
捕获到 SIGUSR2 信号,累计次数:1
捕获到 SIGUSR1 信号,累计次数:2
^C Terminated

三、信号捕获函数的编写规范与注意事项

自定义信号捕获函数是信号处理的核心,但因信号的异步特性,函数编写需遵循严格规范,否则易导致程序崩溃、数据错乱等问题。

1. 捕获函数的基础规范

捕获函数必须满足的语法规则

  • 函数签名固定:必须符合 void (*handler)(int) 类型,即“参数为 int(信号编号)、返回值为 void”,示例:
    void my_handler(int sig) { ... }
  • 参数用途明确:参数 sig 为当前收到的信号编号,可用于区分多个信号共享同一个处理函数(如前文示例中通过 switch 处理 SIGUSR1 和 SIGUSR2);
  • 避免返回值:返回值为 void,不可返回任何数据,若需传递状态,需通过全局变量或静态变量(需加 volatile 修饰,避免编译器优化)。

2. 捕获函数的关键注意事项

  • 严禁调用不可重入函数

    信号捕获函数执行时可能中断主流程的任意操作,若调用“不可重入函数”(如 mallocfreeprintffopen),会导致全局数据结构损坏(如 malloc 维护的内存链表被中断后错乱)。

    可重入函数列表(安全调用):writeclose_exitsigactionmemcpymemset 等(完整列表参考 man 7 signal);
    不可重入函数列表(禁止调用):mallocfreeprintffprintfstrtoktime 等。

    替代方案:若需日志输出,用 write(STDOUT_FILENO, ...) 替代 printf;若需复杂逻辑,在捕获函数中仅设置“事件标记”(如 volatile int flag = 1),主流程轮询标记并执行逻辑。

  • 全局变量需加 volatile 修饰

    编译器会优化未被修改的全局变量(如缓存到寄存器),若捕获函数修改全局变量(如信号计数),主流程可能无法感知变化。需用 volatile 修饰变量,告知编译器“变量可能被异步修改,每次访问需从内存读取”。

    错误示例:int sig_count = 0;(无 volatile,主流程可能读取缓存值);
    正确示例:volatile int sig_count = 0;(有 volatile,确保数据同步)。

  • 重新注册信号处理函数(兼容旧系统)

    部分旧 UNIX 系统(如 BSD)中,信号捕获函数执行一次后会自动恢复为默认动作(SIG_DFL),导致后续信号按默认方式处理(如首次捕获 SIGINT 后,再次按下 Ctrl+C 会终止进程)。

    兼容方案:在捕获函数开头重新注册自身,确保后续信号仍能被捕获,示例:

    void sig_handler(int sig) { signal(sig, sig_handler); // 重新注册,兼容旧系统 printf("捕获到信号:%d\n", sig); }

  • 避免耗时操作

    捕获函数执行期间,进程会暂停原流程,若函数中存在 sleep、循环等耗时操作,会导致主流程长时间阻塞。建议捕获函数仅执行“快速动作”(如设置标记、记录日志、清理资源),耗时逻辑移到主流程。

规范的捕获函数示例(安全处理信号)

代码示例

#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>// 全局事件标记(volatile 修饰)
volatile int exit_flag = 0;
volatile int rotate_flag = 0;// 安全日志输出函数(用 write 替代 printf)
void safe_log(const char *msg) {write(STDOUT_FILENO, msg, strlen(msg));write(STDOUT_FILENO, "\n", 1);
}// 规范的捕获函数
void sig_handler(int sig) {// 1. 重新注册函数(兼容旧系统)signal(sig, sig_handler);// 2. 仅执行快速动作,设置标记switch (sig) {case SIGINT:case SIGTERM:safe_log("收到终止信号,设置退出标记");exit_flag = 1;break;case SIGUSR1:safe_log("收到日志轮转信号,设置轮转标记");rotate_flag = 1;break;default:char buf[64];snprintf(buf, sizeof(buf), "收到未知信号:%d", sig);safe_log(buf);break;}
}// 主流程:轮询标记并执行复杂逻辑
void main_loop() {while (1) {// 检查退出标记if (exit_flag) {safe_log("主流程检测到退出标记,清理资源后退出");// 执行资源清理(如关闭文件、释放内存)sleep(2);safe_log("资源清理完成,程序退出");exit(EXIT_SUCCESS);}// 检查日志轮转标记if (rotate_flag) {safe_log("主流程检测到轮转标记,执行日志轮转");// 执行日志轮转(如重命名旧日志)sleep(1);safe_log("日志轮转完成");rotate_flag = 0; // 重置标记}// 主业务逻辑safe_log("主业务逻辑执行中...");sleep(3);}
}int main() {// 注册信号捕获函数if (signal(SIGINT, sig_handler) == SIG_ERR ||signal(SIGTERM, sig_handler) == SIG_ERR ||signal(SIGUSR1, sig_handler) == SIG_ERR) {perror("signal 注册失败");return 1;}safe_log("程序启动,PID = %d", getpid());safe_log("发送终止信号:kill -2 %d 或 kill -15 %d", getpid(), getpid());safe_log("发送轮转信号:kill -10 %d", getpid());// 启动主流程main_loop();return 0;
}

运行示例

程序启动,PID = 1234
发送终止信号:kill -2 1234 或 kill -15 1234
发送轮转信号:kill -10 1234
主业务逻辑执行中...
主业务逻辑执行中...
收到日志轮转信号,设置轮转标记
主流程检测到轮转标记,执行日志轮转
日志轮转完成
主业务逻辑执行中...
收到终止信号,设置退出标记
主流程检测到退出标记,清理资源后退出
资源清理完成,程序退出

规范点:捕获函数仅设置标记,主流程处理复杂逻辑;用 write 替代 printf;全局变量加 volatile;重新注册函数兼容旧系统,符合所有编写规范。

四、signal 函数的局限性与常见错误

signal 函数虽简单易用,但存在兼容性差、功能有限等局限性,且使用中易因参数错误、逻辑疏漏导致问题。

1. signal 函数的核心局限性

  • 跨系统兼容性差

    不同 UNIX 系统对 signal 函数的行为定义不一致:

    • Linux 系统:捕获函数注册后持续有效,无需重新注册;
    • BSD 系统:捕获函数执行一次后自动恢复为 SIG_DFL,需重新注册;
    • Solaris 系统:部分信号(如 SIGALRM)的处理行为与其他系统差异较大。

    这导致依赖 signal 函数的程序在不同系统中行为不一致,需额外适配。

  • 不支持信号掩码与排队

    signal 函数无法设置“信号掩码”(处理函数执行期间阻塞的其他信号),易导致信号嵌套触发(如处理函数执行时被其他信号中断);同时不支持可靠信号的排队机制,非可靠信号(1~31)短时间内多次发送会丢失。

  • 无法获取信号附加信息

    捕获函数仅能获取信号编号,无法获取信号的附加信息(如发送者 PID、信号携带的数据),无法满足进程间传递额外信息的需求(如分布式系统中的任务 ID 传递)。

  • 不支持自动重启系统调用

    当进程阻塞在可中断系统调用(如 readaccept)时,收到信号后系统调用会返回 -1,错误码为 EINTR,需手动重试;signal 函数无法配置“自动重启”,需额外编写重试逻辑。

2. 使用 signal 函数的常见错误与解决方法

常见错误问题现象原因分析解决方法
信号编号错误signal 调用失败,返回 SIG_ERR,perror 提示“Invalid argument”1. 信号编号为 0 或超过系统最大信号编号(如 65+);
2. 信号不可捕获(如 SIGKILL=9、SIGSTOP=19),设置捕获函数时失败
1. 用 kill -l 查看系统支持的信号编号,确保参数有效;
2. 避免对 SIGKILL、SIGSTOP 调用 signal 函数(这两个信号不可捕获、不可忽略);
3. 示例:if (sig < 1 || sig > SIGRTMAX) { fprintf(stderr, "无效信号编号\n"); }
捕获函数中调用不可重入函数程序随机崩溃、日志错乱、内存泄漏,错误难以复现捕获函数执行时中断主流程的不可重入函数(如 malloc),导致全局数据结构损坏(如内存链表断裂)1. 捕获函数中仅使用可重入函数(如 write、close);
2. 复杂逻辑(如日志、内存操作)移到主流程,捕获函数仅设置标记;
3. 用工具(如 valgrind)检测内存错误,定位不可重入函数调用
未检查返回值signal 调用失败未察觉,后续信号按默认方式处理(如预期捕获却终止进程)开发者默认 signal 调用成功,未判断返回值是否为 SIG_ERR,忽略了“信号编号无效”“权限不足”等错误场景1. 必须检查 signal 返回值,失败时打印错误信息并处理;
2. 示例:
if (signal(SIGUSR1, handler) == SIG_ERR) { perror("signal 失败"); exit(1); }
全局变量未加 volatile 修饰捕获函数修改全局变量后,主流程读取的值未更新(如计数始终为 0)编译器优化未被修改的全局变量,将其缓存到寄存器,捕获函数异步修改内存中的变量后,主流程仍读取寄存器缓存值1. 所有被捕获函数修改的全局/静态变量,均需加 volatile 修饰;
2. 示例:volatile int count = 0;(而非 int count = 0;
系统调用未处理 EINTR 错误进程阻塞在 read、accept 等系统调用时,收到信号后直接退出,未完成预期 I/O 操作可中断系统调用被信号中断后返回 -1,错误码为 EINTR,未手动重试,导致 I/O 逻辑中断1. 对可中断系统调用,检查错误码为 EINTR 时重试;
2. 示例:
while ((n = read(fd, buf, sizeof(buf))) == -1 && errno == EINTR); // 重试
3. 复杂场景改用 sigaction 函数,设置 SA_RESTART 标志自动重启系统调用

五、拓展:sigaction 函数——signal 的增强替代方案

为解决 signal 函数的局限性,POSIX 标准定义了 sigaction 函数,支持信号掩码、自动重启、可靠信号、附加信息获取等功能,是复杂场景下的首选接口。

1. sigaction 函数原型与核心参数

#include <signal.h>// 功能:设置或获取信号处理动作,支持信号掩码、自动重启等增强功能
// 返回值:成功返回 0,失败返回 -1,设置 errno
int sigaction(int sig, const struct sigaction *act, struct sigaction *oldact);// 信号处理动作结构体(核心参数)
struct sigaction {void (*sa_handler)(int);                 // 基础捕获函数(同 signal 的 f 参数)void (*sa_sigaction)(int, siginfo_t *, void *); // 增强捕获函数(获取附加信息)sigset_t sa_mask;                        // 信号掩码:处理函数执行期间阻塞的信号int sa_flags;                            // 功能标志(如自动重启、使用增强函数)void (*sa_restorer)(void);               // 已废弃,无需使用
};// 增强捕获函数的 siginfo_t 结构体(存储信号附加信息)
typedef struct siginfo_t {int si_signo;                            // 信号编号pid_t si_pid;                            // 信号发送者 PIDuid_t si_uid;                            // 信号发送者 UIDvoid *si_addr;                           // 触发信号的内存地址(如 SIGSEGV 时的无效地址)int si_value;                            // 信号携带的整数数据(自定义信号时传递)// ... 其他字段(按需使用)
} siginfo_t;

2. sigaction 的核心优势与使用示例

优势 1:设置信号掩码,避免嵌套中断

通过 sa_mask 设置处理函数执行期间阻塞的信号,防止其他信号中断处理函数,示例:

代码示例

#include <stdio.h>
#include <signal.h>
#include <unistd.h>void sig_handler(int sig) {printf("捕获到信号 %d,处理中(2 秒内阻塞其他信号)...\n", sig);sleep(2); // 模拟处理耗时printf("信号 %d 处理完成\n", sig);
}int main() {struct sigaction sa;// 1. 初始化信号掩码:处理函数执行期间阻塞 SIGINT 和 SIGUSR1sigemptyset(&sa.sa_mask); // 清空掩码sigaddset(&sa.sa_mask, SIGINT); // 添加 SIGINT 到掩码sigaddset(&sa.sa_mask, SIGUSR1); // 添加 SIGUSR1 到掩码// 2. 设置处理函数和标志sa.sa_handler = sig_handler;sa.sa_flags = 0; // 无特殊标志// 3. 注册信号sigaction(SIGUSR1, &sa, NULL);printf("程序启动,PID = %d\n", getpid());printf("先发送 SIGUSR1,2 秒内发送 SIGINT 测试阻塞\n");while (1) {sleep(1);}return 0;
}

运行结果示例

程序启动,PID = 1234
先发送 SIGUSR1,2 秒内发送 SIGINT 测试阻塞
捕获到信号 10,处理中(2 秒内阻塞其他信号)...
^C// 2 秒内按下 Ctrl+C,被阻塞
信号 10 处理完成
Terminated// 处理完成后,SIGINT 生效,终止进程

优势 2:获取信号附加信息(如发送者 PID)

通过 sa_flags = SA_SIGINFO 使用增强捕获函数,获取信号发送者 PID、携带数据等信息,示例:

以下为规范格式后的代码内容,仅调整缩进和排版,未修改原内容:

#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>// 增强捕获函数(带附加信息)
void sig_info_handler(int sig, siginfo_t *info, void *context) {printf("捕获到信号 %d\n", sig);printf("发送者 PID:%d\n", info->si_pid);  // 获取发送者 PIDprintf("发送者 UID:%d\n", info->si_uid);  // 获取发送者 UIDprintf("信号携带数据:%d\n", info->si_value);  // 获取携带数据
}int main() {struct sigaction sa;sigemptyset(&sa.sa_mask);sa.sa_flags = SA_SIGINFO;  // 使用增强捕获函数sa.sa_sigaction = sig_info_handler;  // 设置增强函数// 注册可靠信号(SIGRTMIN + 1)sigaction(SIGRTMIN + 1, &sa, NULL);printf("程序启动,PID = %d\n", getpid());printf("发送信号并携带数据:kill -%d %d -o 123\n", SIGRTMIN + 1, getpid());while (1) {sleep(1);}return 0;
}// 程序输出示例
程序启动,PID = 1234
发送信号并携带数据:kill -35 1234 -o 123
捕获到信号 35
发送者 PID:5678  // 发送者终端的 PID
发送者 UID:1000  // 发送者的 UID
信号携带数据:123  // 自定义携带数据

优势 3:自动重启系统调用

通过 sa_flags |= SA_RESTART 配置自动重启被中断的系统调用,避免手动重试,示例:

#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>void sig_handler(int sig) {printf("捕获到信号 %d,处理完成\n", sig);
}int main() {struct sigaction sa;sigemptyset(&sa.sa_mask);sa.sa_handler = sig_handler;sa.sa_flags = SA_RESTART; // 自动重启系统调用sigaction(SIGUSR1, &sa, NULL);// 打开文件,阻塞读取(测试系统调用重启)int fd = open("test.txt", O_RDONLY);if (fd == -1) {perror("open 失败");return 1;}char buf[128];printf("阻塞读取文件(发送 kill -10 %d 测试重启)\n", getpid());ssize_t n = read(fd, buf, sizeof(buf)); // 被信号中断后自动重启if (n == -1) {perror("read 失败");return 1;}buf[n] = '\0';printf("读取文件内容:%s\n", buf);close(fd);return 0;
}

阻塞读取文件(发送 kill -10 1234 测试重启) 捕获到信号 10,处理完成 读取文件内容:Hello from test.txt // read 被中断后自动重启,成功读取

UNIX 中 signal 函数的使用方法、信号捕获函数的编写规范,分析了 signal 函数的局限性与常见错误,并拓展了增强接口 sigaction 的使用。signal 函数是信号处理的基础,适合简单场景;复杂场景(如可靠信号、信号掩码、自动重启)需使用 sigaction 函数,确保程序的健壮性和跨平台兼容性。

编写信号捕获函数时,需严格遵循“禁用不可重入函数、全局变量加 volatile、避免耗时操作”等规范,避免异步特性导致的程序崩溃。同时,务必检查 signal/sigaction 的返回值,处理无效信号、不可捕获信号等错误场景,确保信号处理逻辑的正确性。

http://www.dtcms.com/a/449869.html

相关文章:

  • dede 分类信息网站 模板wordpress怎么装插件
  • 分布式系统实战:电商平台架构演进
  • 基于YOLOv8+CNN的智能停车场车牌识别系统(视频图片均可)(完整实现,附完整可直接运行代码)
  • @ComponentScan组件扫描原理
  • 沈阳制作网站的公司网站开发要什么
  • MySQL 8.0存储引擎选型指南
  • 做移动端网站设计网站怎样制作
  • redis的哨兵机制简单问题
  • 打造自己的中秋 AR 赏月应用:实现虚实融合的节日体验
  • 建设网站学什么建设考试的报名网站
  • 色块网站设计在家做的网站编辑
  • WebRTC 入门与实战(二)之中级篇
  • pass@1是什么意思
  • 沈阳网站建设技术公司百度站长工具seo
  • 做国内电影网站赚钱不简述电子商务网站开发的主要步骤
  • InputStream和OutputStream在网络编程发挥的作用
  • CCS闪退问题---------中文系统用户名
  • 专业电竞体育数据与系统解决方案
  • 初阶运维工程师工作内容与能力体系:专业视角解析
  • 我的钢铁网网站架构林芝北京网站建设
  • OpenManus项目架构解析
  • 【HarmonyOS】消息通知
  • 网上做流量对网站有什么影响asp.net 做网站实例
  • 深圳建设资格注册中心网站网站建设采用的技术
  • gRPC从0到1系列【22】
  • 闹钟定时器(Alarm Timer)初始化:构建可挂起的定时器基础框架
  • 云南公司建网站多少钱wordpress修改菜单的原始链接
  • 自己如何建设个网站首页站酷网官方入口网页版
  • 华为matebook16s 2022数字键无法使用解决方法
  • 邯郸网站建设品牌公司app和网站开发区别