Linux信号捕捉特性详解:从基础到高级实践
引言
在Linux系统编程中,信号是一种重要的进程间通信机制,也是操作系统与应用程序交互的关键手段。信号机制允许进程在特定事件发生时接收通知,这些事件可能来自内核、其他进程或进程自身。理解信号的捕捉与处理是成为高级Linux开发者的必备技能。
信号机制最初出现在UNIX系统中,经过几十年的发展,已经成为所有类UNIX系统的核心特性之一。Linux信号机制继承自UNIX,并在其基础上进行了扩展和完善。本文将深入探讨Linux信号捕捉的各个方面,从基础概念到高级技术,最后给出实际应用的最佳实践。
第一章:信号的基本概念
1.1 什么是信号
信号是Linux系统中用于通知进程发生了某种事件的机制。这些事件可以是硬件异常(如除零错误、段错误)、用户输入(如Ctrl+C)、定时器到期,或者是其他进程通过系统调用发送的信号。
信号本质上是异步的,即信号可能在进程执行的任何时刻到达。当信号到达时,进程有三种处理方式:
- 忽略信号(除了SIGKILL和SIGSTOP)
- 执行默认操作(通常是终止进程)
- 捕捉信号并执行自定义的信号处理函数
1.2 信号的分类
Linux系统中的信号可以分为标准信号和实时信号两大类:
c
#include <signal.h>
// 标准信号:1-31,有特定名称
#define SIGHUP 1 // 终端挂起
#define SIGINT 2 // 中断信号(Ctrl+C)
#define SIGQUIT 3 // 退出信号(Ctrl+\)
#define SIGILL 4 // 非法指令
#define SIGABRT 6 // 异常终止
#define SIGFPE 8 // 浮点异常
#define SIGKILL 9 // 强制终止(不可捕捉)
#define SIGSEGV 11 // 段错误
#define SIGPIPE 13 // 管道破裂
#define SIGALRM 14 // 闹钟信号
#define SIGTERM 15 // 终止信号
#define SIGUSR1 10 // 用户自定义信号1
#define SIGUSR2 12 // 用户自定义信号2
// 实时信号:34-64,没有特定名称
#define SIGRTMIN 34
#define SIGRTMAX 64
1.3 信号的产生方式
信号可以通过多种方式产生:
c
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
int main() {
pid_t pid = getpid();
// 1. 通过键盘产生信号(如Ctrl+C产生SIGINT)
printf("进程ID: %d,尝试按下Ctrl+C发送SIGINT信号\n", pid);
sleep(5);
// 2. 通过kill命令或kill()函数发送信号
printf("在另一个终端执行: kill -SIGUSR1 %d\n", pid);
sleep(5);
// 3. 通过raise()函数向自身发送信号
printf("向自身发送SIGUSR2信号\n");
raise(SIGUSR2);
// 4. 通过alarm()函数发送SIGALRM信号
printf("设置3秒后发送SIGALRM信号\n");
alarm(3);
pause(); // 等待信号
return 0;
}
1.4 信号的生命周期
一个信号从产生到处理完成经历了以下几个阶段:
- 产生:信号由内核、其他进程或进程自身产生
- 递送:信号被发送到目标进程
- 未决:信号已产生但尚未被处理
- 阻塞:进程可以选择阻塞某些信号,使其保持未决状态
- 处理:进程执行信号处理函数或默认操作
- 清除:信号处理完成后被清除
第二章:信号捕捉的基本机制
2.1 signal()函数:传统信号处理
signal()函数是信号处理的最基本接口,提供了一种简单但不够强大的信号处理机制。
c
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
// 信号处理函数
void signal_handler(int signum) {
const char* signal_name;
switch(signum) {
case SIGINT:
signal_name = "SIGINT";
break;
case SIGTERM:
signal_name = "SIGTERM";
break;
case SIGUSR1:
signal_name = "SIGUSR1";
break;
default:
signal_name = "未知信号";
}
printf("捕捉到信号: %s (%d)\n", signal_name, signum);
// 如果是SIGINT,可以选择优雅地退出
if (signum == SIGINT) {
printf("接收到中断信号,开始清理资源...\n");
sleep(2); // 模拟资源清理
printf("资源清理完成,退出程序\n");
exit(0);
}
}
// 演示signal()函数的基本用法
void demo_signal_basic() {
printf("=== signal()函数基础演示 ===\n");
printf("进程ID: %d\n", getpid());
printf("可以执行以下操作:\n");
printf("1. 按下Ctrl+C发送SIGINT\n");
printf("2. 在另一个终端执行: kill -SIGUSR1 %d\n", getpid());
printf("3. 在另一个终端执行: kill -SIGTERM %d\n", getpid());
// 注册信号处理函数
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
signal(SIGUSR1, signal_handler);
// 忽略SIGQUIT信号
signal(SIGQUIT, SIG_IGN);
printf("SIGQUIT信号已被忽略,按下Ctrl+\\将不会生效\n");
// 恢复SIGALRM信号的默认行为
signal(SIGALRM, SIG_DFL);
printf("SIGALRM信号已恢复默认行为\n");
// 保持程序运行以接收信号
while(1) {
printf("程序运行中...\n");
sleep(5);
}
}
// 演示signal()函数的局限性
void demo_signal_limitations() {
printf("\n=== signal()函数局限性演示 ===\n");
// signal()在不同系统的行为不一致
// 在某些系统上,信号处理函数执行后会被重置为默认行为
void old_handler(int sig) {
printf("旧处理函数: 收到信号 %d\n", sig);
// 这里不重新注册信号处理函数
}
signal(SIGUSR1, old_handler);
printf("第一次发送SIGUSR1:\n");
raise(SIGUSR1);
printf("第二次发送SIGUSR1(某些系统会执行默认操作):\n");
raise(SIGUSR1);
}
int main() {
demo_signal_basic();
// demo_signal_limitations(); // 取消注释以演示局限性
return 0;
}
2.2 sigaction()函数:现代信号处理
sigaction()函数提供了更强大、更可靠的信号处理机制,是现代Linux程序推荐使用的信号处理接口。
c
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
// 改进的信号处理函数
void sigaction_handler(int signum, siginfo_t *info, void *context) {
const char* signal_name;
char sender_info[100];
// 获取信号名称
switch(signum) {
case SIGINT:
signal_name = "SIGINT";
break;
case SIGTERM:
signal_name = "SIGTERM";
break;
case SIGUSR1:
signal_name = "SIGUSR1";
break;
case SIGSEGV:
signal_name = "SIGSEGV";
break;
default:
signal_name = "未知信号";
}
// 获取发送者信息
if (info->si_pid == 0) {
strcpy(sender_info, "内核");
} else {
snprintf(sender_info, sizeof(sender_info), "进程 %d", info->si_pid);
}
printf("捕捉到信号: %s (%d)\n", signal_name, signum);
printf("发送者: %s\n", sender_info);
printf("用户ID: %d\n", info->si_uid);
// 对于SIGSEGV,打印地址信息
if (signum == SIGSEGV) {
printf("错误地址: %p\n", info->si_addr);
printf("错误原因: ");
switch(info->si_code) {
case SEGV_MAPERR:
printf("地址未映射到对象\n");
break;
case SEGV_ACCERR:
printf("对映射对象的无效权限\n");
break;
default:
printf("未知原因\n");
}
}
// 对于实时信号,打印额外信息
if (signum >= SIGRTMIN && signum <= SIGRTMAX) {
printf("实时信号值: %d\n", info->si_value.sival_int);
printf("信号代码: %d\n", info->si_code);
}
// 演示信号处理期间的信号屏蔽
printf("信号处理函数开始执行...\n");
sleep(3); // 模拟处理时间
printf("信号处理函数执行结束\n");
}
// 设置信号处理
void setup_sigaction(int signum, struct sigaction *oldact) {
struct sigaction act;
// 清空结构体
memset(&act, 0, sizeof(act));
// 设置信号处理函数
act.sa_sigaction = sigaction_handler;
// 设置SA_SIGINFO标志以获取额外信息
act.sa_flags = SA_SIGINFO;
// 设置信号处理期间要阻塞的信号
sigemptyset(&act.sa_mask);
sigaddset(&act.sa_mask, SIGTERM); // 在处理当前信号时阻塞SIGTERM
sigaddset(&act.sa_mask, SIGUSR1); // 在处理当前信号时阻塞SIGUSR1
// 设置SA_RESTART标志以自动重启被中断的系统调用
act.sa_flags |= SA_RESTART;
// 应用信号处理
if (sigaction(signum, &act, oldact) == -1) {
perror("sigaction failed");
exit(EXIT_FAILURE);
}
printf("已为信号 %d 设置sigaction处理\n", signum);
}
// 演示sigaction的高级特性
void demo_sigaction_advanced() {
printf("\n=== sigaction()高级特性演示 ===\n");
printf("进程ID: %d\n", getpid());
struct sigaction oldact;
// 为多个信号设置处理函数
setup_sigaction(SIGINT, &oldact);
setup_sigaction(SIGTERM, &oldact);
setup_sigaction(SIGUSR1, &oldact);
// 设置SIGSEGV处理函数以演示错误处理
struct sigaction segv_act;
memset(&segv_act, 0, sizeof(segv_act));
segv_act.sa_sigaction = sigaction_handler;
segv_act.sa_flags = SA_SIGINFO | SA_ONSTACK; // 使用备用信号栈
sigemptyset(&segv_act.sa_mask);
sigaction(SIGSEGV, &segv_act, NULL);
printf("\n测试说明:\n");
printf("1. 按下Ctrl+C发送SIGINT\n");
printf("2. 在另一个终端执行: kill -SIGUSR1 %d\n", getpid());
printf("3. 在另一个终端执行: kill -SIGTERM %d\n", getpid());
printf("4. SIGSEGV信号将在后续演示中触发\n");
// 演示SA_RESTART标志的效果
printf("\n测试SA_RESTART标志(输入一些字符):\n");
char buffer[100];
int n = read(STDIN_FILENO, buffer, sizeof(buffer));
if (n == -1) {
perror("read error");
} else {
printf("成功读取 %d 字节\n", n);
}
// 保持程序运行
while(1) {
pause(); // 等待信号
}
}
// 演示信号处理函数的持久性
void demo_signal_persistence() {
printf("\n=== 信号处理函数的持久性 ===\n");
struct sigaction act, oldact;
memset(&act, 0, sizeof(act));
act.sa_handler = SIG_IGN; // 忽略信号
act.sa_flags = 0;
// 临时忽略SIGINT
sigaction(SIGINT, &act, &oldact);
printf("临时忽略SIGINT信号\n");
sleep(3);
// 恢复原来的信号处理
sigaction(SIGINT, &oldact, NULL);
printf("恢复SIGINT信号处理\n");
}
int main() {
demo_sigaction_advanced();
// demo_signal_persistence(); // 取消注释以演示持久性
return 0;
}
第三章:信号捕捉的关键特性
3.1 信号处理函数的可重入性
可重入函数是指可以被多个任务同时调用而不会产生冲突的函数。在信号处理函数中,必须使用可重入函数,因为信号可能在程序执行的任何时刻发生。
c
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
// 全局变量 - 在信号处理函数中访问这些变量是不安全的
int unsafe_counter = 0;
char unsafe_buffer[100];
// 不可重入的信号处理函数(存在安全隐患)
void unsafe_signal_handler(int signum) {
// 问题1: 调用不可重入函数
printf("不安全处理: 收到信号 %d\n", signum); // printf不可重入
// 问题2: 修改全局变量
unsafe_counter++;
sprintf(unsafe_buffer, "信号计数: %d", unsafe_counter); // sprintf不可重入
// 问题3: 调用其他不可重入函数
// malloc, free, strtok等都不应在信号处理函数中使用
}
// 安全的信号处理函数(只使用异步信号安全函数)
void safe_signal_handler(int signum) {
// 只能使用异步信号安全函数
// 写入文件描述符2(stderr)
const char* msg = "安全处理: 收到信号\n";
write(STDERR_FILENO, msg, strlen(msg));
// 使用系统调用获取进程ID
pid_t pid = getpid(); // getpid是异步信号安全的
char pid_msg[50];
// 注意:snprintf不是异步信号安全的,这里仅作演示
// 在实际应用中应避免在信号处理函数中使用格式化输出
// 安全的退出方式
if (signum == SIGINT) {
_exit(EXIT_FAILURE); // _exit是异步信号安全的,exit不是
}
}
// 演示信号处理函数中的可重入问题
volatile sig_atomic_t signal_flag = 0; // 使用sig_atomic_t类型
void demo_reentrancy_issue() {
printf("\n=== 信号处理函数的可重入性问题演示 ===\n");
// 设置信号处理函数
struct sigaction act;
memset(&act, 0, sizeof(act));
act.sa_handler = unsafe_signal_handler;
sigaction(SIGUSR1, &act, NULL);
printf("发送多个SIGUSR1信号演示可重入问题...\n");
// 快速发送多个信号
for (int i = 0; i < 5; i++) {
raise(SIGUSR1);
}
sleep(1);
printf("\n使用安全处理函数...\n");
// 设置安全处理函数
act.sa_handler = safe_signal_handler;
sigaction(SIGUSR1, &act, NULL);
for (int i = 0; i < 3; i++) {
raise(SIGUSR1);
}
}
// 使用标志位的安全模式
void flag_based_handler(int signum) {
// 只设置标志位,在主循环中处理
signal_flag = 1;
}
void demo_flag_based_approach() {
printf("\n=== 基于标志位的信号处理模式 ===\n");
struct sigaction act;
memset(&act, 0, sizeof(act));
act.sa_handler = flag_based_handler;
act.sa_flags = SA_RESTART;
sigaction(SIGUSR1, &act, NULL);
printf("进程ID: %d\n", getpid());
printf("发送SIGUSR1信号将在主循环中处理\n");
while(1) {
// 检查信号标志
if (signal_flag) {
printf("在主循环中处理信号\n");
signal_flag = 0;
// 这里可以安全地调用任何函数
// 因为我们在主线程上下文中
// 模拟一些处理工作
sleep(1);
printf("信号处理完成\n");
}
// 主程序的其他工作
printf(".");
fflush(stdout);
sleep(2);
}
}
// 异步信号安全函数列表演示
void list_async_safe_functions() {
printf("\n=== 常见的异步信号安全函数 ===\n");
printf("以下函数可以在信号处理函数中安全调用:\n");
printf("1. 系统调用: read(), write(), open(), close()\n");
printf("2. 进程控制: _exit(), getpid(), getuid(), kill()\n");
printf("3. 信号处理: sigaction(), sigprocmask(), sigpending()\n");
printf("4. 其他: pause(), alarm(), sleep()\n");
printf("\n以下函数不安全,不应在信号处理函数中使用:\n");
printf("1. 内存分配: malloc(), calloc(), realloc(), free()\n");
printf("2. 标准I/O: printf(), scanf(), fopen(), fclose()\n");
printf("3. 字符串处理: strtok(), strerror()\n");
printf("4. 其他: system(), exit()\n");
}
int main() {
demo_reentrancy_issue();
// demo_flag_based_approach(); // 取消注释以演示基于标志位的处理
list_async_safe_functions();
return 0;
}
3.2 信号屏蔽与阻塞
信号屏蔽允许进程暂时阻止某些信号的传递,这在关键代码段执行时非常有用。
c
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
// 显示信号集的辅助函数
void print_signal_set(const sigset_t *set, const char *name) {
printf("%s信号集: ", name);
for (int sig = 1; sig <= 32; sig++) {
if (sigismember(set, sig)) {
printf("%d ", sig);
}
}
printf("\n");
}
// 演示基本的信号屏蔽
void demo_basic_signal_mask() {
printf("\n=== 基本信号屏蔽演示 ===\n");
sigset_t new_mask, old_mask, pending_mask;
// 初始化信号集
sigemptyset(&new_mask);
// 添加要屏蔽的信号
sigaddset(&new_mask, SIGINT);
sigaddset(&new_mask, SIGTERM);
sigaddset(&new_mask, SIGUSR1);
// 获取并显示当前信号掩码
sigprocmask(SIG_BLOCK, NULL, &old_mask);
print_signal_set(&old_mask, "当前");
// 设置新的信号掩码(阻塞指定信号)
sigprocmask(SIG_BLOCK, &new_mask, &old_mask);
printf("已阻塞SIGINT, SIGTERM, SIGUSR1信号\n");
// 显示新的信号掩码
sigprocmask(SIG_BLOCK, NULL, &new_mask);
print_signal_set(&new_mask, "新的");
// 发送一些信号,它们将被阻塞
printf("\n发送被阻塞的信号...\n");
raise(SIGINT);
raise(SIGTERM);
raise(SIGUSR1);
// 检查未决信号
sigpending(&pending_mask);
print_signal_set(&pending_mask, "未决");
// 解除信号阻塞
printf("\n解除信号阻塞...\n");
sigprocmask(SIG_UNBLOCK, &new_mask, NULL);
// 检查未决信号(应该为空)
sigpending(&pending_mask);
print_signal_set(&pending_mask, "解除后未决");
printf("注意:解除阻塞后,未决信号会立即被递送\n");
}
// 演示关键代码段的信号保护
void critical_section_protection() {
printf("\n=== 关键代码段的信号保护 ===\n");
sigset_t mask, oldmask;
sigemptyset(&mask);
sigaddset(&mask, SIGINT);
printf("进入关键代码段...\n");
// 阻塞SIGINT信号
sigprocmask(SIG_BLOCK, &mask, &oldmask);
// 关键代码段开始
printf("关键代码段执行中(不会被SIGINT中断)\n");
printf("尝试按下Ctrl+C,信号将被阻塞\n");
for (int i = 0; i < 5; i++) {
printf("关键操作 %d/5\n", i + 1);
sleep(1);
}
// 关键代码段结束
// 恢复原来的信号掩码
sigprocmask(SIG_SETMASK, &oldmask, NULL);
printf("关键代码段结束,信号处理已恢复\n");
}
// 演示pthread中的信号屏蔽
void* thread_function(void* arg) {
printf("线程开始执行\n");
// 线程可以有自己的信号掩码
sigset_t thread_mask;
sigemptyset(&thread_mask);
sigaddset(&thread_mask, SIGUSR1);
// 设置线程信号掩码
pthread_sigmask(SIG_BLOCK, &thread_mask, NULL);
printf("线程已阻塞SIGUSR1信号\n");
printf("线程ID: %lu\n", pthread_self());
// 线程执行一些工作
for (int i = 0; i < 3; i++) {
printf("线程工作中... %d/3\n", i + 1);
sleep(2);
}
return NULL;
}
void demo_thread_signal_mask() {
printf("\n=== 线程中的信号屏蔽 ===\n");
pthread_t thread;
// 创建线程
if (pthread_create(&thread, NULL, thread_function, NULL) != 0) {
perror("pthread_create failed");
return;
}
// 主线程发送信号给子线程
printf("主线程发送SIGUSR1到子线程(将被阻塞)\n");
pthread_kill(thread, SIGUSR1);
// 等待线程结束
pthread_join(thread, NULL);
printf("线程已结束\n");
}
// 演示sigaction中的sa_mask
void mask_in_sigaction_handler(int signum) {
printf("信号处理函数开始,信号 %d\n", signum);
// 显示处理期间的信号掩码
sigset_t current_mask;
sigprocmask(SIG_BLOCK, NULL, ¤t_mask);
print_signal_set(¤t_mask, "处理中");
// 模拟长时间处理
printf("信号处理中...\n");
sleep(3);
printf("信号处理结束\n");
}
void demo_sigaction_mask() {
printf("\n=== sigaction中的sa_mask演示 ===\n");
struct sigaction act;
memset(&act, 0, sizeof(act));
act.sa_handler = mask_in_sigaction_handler;
// 设置信号处理期间要阻塞的信号
sigemptyset(&act.sa_mask);
sigaddset(&act.sa_mask, SIGTERM);
sigaddset(&act.sa_mask, SIGUSR1);
sigaction(SIGINT, &act, NULL);
printf("设置SIGINT处理函数,在处理期间将阻塞SIGTERM和SIGUSR1\n");
printf("进程ID: %d\n", getpid());
printf("1. 按下Ctrl+C发送SIGINT\n");
printf("2. 在SIGINT处理期间发送SIGTERM或SIGUSR1\n");
pause();
}
int main() {
demo_basic_signal_mask();
critical_section_protection();
demo_thread_signal_mask();
demo_sigaction_mask();
return 0;
}
3.3 信号的可靠性与不可靠信号
Linux信号分为可靠信号(实时信号)和不可靠信号(标准信号)。不可靠信号可能丢失,而可靠信号支持排队,不会丢失。
c
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
volatile int standard_signal_count = 0;
volatile int realtime_signal_count = 0;
// 标准信号处理函数
void standard_signal_handler(int signum) {
standard_signal_count++;
printf("标准信号 %d 处理,计数: %d\n", signum, standard_signal_count);
}
// 实时信号处理函数
void realtime_signal_handler(int signum) {
realtime_signal_count++;
printf("实时信号 %d 处理,计数: %d\n", signum, realtime_signal_count);
}
// 演示标准信号的不可靠性
void demo_unreliable_signals() {
printf("\n=== 标准信号的不可靠性演示 ===\n");
// 设置SIGUSR1处理函数
struct sigaction act;
memset(&act, 0, sizeof(act));
act.sa_handler = standard_signal_handler;
act.sa_flags = 0; // 不使用SA_RESTART
sigaction(SIGUSR1, &act, NULL);
printf("标准信号(SIGUSR1)处理函数已设置\n");
printf("将快速发送多个SIGUSR1信号...\n");
// 重置计数器
standard_signal_count = 0;
// 快速发送多个信号
for (int i = 0; i < 10; i++) {
raise(SIGUSR1);
}
sleep(1); // 等待信号处理
printf("发送了10个信号,实际处理了 %d 个信号\n", standard_signal_count);
printf("结论:标准信号可能丢失,不支持排队\n");
}
// 演示实时信号的可靠性
void demo_reliable_signals() {
printf("\n=== 实时信号的可靠性演示 ===\n");
int realtime_sig = SIGRTMIN; // 使用第一个实时信号
// 设置实时信号处理函数
struct sigaction act;
memset(&act, 0, sizeof(act));
act.sa_handler = realtime_signal_handler;
act.sa_flags = SA_SIGINFO; // 使用SA_SIGINFO获取额外信息
sigaction(realtime_sig, &act, NULL);
printf("实时信号(%d)处理函数已设置\n", realtime_sig);
printf("将快速发送多个实时信号...\n");
// 重置计数器
realtime_signal_count = 0;
// 创建信号值
union sigval value;
value.sival_int = 0;
// 快速发送多个实时信号
for (int i = 0; i < 10; i++) {
value.sival_int = i;
if (sigqueue(getpid(), realtime_sig, value) == -1) {
perror("sigqueue failed");
}
}
sleep(2); // 等待信号处理
printf("发送了10个实时信号,实际处理了 %d 个信号\n", realtime_signal_count);
printf("结论:实时信号可靠,支持排队\n");
}
// 演示信号丢失的极端情况
void demo_signal_loss() {
printf("\n=== 信号丢失的极端情况演示 ===\n");
// 长时间运行的信号处理函数
void slow_handler(int signum) {
printf("开始处理信号 %d\n", signum);
sleep(5); // 长时间处理
printf("结束处理信号 %d\n", signum);
}
struct sigaction act;
memset(&act, 0, sizeof(act));
act.sa_handler = slow_handler;
sigaction(SIGUSR1, &act, NULL);
printf("设置慢速处理函数(5秒)\n");
printf("进程ID: %d\n", getpid());
printf("在5秒内快速发送多个SIGUSR1信号...\n");
// 在子进程中快速发送信号
pid_t pid = fork();
if (pid == 0) {
// 子进程:快速发送信号
for (int i = 0; i < 10; i++) {
kill(getppid(), SIGUSR1);
usleep(100000); // 100ms间隔
}
exit(0);
}
// 父进程等待
wait(NULL);
printf("信号发送完成\n");
}
// 演示使用实时信号避免丢失
void demo_realtime_solution() {
printf("\n=== 使用实时信号避免丢失 ===\n");
int realtime_sig = SIGRTMIN + 1;
void realtime_slow_handler(int signum) {
printf("开始处理实时信号 %d\n", signum);
sleep(3); // 长时间处理
printf("结束处理实时信号 %d\n", signum);
}
struct sigaction act;
memset(&act, 0, sizeof(act));
act.sa_handler = realtime_slow_handler;
act.sa_flags = SA_SIGINFO;
sigaction(realtime_sig, &act, NULL);
printf("设置实时信号处理函数(3秒)\n");
printf("进程ID: %d\n", getpid());
printf("在3秒内快速发送多个实时信号...\n");
// 在子进程中快速发送实时信号
pid_t pid = fork();
if (pid == 0) {
// 子进程:快速发送实时信号
union sigval value;
for (int i = 0; i < 10; i++) {
value.sival_int = i;
sigqueue(getppid(), realtime_sig, value);
usleep(100000); // 100ms间隔
}
exit(0);
}
// 父进程等待更长时间以处理所有排队的信号
wait(NULL);
sleep(15); // 给足够时间处理所有排队的信号
printf("所有实时信号都已处理完成\n");
}
int main() {
demo_unreliable_signals();
demo_reliable_signals();
demo_signal_loss();
demo_realtime_solution();
return 0;
}
3.4 信号处理期间的信号屏蔽
当信号处理函数执行时,系统会自动阻塞当前正在处理的信号,防止递归调用。此外,还可以通过sa_mask指定额外的阻塞信号。
c
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
// 用于跟踪递归深度的全局变量
volatile int recursion_depth = 0;
// 递归信号处理函数
void recursive_handler(int signum) {
recursion_depth++;
printf("信号处理函数深度: %d,信号: %d\n", recursion_depth, signum);
if (recursion_depth < 3) {
// 模拟在处理期间再次收到同种信号
printf("模拟再次收到信号...\n");
raise(signum);
}
sleep(1);
recursion_depth--;
printf("信号处理函数结束,深度: %d\n", recursion_depth);
}
// 演示自动阻塞当前信号
void demo_auto_block() {
printf("\n=== 信号处理期间的自动阻塞 ===\n");
struct sigaction act;
memset(&act, 0, sizeof(act));
act.sa_handler = recursive_handler;
act.sa_flags = 0; // 默认行为会自动阻塞当前信号
sigaction(SIGUSR1, &act, NULL);
printf("设置递归信号处理函数\n");
printf("进程ID: %d\n", getpid());
printf("发送SIGUSR1信号...\n");
recursion_depth = 0;
raise(SIGUSR1);
printf("注意:虽然调用了raise(),但同种信号被自动阻塞\n");
}
// 使用sa_mask阻塞额外信号
void handler_with_mask(int signum) {
printf("开始处理信号 %d\n", signum);
// 显示当前的信号掩码
sigset_t current_mask;
sigprocmask(SIG_BLOCK, NULL, ¤t_mask);
printf("处理期间的信号掩码:\n");
for (int sig = 1; sig <= 32; sig++) {
if (sigismember(¤t_mask, sig)) {
printf(" 信号 %d 被阻塞\n", sig);
}
}
printf("处理中...\n");
sleep(3);
printf("处理完成\n");
}
void demo_custom_mask() {
printf("\n=== 使用sa_mask自定义阻塞信号 ===\n");
struct sigaction act;
memset(&act, 0, sizeof(act));
act.sa_handler = handler_with_mask;
// 设置信号处理期间要阻塞的信号
sigemptyset(&act.sa_mask);
sigaddset(&act.sa_mask, SIGINT);
sigaddset(&act.sa_mask, SIGTERM);
sigaddset(&act.sa_mask, SIGUSR1);
sigaction(SIGUSR2, &act, NULL);
printf("设置SIGUSR2处理函数\n");
printf("在处理期间将阻塞SIGINT、SIGTERM和SIGUSR1\n");
printf("进程ID: %d\n", getpid());
// 创建子进程发送信号
pid_t pid = fork();
if (pid == 0) {
sleep(1); // 等待父进程进入信号处理
printf("子进程发送SIGUSR1到父进程(应被阻塞)\n");
kill(getppid(), SIGUSR1);
printf("子进程发送SIGTERM到父进程(应被阻塞)\n");
kill(getppid(), SIGTERM);
printf("子进程发送SIGUSR2到父进程(应被阻塞)\n");
kill(getppid(), SIGUSR2);
exit(0);
}
// 父进程发送SIGUSR2触发处理
printf("父进程发送SIGUSR2触发处理\n");
raise(SIGUSR2);
wait(NULL);
printf("所有信号处理完成\n");
}
// 演示SA_NODEFER标志
void handler_with_nodefer(int signum) {
static int count = 0;
count++;
printf("SA_NODEFER处理函数调用 %d,信号: %d\n", count, signum);
if (count < 3) {
// 由于设置了SA_NODEFER,同种信号不会被阻塞
printf("再次发送同种信号(可能造成递归)\n");
raise(signum);
}
sleep(1);
printf("SA_NODEFER处理函数结束 %d\n", count);
}
void demo_nodefer_flag() {
printf("\n=== SA_NODEFER标志演示 ===\n");
struct sigaction act;
memset(&act, 0, sizeof(act));
act.sa_handler = handler_with_nodefer;
act.sa_flags = SA_NODEFER; // 不自动阻塞当前信号
sigaction(SIGUSR1, &act, NULL);
printf("设置SA_NODEFER标志,同种信号不会被自动阻塞\n");
printf("警告:这可能导致递归和栈溢出\n");
printf("发送SIGUSR1信号...\n");
raise(SIGUSR1);
}
// 演示嵌套信号处理
volatile int outer_handler_called = 0;
volatile int inner_handler_called = 0;
void inner_handler(int signum) {
inner_handler_called = 1;
printf("内部处理函数被调用,信号: %d\n", signum);
// 显示当前的信号掩码
sigset_t current_mask;
sigprocmask(SIG_BLOCK, NULL, ¤t_mask);
printf("内部处理期间的信号掩码:\n");
for (int sig = 1; sig <= 32; sig++) {
if (sigismember(¤t_mask, sig)) {
printf(" 信号 %d 被阻塞\n", sig);
}
}
}
void outer_handler(int signum) {
outer_handler_called = 1;
printf("外部处理函数被调用,信号: %d\n", signum);
// 设置内部信号处理函数
struct sigaction act;
memset(&act, 0, sizeof(act));
act.sa_handler = inner_handler;
sigaction(SIGUSR2, &act, NULL);
// 发送另一个信号
printf("发送SIGUSR2信号...\n");
raise(SIGUSR2);
printf("外部处理函数结束\n");
}
void demo_nested_handling() {
printf("\n=== 嵌套信号处理演示 ===\n");
struct sigaction act;
memset(&act, 0, sizeof(act));
act.sa_handler = outer_handler;
sigaction(SIGUSR1, &act, NULL);
printf("设置嵌套信号处理\n");
printf("发送SIGUSR1将触发SIGUSR2\n");
outer_handler_called = 0;
inner_handler_called = 0;
raise(SIGUSR1);
printf("外部处理函数调用: %d\n", outer_handler_called);
printf("内部处理函数调用: %d\n", inner_handler_called);
}
int main() {
demo_auto_block();
demo_custom_mask();
demo_nodefer_flag();
demo_nested_handling();
return 0;
}
第四章:高级信号处理技术
4.1 使用sigwait同步处理信号
sigwait()函数允许线程同步等待信号的到达,这是一种更可控的信号处理方式,特别适合多线程程序。
c
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <errno.h>
// 全局变量
volatile sig_atomic_t shutdown_flag = 0;
pthread_t signal_thread;
// 信号处理线程函数
void* signal_handler_thread(void* arg) {
printf("信号处理线程启动,线程ID: %lu\n", pthread_self());
// 设置要等待的信号集
sigset_t signal_set;
sigemptyset(&signal_set);
sigaddset(&signal_set, SIGINT);
sigaddset(&signal_set, SIGTERM);
sigaddset(&signal_set, SIGUSR1);
sigaddset(&signal_set, SIGUSR2);
int sig_number;
int ret;
while (!shutdown_flag) {
// 等待信号
ret = sigwait(&signal_set, &sig_number);
if (ret != 0) {
fprintf(stderr, "sigwait失败: %s\n", strerror(ret));
break;
}
// 处理信号
switch(sig_number) {
case SIGINT:
printf("线程收到SIGINT,开始优雅关闭\n");
shutdown_flag = 1;
break;
case SIGTERM:
printf("线程收到SIGTERM,开始优雅关闭\n");
shutdown_flag = 1;
break;
case SIGUSR1:
printf("线程收到SIGUSR1,执行特定操作\n");
// 执行一些操作
break;
case SIGUSR2:
printf("线程收到SIGUSR2,执行其他操作\n");
// 执行其他操作
break;
default:
printf("线程收到未知信号: %d\n", sig_number);
}
}
printf("信号处理线程结束\n");
return NULL;
}
// 初始化信号处理线程
int init_signal_handler() {
printf("初始化信号处理线程\n");
// 在主线程中阻塞所有信号
sigset_t all_signals;
sigfillset(&all_signals);
pthread_sigmask(SIG_BLOCK, &all_signals, NULL);
// 创建信号处理线程
int ret = pthread_create(&signal_thread, NULL, signal_handler_thread, NULL);
if (ret != 0) {
fprintf(stderr, "创建线程失败: %s\n", strerror(ret));
return -1;
}
printf("信号处理线程创建成功\n");
return 0;
}
// 工作线程函数
void* worker_thread(void* arg) {
int thread_id = *(int*)arg;
printf("工作线程 %d 启动\n", thread_id);
while (!shutdown_flag) {
printf("工作线程 %d 工作中...\n", thread_id);
sleep(thread_id + 1); // 不同的工作间隔
// 模拟工作
for (int i = 0; i < 1000000; i++) {
// 空循环模拟工作
}
}
printf("工作线程 %d 收到关闭信号,结束\n", thread_id);
return NULL;
}
// 演示多线程信号处理
void demo_multithread_signal_handling() {
printf("\n=== 多线程信号处理演示 ===\n");
// 初始化信号处理
if (init_signal_handler() != 0) {
exit(EXIT_FAILURE);
}
// 创建工作线程
pthread_t workers[3];
int thread_ids[3] = {1, 2, 3};
for (int i = 0; i < 3; i++) {
pthread_create(&workers[i], NULL, worker_thread, &thread_ids[i]);
}
// 主线程显示信息
printf("主线程ID: %lu\n", pthread_self());
printf("进程ID: %d\n", getpid());
printf("\n可以执行以下操作:\n");
printf("1. 按下Ctrl+C发送SIGINT\n");
printf("2. 发送SIGTERM: kill -TERM %d\n", getpid());
printf("3. 发送SIGUSR1: kill -USR1 %d\n", getpid());
printf("4. 发送SIGUSR2: kill -USR2 %d\n", getpid());
printf("\n等待信号...\n");
// 主线程等待所有工作线程结束
for (int i = 0; i < 3; i++) {
pthread_join(workers[i], NULL);
}
// 等待信号处理线程结束
pthread_join(signal_thread, NULL);
printf("所有线程已结束,程序退出\n");
}
// 使用sigwaitinfo获取更多信号信息
void demo_sigwaitinfo() {
printf("\n=== 使用sigwaitinfo获取信号信息 ===\n");
// 阻塞SIGUSR1和SIGUSR2
sigset_t signal_set;
sigemptyset(&signal_set);
sigaddset(&signal_set, SIGUSR1);
sigaddset(&signal_set, SIGUSR2);
pthread_sigmask(SIG_BLOCK, &signal_set, NULL);
printf("进程ID: %d\n", getpid());
printf("信号已阻塞,使用sigwaitinfo等待\n");
// 创建子进程发送信号
pid_t pid = fork();
if (pid == 0) {
// 子进程:发送带额外信息的信号
union sigval value;
sleep(1);
value.sival_int = 100;
printf("子进程发送SIGUSR1,值: %d\n", value.sival_int);
sigqueue(getppid(), SIGUSR1, value);
sleep(1);
value.sival_int = 200;
printf("子进程发送SIGUSR2,值: %d\n", value.sival_int);
sigqueue(getppid(), SIGUSR2, value);
exit(0);
}
// 父进程:等待信号
siginfo_t info;
int sig;
for (int i = 0; i < 2; i++) {
sig = sigwaitinfo(&signal_set, &info);
if (sig == -1) {
perror("sigwaitinfo失败");
continue;
}
printf("收到信号: %d\n", sig);
printf("发送进程: %d\n", info.si_pid);
printf("信号值: %d\n", info.si_value.sival_int);
printf("用户ID: %d\n", info.si_uid);
}
wait(NULL);
printf("sigwaitinfo演示完成\n");
}
// 使用sigtimedwait等待信号(带超时)
void demo_sigtimedwait() {
printf("\n=== 使用sigtimedwait带超时等待信号 ===\n");
sigset_t signal_set;
sigemptyset(&signal_set);
sigaddset(&signal_set, SIGUSR1);
pthread_sigmask(SIG_BLOCK, &signal_set, NULL);
printf("设置5秒超时等待SIGUSR1信号\n");
printf("进程ID: %d\n", getpid());
// 设置超时时间
struct timespec timeout;
timeout.tv_sec = 5;
timeout.tv_nsec = 0;
int sig = sigtimedwait(&signal_set, NULL, &timeout);
if (sig == -1) {
if (errno == EAGAIN) {
printf("超时:在5秒内未收到SIGUSR1信号\n");
} else {
perror("sigtimedwait失败");
}
} else {
printf("收到信号: %d\n", sig);
}
printf("sigtimedwait演示完成\n");
}
int main() {
demo_multithread_signal_handling();
demo_sigwaitinfo();
demo_sigtimedwait();
return 0;
}
4.2 信号栈
信号栈是一个专门用于信号处理函数执行的栈空间,可以防止栈溢出,特别是在处理递归信号或大量数据时。
c
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
#include <sys/mman.h>
// 递归信号处理函数,可能造成栈溢出
void recursive_signal_handler(int signum) {
static int depth = 0;
char large_buffer[8192]; // 分配大缓冲区
depth++;
printf("递归深度: %d,栈地址: %p\n", depth, &large_buffer);
// 模拟递归调用
if (depth < 10) {
// 填充缓冲区以消耗栈空间
memset(large_buffer, depth, sizeof(large_buffer));
// 再次发送信号
raise(signum);
}
depth--;
}
// 使用备用信号栈的处理函数
void handler_on_altstack(int signum) {
char buffer[4096];
printf("在备用信号栈上处理信号,栈地址: %p\n", &buffer);
// 显示栈信息
stack_t current_stack;
sigaltstack(NULL, ¤t_stack);
printf("当前栈信息:\n");
printf(" 栈基址: %p\n", current_stack.ss_sp);
printf(" 栈大小: %zu 字节\n", current_stack.ss_size);
printf(" 标志: %d\n", current_stack.ss_flags);
sleep(1);
}
// 演示栈溢出问题
void demo_stack_overflow() {
printf("\n=== 信号处理栈溢出演示 ===\n");
struct sigaction act;
memset(&act, 0, sizeof(act));
act.sa_handler = recursive_signal_handler;
sigaction(SIGUSR1, &act, NULL);
printf("设置递归信号处理函数\n");
printf("警告:这可能导致栈溢出\n");
printf("发送SIGUSR1信号...\n");
// 注意:这里可能真的导致栈溢出和程序崩溃
// 在实际运行时要小心
raise(SIGUSR1);
printf("如果看到这行,说明没有栈溢出\n");
}
// 设置和使用备用信号栈
void setup_alternate_signal_stack() {
printf("\n=== 设置备用信号栈 ===\n");
// 分配备用信号栈内存
size_t stack_size = SIGSTKSZ * 4; // 使用4倍默认大小
void* stack_memory = mmap(NULL, stack_size,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK,
-1, 0);
if (stack_memory == MAP_FAILED) {
perror("mmap失败");
exit(EXIT_FAILURE);
}
printf("分配的备用栈内存: %p,大小: %zu 字节\n",
stack_memory, stack_size);
// 设置备用信号栈
stack_t altstack;
altstack.ss_sp = stack_memory;
altstack.ss_size = stack_size;
altstack.ss_flags = 0;
if (sigaltstack(&altstack, NULL) == -1) {
perror("sigaltstack失败");
exit(EXIT_FAILURE);
}
printf("备用信号栈设置成功\n");
// 设置使用备用栈的信号处理
struct sigaction act;
memset(&act, 0, sizeof(act));
act.sa_handler = handler_on_altstack;
act.sa_flags = SA_ONSTACK; // 关键:使用备用栈
sigaction(SIGUSR2, &act, NULL);
printf("设置使用备用栈的信号处理函数\n");
printf("进程ID: %d\n", getpid());
printf("发送SIGUSR2信号将使用备用栈处理\n");
raise(SIGUSR2);
// 清理
sleep(1);
munmap(stack_memory, stack_size);
printf("备用栈内存已释放\n");
}
// 演示信号栈的自动切换
void demo_auto_stack_switch() {
printf("\n=== 信号栈自动切换演示 ===\n");
// 获取当前信号栈设置
stack_t old_stack;
if (sigaltstack(NULL, &old_stack) == -1) {
perror("获取当前栈失败");
return;
}
if (old_stack.ss_flags & SS_ONSTACK) {
printf("当前正在信号栈上执行\n");
} else {
printf("当前不在信号栈上执行\n");
}
// 递归函数,在信号处理中调用
void recursive_func(int depth) {
char buffer[1024];
printf("递归深度 %d,缓冲区地址: %p\n", depth, buffer);
if (depth < 5) {
recursive_func(depth + 1);
}
}
// 信号处理函数
void stack_test_handler(int signum) {
printf("信号处理函数开始\n");
// 检查是否在备用栈上
stack_t current_stack;
sigaltstack(NULL, ¤t_stack);
if (current_stack.ss_flags & SS_ONSTACK) {
printf("正在备用信号栈上执行\n");
} else {
printf("不在备用信号栈上执行\n");
}
// 进行递归调用
recursive_func(1);
printf("信号处理函数结束\n");
}
// 设置信号处理
struct sigaction act;
memset(&act, 0, sizeof(act));
act.sa_handler = stack_test_handler;
act.sa_flags = SA_ONSTACK;
sigaction(SIGUSR1, &act, NULL);
// 设置备用栈
size_t stack_size = SIGSTKSZ;
void* stack_mem = malloc(stack_size);
stack_t new_stack;
new_stack.ss_sp = stack_mem;
new_stack.ss_size = stack_size;
new_stack.ss_flags = 0;
sigaltstack(&new_stack, NULL);
printf("发送信号测试备用栈...\n");
raise(SIGUSR1);
free(stack_mem);
}
// 演示在不同栈上的信号处理
void demo_different_stacks() {
printf("\n=== 不同栈上的信号处理演示 ===\n");
// 处理函数1:在主栈上执行
void handler_on_main_stack(int signum) {
char buffer[100];
printf("处理函数1:在主栈上,缓冲区地址: %p\n", &buffer);
}
// 处理函数2:在备用栈上执行
void handler_on_alt_stack(int signum) {
char buffer[100];
printf("处理函数2:在备用栈上,缓冲区地址: %p\n", &buffer);
}
// 设置备用栈
size_t stack_size = SIGSTKSZ;
void* stack_mem = malloc(stack_size);
stack_t alt_stack;
alt_stack.ss_sp = stack_mem;
alt_stack.ss_size = stack_size;
alt_stack.ss_flags = 0;
sigaltstack(&alt_stack, NULL);
// 设置两个信号处理
struct sigaction act1, act2;
memset(&act1, 0, sizeof(act1));
act1.sa_handler = handler_on_main_stack;
act1.sa_flags = 0; // 不在备用栈上
memset(&act2, 0, sizeof(act2));
act2.sa_handler = handler_on_alt_stack;
act2.sa_flags = SA_ONSTACK; // 在备用栈上
sigaction(SIGUSR1, &act1, NULL);
sigaction(SIGUSR2, &act2, NULL);
printf("设置完成:\n");
printf(" SIGUSR1: 在主栈上处理\n");
printf(" SIGUSR2: 在备用栈上处理\n");
printf("进程ID: %d\n", getpid());
// 发送信号
printf("\n发送SIGUSR1...\n");
raise(SIGUSR1);
printf("\n发送SIGUSR2...\n");
raise(SIGUSR2);
free(stack_mem);
}
int main() {
// 小心运行这个演示,可能导致栈溢出
// demo_stack_overflow();
setup_alternate_signal_stack();
demo_auto_stack_switch();
demo_different_stacks();
return 0;
}
4.3 实时信号
实时信号是Linux中更可靠的信号机制,支持排队、携带额外数据等特性。
c
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <time.h>
// 实时信号处理函数
void realtime_signal_handler(int signum, siginfo_t* info, void* context) {
printf("\n=== 实时信号处理 ===\n");
printf("信号编号: %d\n", signum);
printf("信号代码: %d\n", info->si_code);
// 检查信号来源
if (info->si_code == SI_USER) {
printf("来源: 用户进程 (%d)\n", info->si_pid);
} else if (info->si_code == SI_KERNEL) {
printf("来源: 内核\n");
} else if (info->si_code == SI_QUEUE) {
printf("来源: sigqueue()\n");
printf("发送进程: %d\n", info->si_pid);
printf("用户ID: %d\n", info->si_uid);
printf("信号值: %d\n", info->si_value.sival_int);
}
// 获取时间戳
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
printf("处理时间: %ld.%09ld\n", ts.tv_sec, ts.tv_nsec);
// 模拟处理时间
usleep(100000); // 100ms
}
// 演示实时信号的基本使用
void demo_realtime_basic() {
printf("\n=== 实时信号基本演示 ===\n");
int rt_signal = SIGRTMIN;
// 设置实时信号处理
struct sigaction act;
memset(&act, 0, sizeof(act));
act.sa_sigaction = realtime_signal_handler;
act.sa_flags = SA_SIGINFO; // 必须设置以获取额外信息
if (sigaction(rt_signal, &act, NULL) == -1) {
perror("sigaction失败");
exit(EXIT_FAILURE);
}
printf("实时信号 %d 处理已设置\n", rt_signal);
printf("进程ID: %d\n", getpid());
// 发送普通实时信号
printf("\n1. 发送普通实时信号\n");
kill(getpid(), rt_signal);
// 发送带数据的实时信号
printf("\n2. 发送带数据的实时信号\n");
union sigval value;
value.sival_int = 12345;
if (sigqueue(getpid(), rt_signal, value) == -1) {
perror("sigqueue失败");
}
// 发送多个信号演示排队
printf("\n3. 发送多个实时信号演示排队\n");
for (int i = 0; i < 5; i++) {
value.sival_int = i * 100;
sigqueue(getpid(), rt_signal, value);
}
// 等待所有信号处理完成
sleep(2);
printf("\n所有实时信号处理完成\n");
}
// 演示实时信号的优先级
void demo_realtime_priority() {
printf("\n=== 实时信号优先级演示 ===\n");
// 设置多个实时信号的处理
struct sigaction act;
memset(&act, 0, sizeof(act));
act.sa_sigaction = realtime_signal_handler;
act.sa_flags = SA_SIGINFO;
int rt_signals[] = {SIGRTMIN, SIGRTMIN + 1, SIGRTMIN + 2};
for (int i = 0; i < 3; i++) {
sigaction(rt_signals[i], &act, NULL);
printf("设置实时信号 %d 处理\n", rt_signals[i]);
}
printf("进程ID: %d\n", getpid());
// 以不同顺序发送信号
printf("\n以不同顺序发送信号:\n");
// 先发送高编号信号
union sigval value;
value.sival_int = 300;
sigqueue(getpid(), rt_signals[2], value);
// 然后发送低编号信号
value.sival_int = 100;
sigqueue(getpid(), rt_signals[0], value);
// 最后发送中间编号信号
value.sival_int = 200;
sigqueue(getpid(), rt_signals[1], value);
// 注意:实时信号按照编号顺序处理,编号小的先处理
// 所以处理顺序应该是:SIGRTMIN, SIGRTMIN+1, SIGRTMIN+2
sleep(2);
}
// 演示实时信号的带宽限制
void demo_realtime_bandwidth() {
printf("\n=== 实时信号带宽限制演示 ===\n");
int rt_signal = SIGRTMIN + 3;
int count = 0;
struct timespec start_time, end_time;
// 简单的计数处理函数
void counting_handler(int signum, siginfo_t* info, void* context) {
count++;
}
// 设置处理函数
struct sigaction act;
memset(&act, 0, sizeof(act));
act.sa_sigaction = counting_handler;
act.sa_flags = SA_SIGINFO;
sigaction(rt_signal, &act, NULL);
printf("测试实时信号处理能力\n");
printf("进程ID: %d\n", getpid());
// 获取开始时间
clock_gettime(CLOCK_MONOTONIC, &start_time);
// 发送大量信号
int total_signals = 10000;
printf("发送 %d 个实时信号...\n", total_signals);
for (int i = 0; i < total_signals; i++) {
union sigval value;
value.sival_int = i;
sigqueue(getpid(), rt_signal, value);
}
// 等待所有信号处理完成
sleep(2);
// 获取结束时间
clock_gettime(CLOCK_MONOTONIC, &end_time);
// 计算耗时
double elapsed = (end_time.tv_sec - start_time.tv_sec) +
(end_time.tv_nsec - start_time.tv_nsec) / 1e9;
printf("处理结果:\n");
printf(" 发送信号数: %d\n", total_signals);
printf(" 处理信号数: %d\n", count);
printf(" 总耗时: %.3f 秒\n", elapsed);
printf(" 处理速率: %.0f 信号/秒\n", count / elapsed);
if (count < total_signals) {
printf("警告:%d 个信号可能丢失或仍在队列中\n", total_signals - count);
}
}
// 演示实时信号在多进程间的通信
void demo_realtime_interprocess() {
printf("\n=== 实时信号进程间通信演示 ===\n");
pid_t pid = fork();
if (pid == 0) {
// 子进程:接收信号
int rt_signal = SIGRTMIN + 4;
void child_handler(int signum, siginfo_t* info, void* context) {
printf("子进程收到信号 %d\n", signum);
printf("来自父进程: %d\n", info->si_pid);
printf("信号值: %d\n", info->si_value.sival_int);
// 回送确认信号
union sigval value;
value.sival_int = info->si_value.sival_int * 2;
if (sigqueue(info->si_pid, rt_signal, value) == -1) {
perror("子进程sigqueue失败");
}
}
// 设置信号处理
struct sigaction act;
memset(&act, 0, sizeof(act));
act.sa_sigaction = child_handler;
act.sa_flags = SA_SIGINFO;
sigaction(rt_signal, &act, NULL);
printf("子进程启动,PID: %d\n", getpid());
// 等待信号
while(1) {
pause();
}
exit(0);
} else if (pid > 0) {
// 父进程:发送信号
sleep(1); // 等待子进程准备
int rt_signal = SIGRTMIN + 4;
void parent_handler(int signum, siginfo_t* info, void* context) {
printf("父进程收到确认信号\n");
printf("来自子进程: %d\n", info->si_pid);
printf("返回值: %d\n", info->si_value.sival_int);
}
// 设置父进程的信号处理
struct sigaction act;
memset(&act, 0, sizeof(act));
act.sa_sigaction = parent_handler;
act.sa_flags = SA_SIGINFO;
sigaction(rt_signal, &act, NULL);
printf("父进程启动,PID: %d\n", getpid());
// 发送多个信号到子进程
for (int i = 1; i <= 5; i++) {
printf("\n父进程发送信号 %d\n", i);
union sigval value;
value.sival_int = i;
if (sigqueue(pid, rt_signal, value) == -1) {
perror("父进程sigqueue失败");
}
// 等待子进程回应
sleep(1);
}
// 等待子进程结束
sleep(2);
kill(pid, SIGTERM);
wait(NULL);
printf("进程间通信演示完成\n");
} else {
perror("fork失败");
exit(EXIT_FAILURE);
}
}
// 演示实时信号与标准信号的比较
void demo_comparison() {
printf("\n=== 实时信号与标准信号比较 ===\n");
int standard_count = 0;
int realtime_count = 0;
// 标准信号处理函数
void standard_handler(int signum) {
standard_count++;
}
// 实时信号处理函数
void realtime_handler(int signum, siginfo_t* info, void* context) {
realtime_count++;
}
// 设置标准信号处理
signal(SIGUSR1, standard_handler);
// 设置实时信号处理
struct sigaction act;
memset(&act, 0, sizeof(act));
act.sa_sigaction = realtime_handler;
act.sa_flags = SA_SIGINFO;
sigaction(SIGRTMIN, &act, NULL);
printf("测试信号丢失情况\n");
printf("进程ID: %d\n", getpid());
// 快速发送标准信号
printf("\n快速发送标准信号(SIGUSR1):\n");
for (int i = 0; i < 1000; i++) {
kill(getpid(), SIGUSR1);
}
// 快速发送实时信号
printf("快速发送实时信号(SIGRTMIN):\n");
for (int i = 0; i < 1000; i++) {
union sigval value;
value.sival_int = i;
sigqueue(getpid(), SIGRTMIN, value);
}
// 等待处理完成
sleep(2);
printf("\n结果比较:\n");
printf(" 标准信号发送: 1000,接收: %d,丢失: %d\n",
standard_count, 1000 - standard_count);
printf(" 实时信号发送: 1000,接收: %d,丢失: %d\n",
realtime_count, 1000 - realtime_count);
printf("\n结论:实时信号支持排队,不会丢失\n");
}
int main() {
demo_realtime_basic();
demo_realtime_priority();
demo_realtime_bandwidth();
demo_realtime_interprocess();
demo_comparison();
return 0;
}
第五章:信号捕捉的最佳实践
5.1 设计安全的信号处理程序
c
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
// 全局标志,使用volatile和sig_atomic_t确保原子性
volatile sig_atomic_t graceful_shutdown = 0;
volatile sig_atomic_t reload_config = 0;
volatile sig_atomic_t emergency_stop = 0;
// 日志函数(信号安全的简化版本)
void safe_log(const char* message) {
// 使用write系统调用,它是异步信号安全的
write(STDERR_FILENO, message, strlen(message));
write(STDERR_FILENO, "\n", 1);
}
// 安全的信号处理函数
void safe_signal_handler(int signum) {
const char* msg = NULL;
switch(signum) {
case SIGINT:
case SIGTERM:
msg = "接收到关闭信号,设置优雅关闭标志";
graceful_shutdown = 1;
break;
case SIGHUP:
msg = "接收到重载配置信号";
reload_config = 1;
break;
case SIGUSR1:
msg = "接收到紧急停止信号";
emergency_stop = 1;
break;
case SIGUSR2:
msg = "接收到状态查询信号";
// 可以在这里设置状态查询标志
break;
default:
// 未知信号,使用默认处理
return;
}
if (msg != NULL) {
safe_log(msg);
}
}
// 初始化信号处理
void init_signal_handling() {
struct sigaction sa;
// 初始化结构体
memset(&sa, 0, sizeof(sa));
// 设置处理函数
sa.sa_handler = safe_signal_handler;
// 设置标志:自动重启被中断的系统调用
sa.sa_flags = SA_RESTART;
// 设置信号掩码:在处理信号时阻塞所有其他信号
sigfillset(&sa.sa_mask);
// 注册信号处理
sigaction(SIGINT, &sa, NULL);
sigaction(SIGTERM, &sa, NULL);
sigaction(SIGHUP, &sa, NULL);
sigaction(SIGUSR1, &sa, NULL);
sigaction(SIGUSR2, &sa, NULL);
// 忽略某些信号
signal(SIGPIPE, SIG_IGN); // 忽略管道破裂信号
signal(SIGCHLD, SIG_IGN); // 忽略子进程退出信号
safe_log("信号处理初始化完成");
}
// 主事件循环
void main_event_loop() {
int iteration = 0;
safe_log("主事件循环开始");
while (!graceful_shutdown && !emergency_stop) {
iteration++;
// 检查配置重载标志
if (reload_config) {
safe_log("重新加载配置");
reload_config = 0; // 清除标志
// 这里可以安全地重新加载配置
// 注意:在信号处理函数中不能做复杂操作
}
// 主循环工作
printf("主循环迭代 %d\n", iteration);
// 模拟工作
for (int i = 0; i < 1000000; i++) {
// 模拟CPU工作
asm volatile("" ::: "memory");
}
// 可以安全地进行I/O操作
sleep(1);
// 检查是否有挂起的信号
sigset_t pending;
sigpending(&pending);
if (sigismember(&pending, SIGINT)) {
safe_log("检测到未决的SIGINT信号");
}
}
// 清理资源
if (emergency_stop) {
safe_log("执行紧急停止");
} else {
safe_log("执行优雅关闭");
}
// 这里可以安全地进行资源清理
safe_log("清理资源...");
sleep(2); // 模拟清理时间
safe_log("程序退出");
}
// 演示错误处理
void demo_error_handling() {
printf("\n=== 信号处理中的错误处理 ===\n");
// 1. 检查sigaction返回值
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = SIG_IGN;
if (sigaction(SIGINT, &sa, NULL) == -1) {
perror("sigaction失败");
// 适当的错误处理
}
// 2. 处理信号掩码错误
sigset_t mask;
if (sigemptyset(&mask) == -1) {
perror("sigemptyset失败");
}
if (sigaddset(&mask, SIGINT) == -1) {
perror("sigaddset失败");
}
// 3. 检查sigprocmask错误
sigset_t oldmask;
if (sigprocmask(SIG_BLOCK, &mask, &oldmask) == -1) {
perror("sigprocmask失败");
}
}
// 演示资源清理
void demo_resource_cleanup() {
printf("\n=== 信号处理中的资源清理 ===\n");
// 文件描述符清理的示例
int fd = open("/tmp/testfile", O_CREAT | O_WRONLY, 0644);
if (fd == -1) {
perror("打开文件失败");
return;
}
// 设置退出时的清理函数
void cleanup_file(void) {
safe_log("清理文件资源");
if (fd != -1) {
close(fd);
unlink("/tmp/testfile");
}
}
// 注册清理函数(注意:atexit不是信号安全的)
atexit(cleanup_file);
// 写入数据
write(fd, "测试数据\n", 9);
printf("文件已创建和写入,程序退出时将自动清理\n");
}
// 演示多线程环境下的信号处理
void demo_multithread_safety() {
printf("\n=== 多线程信号安全 ===\n");
// 在所有线程中阻塞信号
sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, SIGINT);
sigaddset(&mask, SIGTERM);
// 设置线程信号掩码
pthread_sigmask(SIG_BLOCK, &mask, NULL);
printf("在所有线程中阻塞了SIGINT和SIGTERM\n");
// 创建专门的信号处理线程
pthread_t sig_thread;
void* signal_thread_func(void* arg) {
printf("信号处理线程启动\n");
int sig;
while (1) {
// 同步等待信号
sigwait(&mask, &sig);
printf("信号处理线程收到信号: %d\n", sig);
// 这里可以安全地处理信号
// 因为在线程上下文中,不是信号处理函数
}
return NULL;
}
pthread_create(&sig_thread, NULL, signal_thread_func, NULL);
printf("专门的信号处理线程已创建\n");
printf("这避免了在信号处理函数中执行复杂操作的问题\n");
}
int main() {
printf("=== 信号处理最佳实践演示 ===\n");
init_signal_handling();
demo_error_handling();
demo_resource_cleanup();
demo_multithread_safety();
// 运行主事件循环
main_event_loop();
return 0;
}
5.2 避免常见陷阱
c
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <pthread.h>
// 常见陷阱1:在信号处理函数中使用不可重入函数
void trap1_unsafe_handler(int signum) {
printf("不安全:在信号处理函数中使用printf\n"); // 陷阱!
fflush(stdout); // 更不安全!
}
// 常见陷阱2:信号处理函数中调用非异步信号安全函数
void trap2_unsafe_memory_handler(int signum) {
char* buffer = (char*)malloc(100); // 陷阱:使用malloc
if (buffer) {
strcpy(buffer, "不安全"); // 陷阱:使用strcpy
free(buffer); // 陷阱:使用free
}
}
// 常见陷阱3:忽略信号处理后的重置
void trap3_reset_handler(int signum) {
printf("处理信号 %d\n", signum);
// 某些系统在信号处理后会将处理函数重置为默认
// 需要手动重新注册
}
// 常见陷阱4:信号竞争条件
volatile int trap4_counter = 0;
void trap4_race_handler(int signum) {
trap4_counter++; // 潜在的数据竞争!
}
// 常见陷阱5:死锁可能性
pthread_mutex_t trap5_mutex = PTHREAD_MUTEX_INITIALIZER;
void trap5_deadlock_handler(int signum) {
pthread_mutex_lock(&trap5_mutex); // 可能死锁!
printf("获取锁\n");
sleep(2);
pthread_mutex_unlock(&trap5_mutex);
}
// 常见陷阱6:在信号处理函数中调用exit()
void trap6_exit_handler(int signum) {
printf("调用exit()退出\n");
exit(1); // 陷阱:exit()不是异步信号安全的
// 应该使用_exit()或设置标志在主循环中退出
}
// 避免陷阱的正确做法
volatile sig_atomic_t correct_flag = 0;
void correct_handler(int signum) {
// 只设置标志位,不做其他操作
correct_flag = 1;
// 可以安全地使用write
const char* msg = "信号已接收\n";
write(STDERR_FILENO, msg, strlen(msg));
}
// 演示如何避免陷阱1:使用异步信号安全函数
void avoid_trap1() {
printf("\n=== 避免陷阱1:使用异步信号安全函数 ===\n");
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = correct_handler;
sigaction(SIGUSR1, &sa, NULL);
printf("安全的信号处理函数已设置\n");
printf("使用write而不是printf\n");
raise(SIGUSR1);
}
// 演示如何避免陷阱2:避免动态内存分配
void avoid_trap2() {
printf("\n=== 避免陷阱2:避免动态内存分配 ===\n");
// 使用预先分配的缓冲区
static char safe_buffer[256];
volatile sig_atomic_t buffer_ready = 0;
void safe_handler(int signum) {
// 只设置标志,在主循环中处理
buffer_ready = 1;
}
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = safe_handler;
sigaction(SIGUSR2, &sa, NULL);
printf("安全的处理模式:信号处理函数只设置标志\n");
printf("复杂的操作在主循环中执行\n");
}
// 演示如何避免陷阱3:正确管理信号处理函数
void avoid_trap3() {
printf("\n=== 避免陷阱3:正确管理信号处理函数 ===\n");
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
// 使用sigaction而不是signal
// sigaction默认不会重置处理函数
// 或者使用SA_RESETHAND标志控制行为
sa.sa_flags = SA_RESETHAND; // 处理一次后重置为默认
printf("使用sigaction并适当设置标志\n");
}
// 演示如何避免陷阱4:避免竞争条件
void avoid_trap4() {
printf("\n=== 避免陷阱4:避免竞争条件 ===\n");
// 使用sig_atomic_t类型
volatile sig_atomic_t safe_counter = 0;
void safe_counter_handler(int signum) {
safe_counter++; // sig_atomic_t保证原子性
}
// 或者使用原子操作(如果可用)
// __atomic_fetch_add(&counter, 1, __ATOMIC_SEQ_CST);
printf("使用sig_atomic_t类型保证原子性\n");
}
// 演示如何避免陷阱5:避免死锁
void avoid_trap5() {
printf("\n=== 避免陷阱5:避免死锁 ===\n");
// 在信号处理函数中不要获取锁
// 使用无锁数据结构或标志位
volatile sig_atomic_t need_lock_operation = 0;
void no_lock_handler(int signum) {
need_lock_operation = 1; // 只设置标志
}
printf("在信号处理函数中避免使用锁\n");
printf("使用标志位在主循环中执行需要锁的操作\n");
}
// 演示如何避免陷阱6:安全退出
void avoid_trap6() {
printf("\n=== 避免陷阱6:安全退出 ===\n");
volatile sig_atomic_t shutdown_requested = 0;
void safe_exit_handler(int signum) {
shutdown_requested = 1; // 只设置标志
// 如果需要立即退出,使用_exit()
// _exit(EXIT_FAILURE); // 这是安全的
}
printf("使用标志位控制优雅退出\n");
printf("或使用_exit()立即退出\n");
}
// 综合示例:安全的信号处理框架
typedef struct {
volatile sig_atomic_t shutdown_requested;
volatile sig_atomic_t reload_requested;
volatile sig_atomic_t user_signal_received;
int user_signal_number;
} SignalState;
SignalState signal_state = {0, 0, 0, 0};
void master_signal_handler(int signum) {
switch(signum) {
case SIGINT:
case SIGTERM:
signal_state.shutdown_requested = 1;
break;
case SIGHUP:
signal_state.reload_requested = 1;
break;
default:
if (signum >= SIGRTMIN && signum <= SIGRTMAX) {
signal_state.user_signal_received = 1;
signal_state.user_signal_number = signum;
}
break;
}
// 安全地记录信号接收
char buffer[50];
char* pos = buffer;
// 手动构建消息(避免sprintf)
const char* prefix = "收到信号: ";
const char* suffix = "\n";
memcpy(pos, prefix, strlen(prefix));
pos += strlen(prefix);
// 简单地将数字转换为字符串
int n = signum;
if (n == 0) {
*pos++ = '0';
} else {
char rev[10];
int i = 0;
while (n > 0) {
rev[i++] = '0' + (n % 10);
n /= 10;
}
while (--i >= 0) {
*pos++ = rev[i];
}
}
memcpy(pos, suffix, strlen(suffix));
pos += strlen(suffix);
write(STDERR_FILENO, buffer, pos - buffer);
}
void setup_safe_signal_handling() {
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = master_signal_handler;
sa.sa_flags = SA_RESTART;
// 阻塞所有其他信号
sigfillset(&sa.sa_mask);
// 注册信号
sigaction(SIGINT, &sa, NULL);
sigaction(SIGTERM, &sa, NULL);
sigaction(SIGHUP, &sa, NULL);
// 注册实时信号
for (int sig = SIGRTMIN; sig <= SIGRTMIN + 5; sig++) {
sigaction(sig, &sa, NULL);
}
printf("安全的信号处理框架已设置\n");
}
int main() {
printf("=== 避免信号处理常见陷阱 ===\n");
avoid_trap1();
avoid_trap2();
avoid_trap3();
avoid_trap4();
avoid_trap5();
avoid_trap6();
setup_safe_signal_handling();
printf("\n安全的信号处理框架正在运行\n");
printf("进程ID: %d\n", getpid());
printf("可以发送各种信号测试\n");
// 主循环
while (!signal_state.shutdown_requested) {
// 检查并处理信号标志
if (signal_state.reload_requested) {
printf("处理配置重载请求\n");
signal_state.reload_requested = 0;
}
if (signal_state.user_signal_received) {
printf("处理用户信号: %d\n", signal_state.user_signal_number);
signal_state.user_signal_received = 0;
}
// 主循环工作
printf(".");
fflush(stdout);
sleep(1);
}
printf("\n接收到关闭信号,优雅退出\n");
return 0;
}
第六章:示例:完整的信号处理程序
6.1 综合示例:一个健壮的服务器信号处理器
c
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <pthread.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <syslog.h>
// ============================================================================
// 配置部分
// ============================================================================
// 程序状态
typedef enum {
STATE_RUNNING,
STATE_RELOADING,
STATE_SHUTTING_DOWN,
STATE_EMERGENCY_STOP
} ProgramState;
// 信号管理器结构
typedef struct {
volatile ProgramState state;
volatile sig_atomic_t signal_received;
int last_signal;
time_t start_time;
pthread_mutex_t state_mutex;
pthread_cond_t state_cond;
} SignalManager;
// ============================================================================
// 全局变量
// ============================================================================
static SignalManager signal_manager;
static pthread_t signal_thread_id;
static int log_fd = -1;
// ============================================================================
// 日志系统(信号安全版本)
// ============================================================================
// 初始化日志系统
int init_logging() {
// 打开系统日志
openlog("signal-server", LOG_PID | LOG_CONS, LOG_DAEMON);
// 同时记录到文件
log_fd = open("/var/tmp/signal-server.log",
O_WRONLY | O_CREAT | O_APPEND, 0644);
if (log_fd == -1) {
// 如果无法打开文件,使用stderr
log_fd = STDERR_FILENO;
}
return 0;
}
// 安全的日志记录函数
void safe_log(int level, const char* format, ...) {
char buffer[1024];
time_t now;
struct tm tm_info;
va_list args;
// 获取当前时间
time(&now);
localtime_r(&now, &tm_info);
// 格式化时间
strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S ", &tm_info);
int len = strlen(buffer);
// 添加日志级别
const char* level_str;
switch(level) {
case LOG_EMERG: level_str = "[EMERG] "; break;
case LOG_ALERT: level_str = "[ALERT] "; break;
case LOG_CRIT: level_str = "[CRIT] "; break;
case LOG_ERR: level_str = "[ERROR] "; break;
case LOG_WARNING: level_str = "[WARN] "; break;
case LOG_NOTICE: level_str = "[NOTICE] "; break;
case LOG_INFO: level_str = "[INFO] "; break;
case LOG_DEBUG: level_str = "[DEBUG] "; break;
default: level_str = "[UNKNOWN] "; break;
}
strcat(buffer, level_str);
len = strlen(buffer);
// 格式化消息
va_start(args, format);
vsnprintf(buffer + len, sizeof(buffer) - len, format, args);
va_end(args);
// 添加换行符
strcat(buffer, "\n");
// 写入日志
write(log_fd, buffer, strlen(buffer));
// 同时写入系统日志
syslog(level, "%s", buffer + len);
}
// ============================================================================
// 信号处理函数
// ============================================================================
// 信号处理线程函数
void* signal_processing_thread(void* arg) {
sigset_t mask;
int sig;
safe_log(LOG_INFO, "信号处理线程启动,线程ID: %lu", pthread_self());
// 等待的信号集
sigemptyset(&mask);
sigaddset(&mask, SIGINT);
sigaddset(&mask, SIGTERM);
sigaddset(&mask, SIGHUP);
sigaddset(&mask, SIGUSR1);
sigaddset(&mask, SIGUSR2);
sigaddset(&mask, SIGQUIT);
// 添加实时信号
for (int i = SIGRTMIN; i <= SIGRTMIN + 10; i++) {
sigaddset(&mask, i);
}
while (signal_manager.state != STATE_EMERGENCY_STOP) {
// 等待信号
if (sigwait(&mask, &sig) != 0) {
safe_log(LOG_ERR, "sigwait失败: %s", strerror(errno));
continue;
}
safe_log(LOG_INFO, "接收到信号: %d (%s)", sig, strsignal(sig));
// 处理信号
pthread_mutex_lock(&signal_manager.state_mutex);
signal_manager.last_signal = sig;
signal_manager.signal_received = 1;
switch(sig) {
case SIGINT:
case SIGTERM:
safe_log(LOG_NOTICE, "接收到终止信号,开始优雅关闭");
signal_manager.state = STATE_SHUTTING_DOWN;
break;
case SIGHUP:
safe_log(LOG_NOTICE, "接收到重载配置信号");
signal_manager.state = STATE_RELOADING;
break;
case SIGUSR1:
safe_log(LOG_INFO, "接收到状态查询信号");
// 状态查询不改变主状态
break;
case SIGUSR2:
safe_log(LOG_INFO, "接收到调试信号");
// 触发调试信息输出
break;
case SIGQUIT:
safe_log(LOG_WARNING, "接收到退出信号,紧急停止");
signal_manager.state = STATE_EMERGENCY_STOP;
break;
default:
// 处理实时信号
if (sig >= SIGRTMIN && sig <= SIGRTMAX) {
safe_log(LOG_DEBUG, "接收到实时信号: %d", sig);
// 实时信号处理逻辑
}
break;
}
// 通知等待状态变化的线程
pthread_cond_broadcast(&signal_manager.state_cond);
pthread_mutex_unlock(&signal_manager.state_mutex);
}
safe_log(LOG_INFO, "信号处理线程退出");
return NULL;
}
// ============================================================================
// 初始化函数
// ============================================================================
// 初始化信号管理器
int init_signal_manager() {
// 初始化信号管理器
memset(&signal_manager, 0, sizeof(signal_manager));
signal_manager.state = STATE_RUNNING;
signal_manager.start_time = time(NULL);
// 初始化互斥锁和条件变量
pthread_mutex_init(&signal_manager.state_mutex, NULL);
pthread_cond_init(&signal_manager.state_cond, NULL);
// 在主线程中阻塞所有信号
sigset_t all_signals;
sigfillset(&all_signals);
pthread_sigmask(SIG_BLOCK, &all_signals, NULL);
// 创建信号处理线程
if (pthread_create(&signal_thread_id, NULL,
signal_processing_thread, NULL) != 0) {
safe_log(LOG_ERR, "创建信号处理线程失败: %s", strerror(errno));
return -1;
}
safe_log(LOG_INFO, "信号管理器初始化完成");
return 0;
}
// ============================================================================
// 服务器主循环
// ============================================================================
// 工作线程函数
void* worker_thread(void* arg) {
int thread_id = *(int*)arg;
safe_log(LOG_INFO, "工作线程 %d 启动", thread_id);
while (1) {
pthread_mutex_lock(&signal_manager.state_mutex);
// 检查程序状态
while (signal_manager.state == STATE_RELOADING) {
safe_log(LOG_INFO, "工作线程 %d 等待重载完成", thread_id);
pthread_cond_wait(&signal_manager.state_cond,
&signal_manager.state_mutex);
}
if (signal_manager.state == STATE_SHUTTING_DOWN ||
signal_manager.state == STATE_EMERGENCY_STOP) {
pthread_mutex_unlock(&signal_manager.state_mutex);
break;
}
pthread_mutex_unlock(&signal_manager.state_mutex);
// 执行工作
safe_log(LOG_DEBUG, "工作线程 %d 执行任务", thread_id);
// 模拟工作负载
struct timespec sleep_time = {0, 100000000}; // 100ms
nanosleep(&sleep_time, NULL);
// 定期检查信号
if (signal_manager.signal_received) {
safe_log(LOG_DEBUG, "工作线程 %d 检测到信号", thread_id);
// 可以在这里处理线程特定的信号逻辑
}
}
safe_log(LOG_INFO, "工作线程 %d 退出", thread_id);
return NULL;
}
// 配置重载函数
void reload_configuration() {
safe_log(LOG_NOTICE, "开始重载配置");
// 模拟配置重载
sleep(2);
safe_log(LOG_NOTICE, "配置重载完成");
}
// 状态报告函数
void report_status() {
time_t now = time(NULL);
time_t uptime = now - signal_manager.start_time;
char buffer[256];
snprintf(buffer, sizeof(buffer),
"服务器状态报告:\n"
" 运行状态: %s\n"
" 运行时间: %ld秒\n"
" 最后信号: %d\n"
" 工作线程: 活跃\n",
signal_manager.state == STATE_RUNNING ? "运行中" :
signal_manager.state == STATE_RELOADING ? "重载中" :
signal_manager.state == STATE_SHUTTING_DOWN ? "关闭中" :
"紧急停止",
uptime,
signal_manager.last_signal);
write(STDOUT_FILENO, buffer, strlen(buffer));
}
// 主服务器循环
void server_main_loop() {
pthread_t workers[5];
int thread_ids[5] = {1, 2, 3, 4, 5};
safe_log(LOG_NOTICE, "服务器主循环启动");
// 创建工作线程
for (int i = 0; i < 5; i++) {
pthread_create(&workers[i], NULL, worker_thread, &thread_ids[i]);
}
// 主循环
while (signal_manager.state != STATE_SHUTTING_DOWN &&
signal_manager.state != STATE_EMERGENCY_STOP) {
// 检查是否需要重载配置
if (signal_manager.state == STATE_RELOADING) {
reload_configuration();
pthread_mutex_lock(&signal_manager.state_mutex);
signal_manager.state = STATE_RUNNING;
pthread_cond_broadcast(&signal_manager.state_cond);
pthread_mutex_unlock(&signal_manager.state_mutex);
}
// 检查状态查询信号
if (signal_manager.last_signal == SIGUSR1) {
report_status();
signal_manager.last_signal = 0;
}
// 主循环工作
safe_log(LOG_DEBUG, "主循环运行中...");
// 等待一段时间或条件变量
pthread_mutex_lock(&signal_manager.state_mutex);
struct timespec timeout;
clock_gettime(CLOCK_REALTIME, &timeout);
timeout.tv_sec += 5; // 5秒超时
pthread_cond_timedwait(&signal_manager.state_cond,
&signal_manager.state_mutex, &timeout);
pthread_mutex_unlock(&signal_manager.state_mutex);
}
// 等待工作线程结束
safe_log(LOG_NOTICE, "等待工作线程结束...");
for (int i = 0; i < 5; i++) {
pthread_join(workers[i], NULL);
}
// 等待信号处理线程结束
pthread_join(signal_thread_id, NULL);
safe_log(LOG_NOTICE, "所有线程已结束");
}
// ============================================================================
// 清理函数
// ============================================================================
void cleanup_resources() {
safe_log(LOG_INFO, "清理资源");
// 关闭日志文件
if (log_fd != -1 && log_fd != STDERR_FILENO) {
close(log_fd);
}
// 关闭系统日志
closelog();
// 销毁互斥锁和条件变量
pthread_mutex_destroy(&signal_manager.state_mutex);
pthread_cond_destroy(&signal_manager.state_cond);
safe_log(LOG_NOTICE, "资源清理完成");
}
// ============================================================================
// 主函数
// ============================================================================
int main(int argc, char* argv[]) {
printf("=== 健壮的服务器信号处理器示例 ===\n");
printf("进程ID: %d\n", getpid());
printf("使用以下命令测试:\n");
printf(" 优雅关闭: kill -TERM %d 或 Ctrl+C\n", getpid());
printf(" 重载配置: kill -HUP %d\n", getpid());
printf(" 状态查询: kill -USR1 %d\n", getpid());
printf(" 调试模式: kill -USR2 %d\n", getpid());
printf(" 紧急停止: kill -QUIT %d\n", getpid());
printf(" 实时信号: kill -%d %d\n", SIGRTMIN, getpid());
printf("\n");
// 初始化日志系统
if (init_logging() != 0) {
fprintf(stderr, "初始化日志系统失败\n");
return EXIT_FAILURE;
}
safe_log(LOG_NOTICE, "服务器启动,进程ID: %d", getpid());
// 初始化信号管理器
if (init_signal_manager() != 0) {
safe_log(LOG_ERR, "初始化信号管理器失败");
cleanup_resources();
return EXIT_FAILURE;
}
// 运行服务器主循环
server_main_loop();
// 清理资源
cleanup_resources();
safe_log(LOG_NOTICE, "服务器正常退出");
return EXIT_SUCCESS;
}
6.2 编译和运行说明
makefile
# Makefile for signal server example
CC = gcc
CFLAGS = -Wall -Wextra -O2 -pthread -D_GNU_SOURCE
TARGET = signal_server
SOURCES = signal_server.c
LIBS = -lpthread
all: $(TARGET)
$(TARGET): $(SOURCES)
$(CC) $(CFLAGS) -o $(TARGET) $(SOURCES) $(LIBS)
debug: $(SOURCES)
$(CC) $(CFLAGS) -g -DDEBUG -o $(TARGET)_debug $(SOURCES) $(LIBS)
clean:
rm -f $(TARGET) $(TARGET)_debug
install: $(TARGET)
cp $(TARGET) /usr/local/bin/
test: $(TARGET)
./$(TARGET) &
.PHONY: all debug clean install test
bash
#!/bin/bash
# 测试脚本:test_signals.sh
SERVER_PID=$1
if [ -z "$SERVER_PID" ]; then
echo "用法: $0 <服务器进程ID>"
exit 1
fi
echo "测试信号处理器 - 服务器PID: $SERVER_PID"
echo "========================================"
# 测试各种信号
signals=("SIGHUP" "SIGUSR1" "SIGUSR2" "SIGRTMIN" "SIGTERM")
for sig in "${signals[@]}"; do
echo -n "发送 $sig... "
kill -$sig $SERVER_PID 2>/dev/null
if [ $? -eq 0 ]; then
echo "成功"
else
echo "失败"
fi
sleep 2
done
echo "测试完成"
总结
Linux信号机制是一个强大而复杂的系统特性,通过本文的详细探讨,我们深入了解了:
-
信号的基本概念:信号是进程间通信的异步机制,用于通知进程发生了特定事件。
-
信号捕捉机制:
signal()函数提供简单的信号处理,但存在可移植性问题sigaction()函数是现代程序推荐使用的信号处理接口,提供更强大的控制能力
-
关键特性:
- 可重入性:信号处理函数必须使用异步信号安全函数
- 信号屏蔽:可以临时阻塞信号,保护关键代码段
- 可靠性:实时信号支持排队,不会丢失
- 处理期间的屏蔽:系统自动防止信号处理函数的递归调用
-
高级技术:
- 使用
sigwait()同步处理信号,特别适合多线程程序 - 信号栈可以防止栈溢出
- 实时信号提供可靠通信和额外数据传递
- 使用
-
最佳实践:
- 信号处理函数应尽可能简单,只设置标志位
- 复杂处理应在主循环中进行
- 避免在信号处理函数中使用非异步信号安全函数
- 正确处理信号竞争条件和资源清理
-
完整示例:展示了如何构建一个健壮的服务器信号处理器,包括线程安全的信号处理、配置重载、优雅关闭等功能。
掌握Linux信号处理对于编写稳定、可靠的系统程序至关重要。正确的信号处理可以确保程序在异常情况下优雅地恢复或关闭,避免数据损坏和资源泄漏。随着Linux系统的发展,信号机制仍然是进程控制和进程间通信的重要工具,值得每个Linux开发者深入学习和掌握。
通过本文的学习,读者应该能够:
- 理解Linux信号机制的工作原理
- 选择合适的信号处理策略
- 避免常见的信号处理陷阱
- 设计健壮的信号处理框架
- 在实际项目中应用信号处理技术
信号处理是Linux系统编程的艺术之一,需要结合理论知识和实践经验。希望本文能为读者提供全面的指导,帮助大家在日常开发中更好地利用这一强大的机制。