信号在内核中的表示

执行信号的处理动作称为信号递达(Delivery),信号从产生到递达之间的状态,称为信号未决(Pending)。进程可以选择阻塞(Block)某个信号。被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。

注意,阻塞和忽略是不同,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。信号在内核中的表示可以看作是这样的:

图-信号的发送过程

解释说明:

1)PCB进程控制块(task_struct)中函数有信号屏蔽状态字(block)和信号未决状态字(pending)还有是否忽略标志;

2) 信号屏蔽状态字(block), 1代表阻塞、0代表不阻塞;

信号未决状态字(pending)的1代表未决,0代表信号可以抵达了;

3)向进程发送SIGINT,内核首先判断信号屏蔽状态字是否阻塞,若阻塞,信号未决状态字(pending)相应位制成1;若阻塞解除,信号未决状态字(pending)相应位制成0;表示信号可以抵达了。

4)block状态字、pending状态字均64位(bit);

5)block状态字用户可以读写,pending状态字用户只能读;这是信号设计机制。

思考1:状态字都64bit,编程时,如何表示状态字那?

思考2:block状态字信息如何获取或者操作那?哪些api?

思考3:pending状态字信息如何获取或者操作那?哪些api?

答案见下;

信号集操作函数(状态字表示)

#include <signal.h>
int sigemptyset(sigset_t *set);	//把信号集清零;(64bit/8=8字节)
int sigfillset(sigset_t *set); 	//把信号集64bit全部置为1
int sigaddset(sigset_t *set, int signo); 	//根据signo,把信号集中的对应位置成1
int sigdelset(sigset_t *set, int signo); 	//根据signo,把信号集中的对应位置成0
int sigismember(const sigset_t *set, int signo);	//判断signo是否在信号集中

sigprocmask:读取/更改信号屏蔽状态字(Block)

int sigprocmask(int how, const sigset_t *set, sigset_t *oset);

返回值:若成功则为0,若出错则为-1

读取:如果oset是非空指针,则读取进程的当前信号屏蔽字通过oset参数传出。

更改:如果set是非空指针,则更改进程的信号屏蔽字,参数how指示如何更改。如果oset和set都是非空指针,则先将原来的信号屏蔽字备份到oset里,然后根据set和how参数更改信号屏蔽字。假设当前的信号屏蔽字为mask,下表说明了how参数的可选值。

How:

sigpending获取信号未决状态字(pending)信息 

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

DESCRIPTION

sigpending()  returns the set of signals that are pending for delivery to the calling thread

(i.e., the signals which have been raised while blocked).

The mask of pending signals is returned in set.

/**	示例1:添加信号SIGINT到信号屏蔽字
此时再按下Ctrl+C键, 进程也接收不到SIGINT信号了
**/
inline void err_exit(std::string message);
void sigHandler(int signo);
void printSigSet(const sigset_t &sigset);

int main()
{
//虽然此处安装了信号处理函数, 但是该进程还是接收不到SIGINT信号
    if (signal(SIGINT, sigHandler) == SIG_ERR)
        err_exit("signal error");

    //添加信号屏蔽字: 屏蔽SIGINT信号
    sigset_t addset;
    sigemptyset(&addset);
    sigaddset(&addset, SIGINT);
    if (sigprocmask(SIG_BLOCK, &addset, NULL) == -1)
        err_exit("sigprocmask error");

    // 不断打印当前的信号屏蔽字
    sigset_t sigset;
    while (true)
    {
        sigpending(&sigset);
        printSigSet(sigset);
        sleep(1);
    }
}

inline void err_exit(std::string message)
{
    perror(message.c_str());
    exit(EXIT_FAILURE);
}
void sigHandler(int signo)
{
    cout << "catch a signal, number = " << signo << endl;
}
void printSigSet(const sigset_t &sigset)
{
    for (unsigned i = 1; i < NSIG; ++i)
    {
        if (sigismember(&sigset, i))
            putchar('1');
        else
            putchar('0');
    }
    putchar('\n');
}
/** 示例2:在示例1的基础上继续屏蔽SIGINT信号, 但是如果该进程接收到了SIGQUIT信号, 则将对SIGINT信号的屏蔽解除, 需要
1.在main函数中再安装一个SIGQUIT捕捉函数
2.对sigHandler函数进行改造
**/
int main()
{
    if (signal(SIGINT, sigHandler) == SIG_ERR)
        err_exit("signal SIGINT error");
    if (signal(SIGQUIT, sigHandler) == SIG_ERR)
        err_exit("signal SIGQUIT error");

    //添加信号屏蔽字: 屏蔽SIGINT信号
    sigset_t addset;
    sigemptyset(&addset);
    sigaddset(&addset, SIGINT);
    if (sigprocmask(SIG_BLOCK, &addset, NULL) == -1)
        err_exit("sigprocmask error");

    // 不断打印当前的信号屏蔽字
    sigset_t sigset;
    while (true)
    {
        sigpending(&sigset);
        printSigSet(sigset);
        sleep(1);
    }
}

void sigHandler(int signo)
{
    switch (signo)
    {
    case SIGINT:
        cout << "catch a signal SIGINT" << endl;
        break;
    case SIGQUIT:
        //如果是SIGQUIT信号, 则将SIGINT信号的屏蔽进行解除
        sigset_t unblockSet;
        sigemptyset(&unblockSet);
        sigaddset(&unblockSet, SIGINT);
        if (sigprocmask(SIG_UNBLOCK, &unblockSet, NULL) == -1)
            err_exit("sigprocmask unblock error");
        else
            cout << "sigprocmask success" << endl;
        break;
    default:
        cerr << "unknown signal" << endl;
        break;
    }
}
/**
    连续的按Ctrl+c键盘,虽然发送了多个SIGINT信号,
    但是因为信号是不稳定的(不支持排队),所以只保留了一个,如下图
*/

//示例: 换成实时信号SIGRTMAX
int main()
{
    if (signal(SIGRTMAX, sigHandler) == SIG_ERR)
        err_exit("signal SIGRTMAX error");
    if (signal(SIGQUIT, sigHandler) == SIG_ERR)
        err_exit("signal SIGQUIT error");

    //添加信号屏蔽字: 屏蔽SIGRTMAX信号
    sigset_t addset;
    sigemptyset(&addset);
    sigaddset(&addset, SIGRTMAX);
    if (sigprocmask(SIG_BLOCK, &addset, NULL) == -1)
        err_exit("sigprocmask error");

    // 不断打印当前的信号屏蔽字
    sigset_t sigset;
    while (true)
    {
        sigpending(&sigset);
        printSigSet(sigset);
        sleep(1);
    }
}
void sigHandler(int signo)
{
    if (signo == SIGRTMAX)
        cout << "catch a signal SIGRTMAX" << endl;
    else if (signo == SIGQUIT)
    {
        sigset_t unblockSet;
        sigemptyset(&unblockSet);
        sigaddset(&unblockSet, SIGRTMAX);
        if (sigprocmask(SIG_UNBLOCK, &unblockSet, NULL) == -1)
            err_exit("sigprocmask unblock error");
        else
            cout << "sigprocmask success" << endl;
    }
    else
        cerr << "unknown signal" << endl;
}

/** 向该进程连续发送四个SIGRTMAX, 则进程都能够收到**/

综合案例

1) 创建子进程与父进程;

2) 注册SIGINT非实时信号与SIGRTMIN实时信号,并将这两种信号添加到进程屏蔽信号组中;

3) 注册用户自定义信号;

4) 子进程发送5次非实时信号,发5次实时信号;

5) 然后子进程发送SIGUSR1解除进程对SIGINT,SIGTRMIN信号的阻塞

6) 观察实时信号与非实时信号的区别

//程序示例
void onSigAction(int signalNumber, siginfo_t *sigInfoStruct, void *)
{
    //获取接收到的数据
    int receiveNumber = sigInfoStruct->si_int;

    //如果收到的是SIGUSR1信号,则解除对SIGINT,SIGRTMIN的屏蔽
    if (signalNumber == SIGUSR1)
    {
        sigset_t unblockSet;
        sigemptyset(&unblockSet);
        sigaddset(&unblockSet,SIGINT);
        sigaddset(&unblockSet,SIGRTMIN);;
        sigprocmask(SIG_UNBLOCK,&unblockSet,NULL);

        //Value值会是乱码!
        //cout << "Receive SIGUSR1, Value = " << receiveNumber << endl;
        cout << "Unblock, Receive SIGUSR1" << endl;
    }
    else if (signalNumber == SIGINT)
    {
        cout << "Receive SIGINT, Value = " << receiveNumber << endl;
    }
    else if (signalNumber == SIGRTMIN)
    {
        cout << "Receive SIGRTMIN, Value = " << receiveNumber << endl;
    }
}

int main()
{
    struct sigaction act;
    //如果需要使得信号处理程序接收额外数据,
    //则必须将sa_flags位置为SA_SIGINFO
    act.sa_flags = SA_SIGINFO;
    sigemptyset(&act.sa_mask);
    act.sa_sigaction = onSigAction;

    //注册信号处理函数
    if (sigaction(SIGINT,&act,NULL) < 0)
        err_exit("sigaction SIGINT");
    if (sigaction(SIGRTMIN,&act,NULL) < 0)
        err_exit("sigaction SIGRTMIN");
    if (sigaction(SIGUSR1,&act,NULL) < 0)
        err_exit("sigaction SIGUSR1");

    //将信号SIGINT,SIGRTMIN信号阻塞
    sigset_t blockSet;
    sigemptyset(&blockSet);
    sigaddset(&blockSet,SIGINT);
    sigaddset(&blockSet,SIGRTMIN);
    if (sigprocmask(SIG_BLOCK,&blockSet,NULL) < 0)
        err_exit("sigprocmask error");

    pid_t pid = fork();
    if (pid == -1)
        err_exit("fork error");
    else if (pid == 0)
    {
        union sigval Value;
        Value.sival_int = 200;

        //给父进程发送五次带额外数据的非可靠信号(其实最终只接受到了一次)
        for (int i = 0; i < 5; ++i)
        {
            ++ Value.sival_int;
            if (sigqueue(getppid(),SIGINT,Value) != 0)
                err_exit("sigqueue error");
        }

        //给父进程发送五次带额外数据的可靠信号(最终接收到了五次!!!)
        Value.sival_int = 0;
        for (int i = 0; i < 5; ++i)
        {
            ++ Value.sival_int;
            if (sigqueue(getppid(),SIGRTMIN,Value) != 0)
                err_exit("sigqueue error");
        }

        //给父进程发送SIGUSR1信号,解除对SIGINT,SIGRTMIN信号的阻塞
        kill(getppid(),SIGUSR1);
    }

    while (true)
    {
        pause();
    }
}

运行结果:

其实只是收到了一份非可靠信号SIGINT!

Linux信号实践(3) --信号内核表示的更多相关文章

  1. Linux信号实践(2) --信号分类

    信号分类 不可靠信号 Linux信号机制基本上是从UNIX系统中继承过来的.早期UNIX系统中的信号机制比较简单和原始,后来在实践中暴露出一些问题,它的主要问题是: 1.进程每次处理信号后,就将对信号 ...

  2. Linux驱动实践:中断处理函数如何【发送信号】给应用层?

    作 者:道哥,10+年嵌入式开发老兵,专注于:C/C++.嵌入式.Linux. 关注下方公众号,回复[书籍],获取 Linux.嵌入式领域经典书籍:回复[PDF],获取所有原创文章( PDF 格式). ...

  3. Linux信号实践(1) --Linux信号编程概述

    中断 中断是系统对于异步事件的响应, 进程执行代码的过程中可以随时被打断,然后去执行异常处理程序; 计算机系统的中断场景:中断源发出中断信号 -> CPU判断中断是否屏蔽屏蔽以及保护现场 -&g ...

  4. Linux信号实践(4) --可靠信号

    Sigaction #include <signal.h> int sigaction(int signum, const struct sigaction *act, struct si ...

  5. linux第2天 信号 wait

    孤儿进程和僵尸进程 如果父进程先退出,子进程还没退出那么子进程的父进程将变为init进程.(注:任何一个进程都必须有父进程) 如果子进程先退出,父进程还没退出,那么子进程必须等到父进程捕获到了子进程的 ...

  6. Linux环境进程间通信(二): 信号(上)

    linux下进程间通信的几种主要手段: 管道(Pipe)及有名管道(named pipe):管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允 ...

  7. linux系统编程之信号(一):中断与信号

    一,什么是中断? 1.中断的基本概念 中断是指计算机在执行期间,系统内发生任何非寻常的或非预期的急需处理事件,使得CPU暂时中断当前正在执行的程序而转去执行相应的事件处理程序,待处理完毕后又返回原来被 ...

  8. linux系统编程之信号(二)

    经过了漫长的间歇,对于c语言的学习也被中断了很久,现实确实有很多的无耐,计划中的事情总会被打乱,但不管怎样,学习的道路是不能休止的,所以经过了一断温习后现在继续学习C语言,话不多说,进入正题: 信号分 ...

  9. Linux 信号详解六(可靠信号与不可靠信号)

    #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h&g ...

随机推荐

  1. Git/GitHub SSH配置

    生成 SSH 公钥 如前所述,许多 Git 服务器都使用 SSH 公钥进行认证. 为了向 Git 服务器提供 SSH 公钥,如果某系统用户尚未拥有密钥,必须事先为其生成一份. 这个过程在所有操作系统上 ...

  2. Linux 管理软件

    公司的openfire先前运行在windows上的,但由于在windows上openfire内存机制问题,最多只能占用2GB内存,且时间稍微长久一些就会自动挂掉,用户无法登陆和连接,因此迁移到了Cen ...

  3. windows资源管理器中配置右键bash here

    windows下安装了git后有git bash here 但是安装了cygwin没有bash here 我们可以通过修改注册表的方式自己做一个 Win10下在注册表内有一般有两个默认的 cmd 和 ...

  4. python通过token登录,并爬取数据实例

    from bs4 import BeautifulSoup import requests class Zabbix(object): def __init__(self, headers): sel ...

  5. spring-boot配置静态资源映射的坑:properties文件不能添加注释

    如此博文所述,Spring Boot 对静态资源映射提供了默认配置 默认将 /** 所有访问映射到以下目录:classpath:/staticclasspath:/publicclasspath:/r ...

  6. oo第二阶段总结

    第五次作业--多线程电梯 一.设计策略 本次作业是我们第一次接触多线程,给程序添加多线程功能后最大的挑战是实现共享数据的安全.避免冲突,由于这次作业是第一次尝试多线程方法,因此采用了将所有方法都加上s ...

  7. MySQL系列教程(五)

    MyCAT MyCat是基于阿里开源的Cobar产品而研发,Cobar的稳定性.可靠性.优秀的架构和性能以及众多成熟的使用案例使得MYCAT一开始就拥有一个很好的起点,站在巨人的肩膀上,我们能看到更远 ...

  8. Git之(六)标签管理

    发布一个版本时,我们通常先在版本库中打一个标签(tag),这样,就唯一确定了打标签时刻的版本.将来无论什么时候,取某个标签的版本,就是把那个打标签的时刻的历史版本取出来.所以,标签也是版本库的一个快照 ...

  9. 【SSH系列】spring中为什么要使用IOC

    开篇前言 在前面的博文中,小编主要简单的介绍了spring的入门知识,随着学习的深入,我们知道spring最核心的两大技术,IOC和AOP,这两个技术也是spring最耀眼的地方,在后续的博文中小编将 ...

  10. 深入浅出如何解析xml文件---下篇

    在上篇博文中,小编主要介绍xml的两种解析方式,分别是dom4j和dom,今天这篇博文,小编主要来简单介绍一下xml的其她两种解析方式sax和jdom.  sax解析xml文件 sax,全称是Simp ...