MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Linux C语言信号处理的信号集操作

2023-02-124.4k 阅读

信号集概述

在Linux环境下使用C语言进行信号处理时,信号集是一个非常重要的概念。信号集是一种数据结构,用于表示一组信号。它提供了一种方便的方式来操作和管理多个信号。在Linux系统中,信号集的数据类型为 sigset_t,定义在 <signal.h> 头文件中。

信号集主要用于以下几个方面:

  1. 阻塞信号:可以将某些信号添加到信号集中,然后使用系统调用将这些信号阻塞,使得进程在特定时间段内不会响应这些信号。
  2. 检查信号:可以检查某个信号是否在信号集中,以此来判断进程是否应该处理该信号。

信号集操作函数

sigemptyset函数

sigemptyset 函数用于初始化一个信号集,将其设置为空集,即不包含任何信号。函数原型如下:

#include <signal.h>
int sigemptyset(sigset_t *set);

参数 set 是指向要初始化的信号集的指针。函数成功时返回0,失败时返回 -1。

示例代码:

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

int main() {
    sigset_t myset;
    if (sigemptyset(&myset) == -1) {
        perror("sigemptyset");
        return 1;
    }
    printf("信号集已初始化为空集\n");
    return 0;
}

sigfillset函数

sigfillset 函数用于初始化一个信号集,将其设置为包含所有信号。函数原型如下:

#include <signal.h>
int sigfillset(sigset_t *set);

参数 set 是指向要初始化的信号集的指针。函数成功时返回0,失败时返回 -1。

示例代码:

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

int main() {
    sigset_t myset;
    if (sigfillset(&myset) == -1) {
        perror("sigfillset");
        return 1;
    }
    printf("信号集已初始化为包含所有信号\n");
    return 0;
}

sigaddset函数

sigaddset 函数用于将一个特定的信号添加到信号集中。函数原型如下:

#include <signal.h>
int sigaddset(sigset_t *set, int signum);

参数 set 是指向要操作的信号集的指针,signum 是要添加的信号编号。函数成功时返回0,失败时返回 -1。

示例代码:

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

int main() {
    sigset_t myset;
    if (sigemptyset(&myset) == -1) {
        perror("sigemptyset");
        return 1;
    }
    if (sigaddset(&myset, SIGINT) == -1) {
        perror("sigaddset");
        return 1;
    }
    printf("已将SIGINT信号添加到信号集\n");
    return 0;
}

sigdelset函数

sigdelset 函数用于将一个特定的信号从信号集中移除。函数原型如下:

#include <signal.h>
int sigdelset(sigset_t *set, int signum);

参数 set 是指向要操作的信号集的指针,signum 是要移除的信号编号。函数成功时返回0,失败时返回 -1。

示例代码:

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

int main() {
    sigset_t myset;
    if (sigemptyset(&myset) == -1) {
        perror("sigemptyset");
        return 1;
    }
    if (sigaddset(&myset, SIGINT) == -1) {
        perror("sigaddset");
        return 1;
    }
    if (sigdelset(&myset, SIGINT) == -1) {
        perror("sigdelset");
        return 1;
    }
    printf("已将SIGINT信号从信号集移除\n");
    return 0;
}

sigismember函数

sigismember 函数用于检查一个特定的信号是否在信号集中。函数原型如下:

#include <signal.h>
int sigismember(const sigset_t *set, int signum);

参数 set 是指向要检查的信号集的指针,signum 是要检查的信号编号。如果信号在信号集中,函数返回1;如果信号不在信号集中,函数返回0;如果发生错误,函数返回 -1。

示例代码:

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

int main() {
    sigset_t myset;
    if (sigemptyset(&myset) == -1) {
        perror("sigemptyset");
        return 1;
    }
    if (sigaddset(&myset, SIGINT) == -1) {
        perror("sigaddset");
        return 1;
    }
    if (sigismember(&myset, SIGINT) == 1) {
        printf("SIGINT信号在信号集中\n");
    } else if (sigismember(&myset, SIGINT) == 0) {
        printf("SIGINT信号不在信号集中\n");
    } else {
        perror("sigismember");
        return 1;
    }
    return 0;
}

使用信号集阻塞信号

在Linux系统中,可以使用信号集来阻塞某些信号,使得进程在特定时间段内不会响应这些信号。这可以通过 sigprocmask 函数来实现。

sigprocmask函数

sigprocmask 函数用于查询或更改进程的信号掩码。函数原型如下:

#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
  • how 参数指定如何更改信号掩码,有以下几个取值:
    • SIG_BLOCK:将 set 中的信号添加到当前信号掩码中。
    • SIG_UNBLOCK:将 set 中的信号从当前信号掩码中移除。
    • SIG_SETMASK:将当前信号掩码设置为 set
  • set 参数是指向要操作的信号集的指针,如果 howSIG_BLOCKSIG_SETMASK,则 set 不能为空。
  • oldset 参数是指向一个信号集的指针,用于保存旧的信号掩码。如果不需要保存旧的信号掩码,可以将其设置为 NULL

函数成功时返回0,失败时返回 -1。

示例代码:

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

void signal_handler(int signum) {
    printf("捕获到信号 %d\n", signum);
}

int main() {
    struct sigaction sa;
    sigset_t myset;

    // 初始化信号处理函数
    sa.sa_handler = signal_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;
    if (sigaction(SIGINT, &sa, NULL) == -1) {
        perror("sigaction");
        return 1;
    }

    // 初始化信号集并添加SIGINT信号
    if (sigemptyset(&myset) == -1) {
        perror("sigemptyset");
        return 1;
    }
    if (sigaddset(&myset, SIGINT) == -1) {
        perror("sigaddset");
        return 1;
    }

    // 阻塞SIGINT信号
    if (sigprocmask(SIG_BLOCK, &myset, NULL) == -1) {
        perror("sigprocmask");
        return 1;
    }
    printf("已阻塞SIGINT信号,接下来睡眠5秒\n");
    sleep(5);

    // 解除阻塞SIGINT信号
    if (sigprocmask(SIG_UNBLOCK, &myset, NULL) == -1) {
        perror("sigprocmask");
        return 1;
    }
    printf("已解除阻塞SIGINT信号\n");

    while (1) {
        // 循环等待信号
        pause();
    }

    return 0;
}

在上述代码中,首先设置了 SIGINT 信号的处理函数,然后初始化一个信号集并添加 SIGINT 信号。接着使用 sigprocmask 函数阻塞 SIGINT 信号,睡眠5秒后再解除阻塞。最后进入一个无限循环,使用 pause 函数等待信号。在阻塞期间,发送 SIGINT 信号(通常通过按下 Ctrl+C)不会立即触发信号处理函数,而在解除阻塞后,发送 SIGINT 信号会触发信号处理函数。

使用信号集检查未决信号

未决信号是指已经产生但由于被阻塞而尚未被处理的信号。可以使用 sigpending 函数来获取当前进程的未决信号集。

sigpending函数

sigpending 函数用于获取当前进程的未决信号集。函数原型如下:

#include <signal.h>
int sigpending(sigset_t *set);

参数 set 是指向一个信号集的指针,用于保存未决信号集。函数成功时返回0,失败时返回 -1。

示例代码:

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

void signal_handler(int signum) {
    printf("捕获到信号 %d\n", signum);
}

int main() {
    struct sigaction sa;
    sigset_t myset, pendingset;

    // 初始化信号处理函数
    sa.sa_handler = signal_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;
    if (sigaction(SIGINT, &sa, NULL) == -1) {
        perror("sigaction");
        return 1;
    }

    // 初始化信号集并添加SIGINT信号
    if (sigemptyset(&myset) == -1) {
        perror("sigemptyset");
        return 1;
    }
    if (sigaddset(&myset, SIGINT) == -1) {
        perror("sigaddset");
        return 1;
    }

    // 阻塞SIGINT信号
    if (sigprocmask(SIG_BLOCK, &myset, NULL) == -1) {
        perror("sigprocmask");
        return 1;
    }
    printf("已阻塞SIGINT信号,接下来睡眠5秒\n");
    sleep(5);

    // 获取未决信号集
    if (sigpending(&pendingset) == -1) {
        perror("sigpending");
        return 1;
    }

    // 检查SIGINT信号是否在未决信号集中
    if (sigismember(&pendingset, SIGINT) == 1) {
        printf("SIGINT信号是未决信号\n");
    } else if (sigismember(&pendingset, SIGINT) == 0) {
        printf("SIGINT信号不是未决信号\n");
    } else {
        perror("sigismember");
        return 1;
    }

    // 解除阻塞SIGINT信号
    if (sigprocmask(SIG_UNBLOCK, &myset, NULL) == -1) {
        perror("sigprocmask");
        return 1;
    }
    printf("已解除阻塞SIGINT信号\n");

    while (1) {
        // 循环等待信号
        pause();
    }

    return 0;
}

在上述代码中,首先阻塞 SIGINT 信号,然后睡眠5秒。在睡眠期间,如果发送 SIGINT 信号,该信号会成为未决信号。接着使用 sigpending 函数获取未决信号集,并使用 sigismember 函数检查 SIGINT 信号是否在未决信号集中。最后解除阻塞 SIGINT 信号。

信号集操作的注意事项

  1. 可移植性:虽然信号集操作函数在大多数UNIX - like系统上都有相同的接口,但在一些特定系统上可能会有细微差异。在编写跨平台代码时,需要特别注意。
  2. 信号掩码的保存与恢复:在更改信号掩码时,最好保存旧的信号掩码,并在适当的时候恢复。这样可以避免因意外更改信号掩码而导致的程序行为异常。
  3. 原子性操作:信号集操作函数通常是原子性的,这意味着在多线程环境下,多个线程对信号集的操作不会相互干扰。但在使用信号相关函数时,仍需要注意线程安全问题。
  4. 信号处理与信号集:在信号处理函数中,也可以操作信号集。但需要注意避免在信号处理函数中进行复杂的操作,以免影响程序的稳定性和可预测性。

通过深入理解和熟练运用信号集操作,开发者可以更好地控制进程对信号的响应,提高程序的稳定性和可靠性。在实际应用中,根据具体的需求合理地使用信号集操作函数,能够有效地处理各种信号相关的任务。例如,在服务器程序中,可以通过阻塞某些信号来确保关键操作不受信号干扰,而在适当的时候解除阻塞并处理信号,以实现程序的正常退出或其他特定功能。