linux信号集与信号屏蔽字

信号传递过程

  • 信号源产生信号,由内核向进程发送信号
  • 进程选择是否阻塞进程,若阻塞,则信号进入阻塞信号列表,只有当解除阻塞后,进程才接收该信号,若一直不接触,内核则将该信号从阻塞列表中移除并丢弃;若不阻塞,则进程接收信号
  • 进程接收信号后,进程可屏蔽该信号,或者执行用户编写的处理函数,或者执行默认动作

    以上便是linux进程对信号的处理过程,如果你足够细心,你会提出疑问:我如何才能让进程对一个信号进行处理呢?这也就是我们这篇博客要解决的问题

一些名词

  • 信号集
    信号集顾名思义,就是信号的集合。在linux中,它的类型是sigset_t,大小是64bits
    此时你应该又有疑问了:为何是64bits?原因很简单,因为目前linux流行版本一共有64个信号(不同版本信号格式不同),我们一个bit来表示一种信号,一共只需要64bits就行啦!!!
    那么信号集的作用到底是什么呢?信号集一共有64bits,自然,第一位就是1号信号(SIGHUP),第二位就是2号信号(SIGINT)….以此类推(我们假设位号是从1开始,而不是从0开始)。那么,假如1号位如果是1,则表示1号信号已经注册到该信号集中;如果是0,就是为注册到该信号集中咯!为什么要把信号注册到信号集中呢?当然是为了批量管理信号!!!具体的用法,看到下面你就知道啦

  • 抵达(delivery) && 未决(pending)
    抵达:执行信号的处理动作叫抵达。也就是说信号被进程接收啦!抵达包括:忽略执行默认动作执行处理函数
    未决:信号从产生到抵达直接的状态。说白了未决就是未抵达。自然,一个信号被阻塞啦,那么该信号就是处于未决状态。

  • 信号屏蔽状态字(block) && 信号未决状态字(pending)
    在进程控制块(PCB)中的结构体中,有三个比较重要的变量,分别是:信号屏蔽状态字信号未决状态字是否忽略标志
    信号屏蔽状态字(block):64bits,每一位代表该进程对对应号码的信号是否屏蔽:1是屏蔽,0是不屏蔽
    信号未决状态字(pending):64bits,每一位代表该进程对对应号码的信号的状态:1是未决,0是不抵达
    是否忽略标志:这里我们不讨论

需要注意的是:这些变量之间也存在一些关系,比如:进程将信号屏蔽字的2号为置为1,也就是说屏蔽SIGINT信号,那么但你向进程发送该信号时(ctrl+c),该信号必然处于未决状态。那么,信号未决状态字的2号位自然也就是1啦。

信号集操作函数

POSIX.1 定义了一个数据类型sigset_t,用于表示信号集。另外,头文件 signal.h 提供了下列五个处理信号集的函数

  • sigemptyset 初始化set所指向的信号集,清除里面所有已经注册的信号,即将所有位置0
    int sigemptyset(sigset_t *set);
    返回值:若成功则返回0,若出错则返回-1

  • sigfillset 初始化由 set 指向的信号集,使其包含所有信号。即将所有位置1
    int sigfillset(sigset_t *set);
    返回值:若成功则返回0,若出错则返回-1

  • sigaddset 将一个信号 signo 添加到现有信号集 set 中。即将该信号对应的位置1
    int sigaddset(sigset_t *set, int signo);
    返回值:若成功则返回0,若出错则返回-1

  • sigdelset 将一个信号 signo 从信号集 set 中删除。即将该信号对应的位置0
    int sigdelset(sigset_t *set, int signo);
    返回值:若成功则返回0,若出错则返回-1

  • sigismember 判断指定信号 signo 是否在信号集 set 中。
    int sigismember(const sigset_t *set, int signo);
    返回值:若真则返回1,若假则返回0,若出错则返回-1

这些函数的具体运用会在下面的代码中

sigprocmask && 设置进程的信号屏蔽字

  • sigprocmask 函数可以检测或者设置进程的信号屏蔽字。
    #include <signal.h>
    int sigprocmask(int how, const sigset_t * set, sigset_t * oset);
    返回值:若成功则返回0,若出错则返回-1

  • 参数说明
    set:如果set是非空指针,则更改进程的信号屏蔽字
    oset:如果oset是非空指针,则读取进程的当前信号屏蔽字通过oset参数传出
    how:参数how指示如何更改
    注意如果set为空,那how参数显然也没有意义啦,信号集都没有,我哪知道要改哪个信号的屏蔽状态字!!!

  • how 参数可选值

how 说明
SIG_BLOCK 该进程新的信号屏蔽字是其当前信号屏蔽字和 set 指向信号集的并集。set 包含了我们希望阻塞的信号。
SIG_UNBLOCK 该进程的信号屏蔽字是当前信号屏蔽字和 set 所指向信号集补给的交集。set 包含了我们希望解除阻塞的信号。
SIG_SETMASK 设置当前信号屏蔽字设置为 set 所指向的信号集。

上面的东西是不是不是很懂,那么我们通俗点讲

how 说明
SIG_BLOCK 把set信号集里面的信号全部设置为阻塞
SIG_UNBLOCK 把set信号集里面的信号全部解除阻塞
SIG_SETMASK 把set信号集里面的信号全部设置为阻塞或者解除阻塞(当前信号屏蔽字可能是阻塞,,也可能是接触阻塞)
  • 下面我们来看一个具体的例子
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>

void handler()
{
    printf("SIG_INT respond\n");
    return;
}

int main()
{
    char tmp = 'a';
    sigset_t bset; //用来设置阻塞的信号集

    sigemptyset(&bset); //清空信号集
    sigaddset(&bset, SIG_INT); //将SIG_INT信号添加到信号集中

    if(signal(SIG_INT, handler) == SiG_ERR)//注册安装处理函数
        perror("signal err:");

    sigprocmask(SIG_BLOCK, &bset, NULL); //阻塞SIG_INT信号

    while(tmp != 'q' )
    {
        tmp = getchar();
    }
    sigprocmask(SIG_UNBLOCK, &bset, NULL);//解锁阻塞

    pause();
    return 0;
}


编译运行此函数,按一下步骤输入:
程序运行到getchar(), 输入ctrl+c,此时并未看见执行信号处理函数
输入'q',退出循环,再按ctrl+c,看见控制端打印SIG_INT respond

sigpending 获取进程未决的信号集
  • sigpending 获取当前进程所有未决的信号。通过其 set 参数返回未决的信号集。
    #include <signal.h>
    int sigpending(sigset_t *set);
    返回值:若成功则返回0,若出错则返回-1

通俗地将,就是:一旦调用sigpending函数,那么set信号集中,处于未决状态的信号对应的位,被置为1

  • 看个例子
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>

void printsigset(sigset_t *set) 
{
    int i;
    for (i=1; i<NSIG; ++i)
    {
        if (sigismember(set, i))//置1的位,说明对应信号在信号集 set中。返回真,则打印1。如01000000000000....说明2号信号是处于未决状态
            putchar('1');
        else
            putchar('0');
    }
    printf("\n");
}


int main()
{
    char tmp = 'a';
    sigset_t bset; //用来设置阻塞的信号集
    sigset_t pset; //用来打印未决信号集

    sigemptyset(&bset); //清空信号集
    sigaddset(&bset, SIG_INT); //将SIG_INT信号添加到信号集中

    sigprocmask(SIG_BLOCK, &bset, NULL); //阻塞SIG_INT信号

    for(;;;)
    {
        //获取未决 字信息
        sigpending(&pset);//若一信号是未决状态,则将set对应位置1

        //打印信号未决 sigset_t字
        printsigset(&pset);
        sleep(1);
    }
    pause();
    return 0;
}


程序开始执行:打印000000000000......
输入ctrl+c,因为SIG_INT被阻塞,所以该信号处于未决状态
故输出01000000000000000......

需要注意的是:如果在信号处理函数中对某个信号进行解除阻塞时,则只是将pending位清0,让此信号递达一次(同个实时信号产生多次进行排队都会抵达),但不会将block位清0,即再次产生此信号时还是会被阻塞,处于未决状态。

发布了23 篇原创文章 · 获赞 24 · 访问量 2万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 编程工作室 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览