ITPub博客

首页 > 应用开发 > IT综合 > Linux信号处理艺术:异步安全与可重入函数实践

Linux信号处理艺术:异步安全与可重入函数实践

翻译 IT综合 作者:bhluo 时间:2026-02-06 11:31:43 0 删除 编辑

# Linux信号处理艺术:异步安全与可重入函数实践


在Unix/Linux系统中,信号是进程间通信的基本机制之一,但信号处理的复杂性常常被低估。本文将深入探讨信号处理的核心概念,特别是可重入函数的重要性,并提供实用的编程指南。


## 信号的本质与分类


信号是发送给进程的异步通知,通常用于通知进程发生了某种事件。Linux支持多种标准信号,每种信号都有特定用途。


```c

#include <signal.h>

#include <stdio.h>

#include <unistd.h>


// 常见信号分类

void list_common_signals() {

    printf("进程终止信号:\n");

    printf("  SIGTERM (%d) - 优雅终止请求\n", SIGTERM);

    printf("  SIGKILL (%d) - 强制终止(不可捕获)\n", SIGKILL);

    printf("  SIGINT (%d) - 中断(通常来自Ctrl+C)\n", SIGINT);

    

    printf("\n进程异常信号:\n");

    printf("  SIGSEGV (%d) - 段错误(内存访问违规)\n", SIGSEGV);

    printf("  SIGFPE (%d) - 浮点异常\n", SIGFPE);

    printf("  SIGILL (%d) - 非法指令\n", SIGILL);

    

    printf("\n进程控制信号:\n");

    printf("  SIGCHLD (%d) - 子进程状态改变\n", SIGCHLD);

    printf("  SIGCONT (%d) - 继续执行被暂停的进程\n", SIGCONT);

    printf("  SIGSTOP (%d) - 暂停进程执行(不可捕获)\n", SIGSTOP);

    

    printf("\n用户定义信号:\n");

    printf("  SIGUSR1 (%d), SIGUSR2 (%d)\n", SIGUSR1, SIGUSR2);

}

```


## 信号处理的基本方法


### 传统的signal()函数


```c

#include <stdlib.h>

#include <errno.h>


// 简单的信号处理示例

void sigint_handler(int signo) {

    printf("\n收到SIGINT信号(%d)\n", signo);

    printf("正在清理资源...\n");

    exit(0);

}


void setup_signal_handlers() {

    // 注册SIGINT信号处理函数

    if (signal(SIGINT, sigint_handler) == SIG_ERR) {

        perror("无法设置SIGINT处理器");

        exit(1);

    }

    

    // 忽略SIGPIPE信号(防止网络连接断开导致进程终止)

    if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) {

        perror("无法忽略SIGPIPE");

    }

}

```


### 现代的sigaction()函数


```c

#include <string.h>


// 使用sigaction的更可靠信号处理

void setup_signal_handlers_modern() {

    struct sigaction sa;

    

    // 设置SIGINT处理器

    memset(&sa, 0, sizeof(sa));

    sa.sa_handler = sigint_handler;

    sa.sa_flags = SA_RESTART;  // 系统调用被中断后自动重启

    

    // 阻塞其他信号,防止处理期间被中断

    sigemptyset(&sa.sa_mask);

    sigaddset(&sa.sa_mask, SIGTERM);

    

    if (sigaction(SIGINT, &sa, NULL) == -1) {

        perror("sigaction failed");

        exit(1);

    }

    

    // 设置SIGCHLD处理器,防止僵尸进程

    memset(&sa, 0, sizeof(sa));

    sa.sa_handler = SIG_DFL;  // 使用默认处理

    sa.sa_flags = SA_NOCLDWAIT | SA_NOCLDSTOP;

    

    if (sigaction(SIGCHLD, &sa, NULL) == -1) {

        perror("sigaction failed for SIGCHLD");

    }

}

```


## 可重入函数的概念与重要性


### 什么是可重入函数


可重入函数可以在任何时刻被中断,然后在中断结束后继续执行,而不会影响功能正确性。这类函数不依赖静态数据,不使用全局变量,也不调用不可重入函数。


```c

// 不可重入函数的例子

#include <string.h>

#include <pthread.h>


static char buffer[256];  // 静态缓冲区


// 不可重入版本:使用静态缓冲区

char* unsafe_strtok(char* str, const char* delim) {

    static char* saved;  // 静态变量保存状态

    return strtok_r(str, delim, &saved);  // 实际应使用strtok_r

}


// 可重入函数的例子

char* safe_strtok_r(char* str, const char* delim, char** saveptr) {

    // 所有状态通过参数传递,不使用静态变量

    if (str != NULL) {

        *saveptr = str;

    }

    

    char* start = *saveptr;

    if (*start == '\0') return NULL;

    

    // 查找分隔符

    while (*start != '\0') {

        const char* d = delim;

        while (*d != '\0') {

            if (*start == *d) {

                *start = '\0';

                (*saveptr) = start + 1;

                return (*saveptr - 1);

            }

            d++;

        }

        start++;

    }

    

    *saveptr = start;

    return (*saveptr == start) ? NULL : start;

}

```


## 异步信号安全函数


异步信号安全函数是可以在信号处理器中安全调用的函数。这些函数要么是可重入的,要么在信号处理期间阻塞其他信号。


```c

#include <signal.h>

#include <unistd.h>

#include <sys/types.h>


// 异步信号安全函数示例

volatile sig_atomic_t signal_received = 0;


void signal_handler(int sig) {

    // 只有极少数函数可以在信号处理器中安全调用

    signal_received = sig;

    

    // 安全:write()是异步信号安全的

    const char msg[] = "Signal received\n";

    write(STDOUT_FILENO, msg, sizeof(msg) - 1);

    

    // 危险:printf()不是异步信号安全的

    // printf("Signal %d received\n", sig);  // 可能导致死锁

    

    // 危险:malloc()不是异步信号安全的

    // void* p = malloc(100);  // 可能导致堆损坏

}


// 信号安全的内存分配器示例

void* signal_safe_malloc(size_t size) {

    // 使用mmap而不是malloc,因为mmap是信号安全的

    void* ptr = mmap(NULL, size, 

                    PROT_READ | PROT_WRITE,

                    MAP_PRIVATE | MAP_ANON,

                    -1, 0);

    

    if (ptr == MAP_FAILED) {

        return NULL;

    }

    

    return ptr;

}

```


## 线程与信号的交互


```c

#include <pthread.h>

#include <signal.h>


// 线程特定的信号处理

void* thread_function(void* arg) {

    sigset_t set;

    <"v9.h4k7.org.cn"><"s2.h4k7.org.cn"><"f5.h4k7.org.cn">

    // 阻塞所有信号

    sigfillset(&set);

    pthread_sigmask(SIG_BLOCK, &set, NULL);

    

    // 线程执行实际工作,不会被信号中断

    

    // 解除阻塞,恢复默认信号处理

    sigemptyset(&set);

    pthread_sigmask(SIG_SETMASK, &set, NULL);

    

    return NULL;

}


// 主线程设置信号处理

void setup_thread_signals() {

    pthread_t thread;

    sigset_t set;

    

    // 创建新线程前阻塞某些信号

    sigemptyset(&set);

    sigaddset(&set, SIGINT);

    sigaddset(&set, SIGTERM);

    pthread_sigmask(SIG_BLOCK, &set, NULL);

    

    pthread_create(&thread, NULL, thread_function, NULL);

    

    // 在新线程中处理特定信号

    pthread_sigmask(SIG_UNBLOCK, &set, NULL);

    

    pthread_join(thread, NULL);

}

```


## 实际应用:可靠的信号处理框架


```c

#include <stdlib.h>

#include <sys/select.h>


// 信号处理框架结构

typedef struct {

    volatile sig_atomic_t signals_received;

    int signal_pipe[2];  // 用于信号传递的管道

} signal_framework_t;


// 全局框架实例

static signal_framework_t g_sig_framework;


// 信号处理器:仅设置标志,通过管道通知主循环

static void sig_handler(int sig) {

    // 原子操作设置信号标志

    g_sig_framework.signals_received = 1;

    

    // 通过管道通知主循环

    char sig_byte = (char)sig;

    write(g_sig_framework.signal_pipe[1], &sig_byte, 1);

}


// 初始化信号框架

int init_signal_framework() {

    // 创建管道用于信号通信

    if (pipe(g_sig_framework.signal_pipe) == -1) {

        return -1;

    }

    

    // 设置非阻塞写入端

    fcntl(g_sig_framework.signal_pipe[1], F_SETFL, 

          fcntl(g_sig_framework.signal_pipe[1], F_GETFL) | O_NONBLOCK);

    

    struct sigaction sa;

    memset(&sa, 0, sizeof(sa));

    sa.sa_handler = sig_handler;

    sa.sa_flags = SA_RESTART;

    

    // 设置要处理的信号

    sigaction(SIGINT, &sa, NULL);

    sigaction(SIGTERM, &sa, NULL);

    sigaction(SIGUSR1, &sa, NULL);

    

    return 0;

}


// 主事件循环

void event_loop() {

    fd_set readfds;

    

    while (1) {

        FD_ZERO(&readfds);

        FD_SET(g_sig_framework.signal_pipe[0], &readfds);

        

        // 等待信号或数据

        if (select(g_sig_framework.signal_pipe[0] + 1, 

                  &readfds, NULL, NULL, NULL) > 0) {

            

            if (FD_ISSET(g_sig_framework.signal_pipe[0], &readfds)) {

                char sig_num;

                read(g_sig_framework.signal_pipe[0], &sig_num, 1);

                

                // 在主循环中安全处理信号

                handle_signal_safely(sig_num);

            }

        }

    }

}

```


## 可重入数据结构设计


```c

#include <stdatomic.h>


// 可重入的计数器

typedef struct {

    _Atomic int value;

} reentrant_counter_t;


void counter_init(reentrant_counter_t* counter) {

    atomic_init(&counter->value, 0);

}


int counter_increment(reentrant_counter_t* counter) {

    return atomic_fetch_add(&counter->value, 1) + 1;

}


// 可重入的链表节点

typedef struct reentrant_node {

    void* data;

    struct reentrant_node* next;

    pthread_mutex_t lock;

} reentrant_node_t;


// 可重入的链表插入操作

int reentrant_list_insert(reentrant_node_t** head, void* data) {

    reentrant_node_t* new_node = malloc(sizeof(reentrant_node_t));

    if (!new_node) return -1;

    

    new_node->data = data;

    pthread_mutex_init(&new_node->lock, NULL);

    

    // 使用原子操作更新链表头

    reentrant_node_t* old_head;

    do {

        old_head = atomic_load(head);

        new_node->next = old_head;

    } while (!atomic_compare_exchange_weak(head, &old_head, new_node));

    

    return 0;

}

```


## 信号处理中的常见陷阱


```c

// 陷阱1:信号处理中调用不可重入函数

void dangerous_signal_handler(int sig) {

    // 危险:调用printf(使用全局的stdout缓冲区)

    printf("Signal %d received\n", sig);

    

    // 危险:调用malloc(操作全局堆结构)

    // void* p = malloc(100);

    

    // 正确做法:只设置标志,在主循环中处理

}


// 陷阱2:忽略信号处理中的错误

void unsafe_signal_handler(int sig) {

    // 没有错误处理

    int fd = open("/tmp/file", O_WRONLY);

    write(fd, "signal", 6);  // 如果fd为-1会怎样?

    close(fd);  // 如果fd为-1会怎样?

}


// 正确的信号处理器

void safe_signal_handler(int sig) {

    // 只做最少的工作

    volatile static sig_atomic_t flag = 0;

    flag = 1;

    <"z1.h4k7.org.cn"><"j8.h4k7.org.cn"><"n0.h4k7.org.cn">

    // 使用异步信号安全函数

    const char msg[] = "Signal received\n";

    write(STDERR_FILENO, msg, sizeof(msg) - 1);

}

```


## 性能优化与最佳实践


```c

// 信号阻塞优化

void optimize_signal_handling() {

    sigset_t oldset, newset;

    

    // 保存当前信号掩码

    sigprocmask(SIG_SETMASK, NULL, &oldset);

    

    // 设置新的信号掩码,阻塞不需要的信号

    sigemptyset(&newset);

    sigaddset(&newset, SIGALRM);

    sigaddset(&newset, SIGVTALRM);

    

    // 关键代码段:阻塞信号防止中断

    sigprocmask(SIG_BLOCK, &newset, NULL);

    

    // 执行关键操作

    perform_critical_operation();

    

    // 恢复信号掩码

    sigprocmask(SIG_SETMASK, &oldset, NULL);

}


// 信号延迟处理模式

typedef struct {

    int signal_number;

    void* context;

    struct timespec timestamp;

} deferred_signal_t;


deferred_signal_t signal_queue[10];

_Atomic int signal_queue_head = 0;

_Atomic int signal_queue_tail = 0;


// 信号处理器:仅将信号加入队列

void deferred_signal_handler(int sig) {

    int next_tail = (signal_queue_tail + 1) % 10;

    

    // 简单队列满检测

    if (next_tail != signal_queue_head) {

        signal_queue[signal_queue_tail].signal_number = sig;

        clock_gettime(CLOCK_MONOTONIC, 

                     &signal_queue[signal_queue_tail].timestamp);

        signal_queue_tail = next_tail;

    }

}


// 主循环中处理延迟的信号

void process_deferred_signals() {

    while (signal_queue_head != signal_queue_tail) {

        deferred_signal_t* sig = &signal_queue[signal_queue_head];

        

        // 安全地处理信号(不在信号上下文中)

        handle_signal_in_safe_context(sig);

        

        signal_queue_head = (signal_queue_head + 1) % 10;

    }

}

```


## 调试与测试工具


```c

// 信号调试辅助函数

#include <execinfo.h>


void signal_debug_handler(int sig, siginfo_t* info, void* context) {

    void* array[50];

    size_t size;

    

    // 获取调用栈

    size = backtrace(array, 50);

    

    // 打印调用栈到stderr(write是信号安全的)

    const char msg[] = "\n=== Signal received ===\n";

    write(STDERR_FILENO, msg, sizeof(msg) - 1);

    

    // 保存调用栈到文件

    int fd = open("/tmp/signal_debug.log", 

                  O_WRONLY | O_CREAT | O_APPEND, 0644);

    if (fd >= 0) {

        backtrace_symbols_fd(array, size, fd);

        close(fd);

    }

    

    // 重新触发默认处理

    signal(sig, SIG_DFL);

    raise(sig);

}


// 设置调试信号处理器

void setup_debug_signal_handlers() {

    struct sigaction sa;

    memset(&sa, 0, sizeof(sa));

    sa.sa_sigaction = signal_debug_handler;

    sa.sa_flags = SA_SIGINFO | SA_RESETHAND;

    

    sigaction(SIGSEGV, &sa, NULL);

    sigaction(SIGABRT, &sa, NULL);

    sigaction(SIGFPE, &sa, NULL);

}

```


## 总结与最佳实践


信号处理是Linux系统编程中的重要课题,正确处理信号需要遵循以下原则:


1. **保持信号处理器简单**:只设置标志,在主循环中处理实际逻辑

2. **使用异步信号安全函数**:在信号处理器中只调用保证安全的函数

3. **避免竞态条件**:使用`sig_atomic_t`类型和原子操作

4. **正确处理信号阻塞**:在关键代码段阻塞不必要的信号

5. **考虑可重入性**:设计函数时避免使用全局状态


特别需要注意的是:

- 使用`sigaction`而不是过时的`signal`函数

- 为多线程程序使用`pthread_sigmask`

- 避免在信号处理器中进行复杂的资源分配

- 使用管道或实时信号进行信号传递


通过理解信号处理的机制和限制,开发者可以编写出更稳定、更可靠的Linux应用程序。信号处理虽然复杂,但掌握了正确的模式和方法后,就能充分利用这一强大的进程间通信机制。


来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/19545/viewspace-3110097/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2015-11-01

  • 博文量
    895
  • 访问量
    933924