Linux多进程17-信号集及相关函数

信号集

  • 许多信号相关的系统调用都需要能表示一组不同的信号,多个信号可使用一个称之为信号集的数据结构来表示,其系统数据类型为 sigset_t(64位的整数)。
  • 在 PCB(进程控制块) 中有两个非常重要的信号集。一个称之为 “阻塞信号集” (阻塞信号递达),另一个称之为“未决信号集” (没有递达的信号)。这两个信号集都是内核使用位图机制(使用二进制位 )来实现的。但操作系统不允许我们直接对这两个信号集进行位操作。而需自定义另外一个集合,借助信号集操作函数来对 PCB 中的这两个信号集进行修改。
  • 信号的 “未决” 是一种状态,指的是从信号的产生到信号被处理前的这一段时间。
  • 信号的 “阻塞” 是一个开关动作,指的是阻止信号被处理,但不是阻止信号产生。
  • 信号的阻塞就是让系统暂时保留信号留待以后发送。由于另外有办法让系统忽略信号,所以一般情况下信号的阻塞只是暂时的,只是为了防止信号打断敏感的操作。

阻塞信号集和未决信号集

image

一个模拟的过程:

  1. 用户通过键盘Ctrl+c, 产生2号信号SIGINT, 信号被创建
  2. 信号产生, 但没有被处理, 未决状态
    • 在内核中将所有没被处理的信号存储在一个集合中, 未决信号集
    • SIGINT 信号状态被存储在第二个标志位上, 这个标志位为0说明信号不是未决状态, 标志位为1, 说明信号处于未决状态
  3. 这个未决状态的信号需要被处理, 处理之前需要和另一个信号集, 进行比较
    • 阻塞信号集默认不阻塞任何信号
    • 如果想要阻塞某些信号, 需要用户调用系统API
  4. 在处理的时候, 和阻塞信号集中的标志位进行查询, 看是不是对该信号设置阻塞了
    • 如果没有阻塞, 这个信号就被处理
    • 如果阻塞了, 这个信号就继续处于未决状态, 知道阻塞解除, 这个信号就被处理

相关函数

int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signum);
int sigdelset(sigset_t *set, int signum);
int sigismember(const sigset_t *set, int signum);
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
int sigpending(sigset_t *set);
以下信号集相关的函数都是对自定义的信号集进行操作

int sigemptyset(sigset_t *set);
    - 功能: 清空信号集中的数据, 将信号集中的所有的标志位置为0
    - 参数: set, 传出参数, 需要操作的信号集
    - 返回: 0成功, -1失败

int sigfillset(sigset_t *set);
    - 功能: 将信号集中的所有的标志位置为1
    - 参数: set, 传出参数, 需要操作的信号集
    - 返回: 0成功, -1失败
int sigaddset(sigset_t *set, int signum);
    - 功能: 设置信号集中的某一个信号对应的标志位为1, 表示阻塞这个信号
    - 参数:
        - set, 传出参数, 需要操作的信号集
        - signum, 需要设置阻塞的那个信号
    - 返回: 0成功, -1失败

int sigdelset(sigset_t *set, int signum);
    - 功能: 设置信号集中的某一个信号对应的标志位为0, 表示不阻塞这个信号
    - 参数:
        - set, 传出参数, 需要操作的信号集
        - signum, 需要设置不阻塞的那个信号
    - 返回: 0成功, -1失败

int sigismember(const sigset_t *set, int signum);
    - 功能: 判断某个信号是否阻塞
    - 参数:
        - set: 需要操作的信号集
        - signum, 需要判断的那个信号
    - 返回:
        1: signum被阻塞
        0: signum不阻塞
        -1: 调用失败

代码实例

setitimer.c

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

int main(int argc, char const *argv[])
{
    //创建一个信号集
    sigset_t set;
    //清空信号集内容
    sigemptyset(&set);
    //判断SIGINT是否在信号集set里
    int ret = sigismember(&set, SIGINT);
    if (ret == 0)
    {
        printf("SIGINT 不阻塞\n");
    }
    else if (ret == 1)
    {
        printf("SIGINT 被阻塞\n");
    }
    else if (ret == -1)
    {
        perror("sigismember err\n");
        exit(0);
    }

    //添加几个信号到信号集中
    sigaddset(&set, SIGINT);
    sigaddset(&set, SIGQUIT);

    //判断SIGINT是否在信号集set里
    ret = sigismember(&set, SIGINT);
    if (ret == 0)
    {
        printf("SIGINT 不阻塞\n");
    }
    else if (ret == 1)
    {
        printf("SIGINT 被阻塞\n");
    }
    else if (ret == -1)
    {
        perror("sigismember err\n");
        exit(0);
    }

    //判断SIGQUIT是否在信号集set里
    ret = sigismember(&set, SIGQUIT);
    if (ret == 0)
    {
        printf("SIGQUIT 不阻塞\n");
    }
    else if (ret == 1)
    {
        printf("SIGQUIT 被阻塞\n");
    }
    else if (ret == -1)
    {
        perror("sigismember err\n");
        exit(0);
    }

    //从信号集中删除一个信号
    sigdelset(&set, SIGQUIT);

    //判断SIGQUIT是否在信号集set里
    ret = sigismember(&set, SIGQUIT);
    if (ret == 0)
    {
        printf("SIGQUIT 不阻塞\n");
    }
    else if (ret == 1)
    {
        printf("SIGQUIT 被阻塞\n");
    }
    else if (ret == -1)
    {
        perror("sigismember err\n");
        exit(0);
    }

    return 0;
}

运行程序

$./sigset
SIGINT 不阻塞
SIGINT 被阻塞
SIGQUIT 被阻塞
SIGQUIT 不阻塞

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
    - 功能: 将自定义信号集中的数据设置到内核中(设置阻塞, 解除阻塞, 替换)
    - 参数:
        - how: 如何对内核阻塞信号集进行处理
            SIG_BLOCK: 将用户设置的阻塞信号集添加到内核中, 内核中原来的数据不变
                假设内核中默认的阻塞信号集是mask, mask | set
            SIG_UNBLOCK: 根据用户设置的数据, 对内核中的数据进行解除阻塞
                mask &- ~set
            SIG_SETMASK: 覆盖内核中原来的值

        - set: 已经初始化好的用户自定义的信号集
        - oldset: 保存设置之前的内核中的阻塞信号集的状态, 可以是NULL
    - 返回值:
        成功: 0
        失败: -1 错误号:EFAULT / EINVAL


int sigpending(sigset_t *set);
    - 作用: 获取内核中的未决信号集
    - 参数: set: 传出参数, 保存内核中的未决信号集中的信息
    - 返回: 0成功 -1失败

代码实例

编写程序, 把所有常规信号1-31(kill -l)的未决状态打印到屏幕

设置某些信号是阻塞的, 通过键盘产生这些信号

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

int main()
{

    // 设置2、3号信号阻塞
    sigset_t set;
    sigemptyset(&set);
    // 将2号和3号信号添加到信号集中
    sigaddset(&set, SIGINT);
    sigaddset(&set, SIGQUIT);

    // 修改内核中的阻塞信号集
    sigprocmask(SIG_BLOCK, &set, NULL);

    int num = 0;

    while (1)
    {
        num++;
        // 获取当前的未决信号集的数据
        sigset_t pendingset;
        sigemptyset(&pendingset);
        sigpending(&pendingset);

        // 遍历前32位
        for (int i = 1; i <= 31; i++)
        {
            if (sigismember(&pendingset, i) == 1)
            {
                printf("1");
            }
            else if (sigismember(&pendingset, i) == 0)
            {
                printf("0");
            }
            else
            {
                perror("sigismember");
                exit(0);
            }
        }

        printf("\n");
        sleep(1);
        if (num == 10)
        {
            // 解除阻塞
            sigprocmask(SIG_UNBLOCK, &set, NULL);
        }
    }

    return 0;
}

运行程序

$./sigpromask 
0000000000000000000000000000000
0000000000000000000000000000000
0000000000000000000000000000000
^\0010000000000000000000000000000
0010000000000000000000000000000
^C0110000000000000000000000000000
0110000000000000000000000000000
0110000000000000000000000000000
0110000000000000000000000000000
0110000000000000000000000000000
posted @ 2023-05-17 18:42  言叶以上  阅读(27)  评论(0编辑  收藏  举报