在这里插入图片描述

个人主页~


一、信号发送

1、信号动作

通过指令man -7 signal查看信号的手册,然后往下翻翻可以看到普通信号发出后对应的操作,以及它们的信号编号,和详细描述信息

在这里插入图片描述

2、信号发送的本质

普通信号

信号发送的本质实际上是写信号,把信号写到进程PCB结构体对应的位图上去,在进程的PCB中有这么一个位图(是pending位图,下面会说)正好对应着我们从1 ~ 31的普通信号编号,收到哪个信号就将哪一位对应的比特位置为1,表示收到信号,然后PCB再做对应的工作

值得注意的是,如果连续发普通信号,那么进程只会处理最后一次的信号,每次写都是覆盖写的

实时信号

我们前面说过信号分为31个普通信号和31个实时信号,实时信号的作用类似于我们嵌入式RTOS实时运转场景,要保持实时性,实时信号发送的本质类似于普通信号,不过此时我们保存信号的载体不再是一个位图,而是一个结构体,它们被组织在信号队列当中,谁先发送谁就先入队,队列遵循先入先出的规则,所以先发送也代表着先被处理

值得注意的是,如果连续发实时信号,那么进程会将队列中的信号一个个全部处理

3、core dump

我们在《进程控制》一文中解释了wait函数的status参数,其中第7位就是core dump标志,这里简单解释一下
当程序在运行过程中发生崩溃(如段错误、除零错误等),Core dump 会记录下程序崩溃瞬间的内存状态,包括寄存器的值、调用栈信息、全局变量和局部变量的值等,开发人员可以使用调试工具(如 GDB)加载 Core dump 文件,通过分析这些信息,准确地找到程序崩溃的位置和原因

我们可以通过ulimit -c 10240core文件的大小限制修改为10240字节,出现错误的时候core文件可能瞬间会被打满的,所以我们云服务器上一般默认core文件的大小限制为0,我们要是用的话再修改它的大小限制即可
形成的文件叫做core.pidpid就是出错进程的pid,假设test进程出现错误,12314是它的pid,我们可以通过在gdb模式下输入gdb test core.12314打印错误信息和原因

二、信号的保存

1、前置概念

实际执行信号的处理动作称为信号递达

信号从产生到递达之间的状态,称为信号未决

被阻塞的信号产生是将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作

在这里插入图片描述

2、阻塞信号

信号被阻塞就是将信号阻塞在信号未决状态,具体实现阻塞功能的,是一个位图block

block是一个位图,共31位,对应1 ~ 31号信号,当对应比特位为1时,表示该编号信号被阻塞,为0则表示不阻塞

如果信号被阻塞则进入阻塞态,若没有被阻塞那么信号进入未决状态

3、保存信号

未决状态的作用就是保存信号,它保存信号的方式也是通过位图pending

pending也是一个位图,与block一致,对应的下标和信号编号也是一一对应,当对应比特位为1时,表示该编号信号处于未决状态,为0则信号递达

实际上blockpending都属于保存信号,只不过因为有两个位图,我们分开来说罢了

4、信号递达

信号递达后的信号,会执行相对应的行为,有SIG_DFL:默认处理动作,SIG_IGN:忽略,和自定义处理sighandler,这在前面提到过

在这里插入图片描述

5、总结

在这里插入图片描述

一个信号,首先要经过blockblock为0来到pendingpending为0来到handler执行动作,其中,9号和19号新号还是特例,它们是不能被阻塞和保存的,这两个信号一旦发出就是直接handler,其实也不用handler了,它们对应的不可能为忽略和信号捕捉后的自定义函数,只能为默认动作终止和暂停

三、信号集操作函数

信号集操作函数顾名思义就是操作信号集的函数,sigset_t被称作信号集,是操作系统提供的数据类型,用于描述位图,下面就是信号集操作函数

#include <signal.h>

int sigemptyset(sigset_t *set); 
// 将位图全部设置为 0

int sigfillset(sigset_t *set); 
// 将位图全部都设置为 1

int sigaddset (sigset_t *set, int signo); 
// 将位图中的某一位设置为 1

int sigdelset(sigset_t *set, int signo); 
// 将位图中的某一位设置为 0

int sigismember(const sigset_t *set, int signo); 
// 判断一个信号是否在信号集中,不在返回0,在返回1,出错返回-1

1、设置block位图

sigprocmask是一个在信号处理中非常重要的系统调用,主要用于检查、修改进程的信号掩码(阻塞信号集)

#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset); 

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

how:指定对信号掩码的操作方式

how 取值含义示例说明
SIG_BLOCKset 所指向的信号集中的信号添加到当前的信号掩码中,即阻塞 set 中的信号若当前信号掩码已阻塞 SIGINT,使用 SIG_BLOCK 并传入包含 SIGTERM 的信号集,SIGTERM 也会被阻塞
SIG_UNBLOCK从当前的信号掩码中移除 set 所指向的信号集中的信号,即解除对 set 中信号的阻塞若当前信号掩码阻塞了 SIGINTSIGTERM,使用 SIG_UNBLOCK 并传入包含 SIGINT 的信号集,SIGINT 信号的阻塞状态将被解除
SIG_SETMASK将当前的信号掩码设置为 set 所指向的信号集,覆盖原来的信号掩码若原信号掩码阻塞 SIGINT,使用 SIG_SETMASK 并传入包含 SIGTERM 的信号集,信号掩码将只阻塞 SIGTERM

set:指向一个sigset_t类型的信号集,该信号集包含了要操作的信号,如果how的值为SIG_BLOCKSIG_UNBLOCK,则set表示要添加或移除的信号集;如果how的值为SIG_SETMASK,则set表示要设置的新的信号掩码,若该参数为NULL,则不改变当前的信号掩码,仅获取当前信号掩码,此时oset不能为NULL

oset:指向一个sigset_t类型的信号集,用于存储调用sigprocmask之前的信号掩码,如果不需要保存旧的信号掩码,可以将该参数设置为NULL

2、设置pending位图

sigpending是一个用于获取进程当前未决信号集的系统调用

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

返回值:成功返回0,失败返回-1

setsigpending函数会将当前进程中处于未决状态(即已发送但由于被阻塞而尚未被处理)的信号集存储到set所指向的sigset_t对象中

3、设置handler行为

三种情况,默认,忽略和自定义,自定义那当然是signal函数,前面有,不再赘述

四、验证信号保存行为

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

using namespace std;
//打印出位图
void PrintPending(const sigset_t &pset)
{
    for(int i = 31; i >= 1; i--)
    {
        cout << sigismember(&pset, i);
    }
    cout << endl;
}

void handler(int signum)
{
    cout << "catch a signum: " << signum << endl;
}

int main()
{
	//自定义捕捉2号信号
    signal(2, handler);

    sigset_t bset, oset; 
    sigemptyset(&bset); // bset信号集清空
    sigemptyset(&oset); // oset信号集清空
    sigaddset(&bset, 2); // 将bset的第2位设为1,也就是给bset中添加上2号信号

    // 调用系统调用,将数据设置进内核,设置block,此时2号信号被阻塞
    sigprocmask(SIG_SETMASK, &bset, &oset);

    // 重复打印当前进程的 pending 信号集,期间向进程发送 2号信号
    // 因为 2号信号被阻塞了,所以 2号信号会一直被保存在 pending 中
    sigset_t pset; 
    sigemptyset(&pset);// pset信号集清空
    int cnt = 0;
    //在15秒内,未接受信号前,都是一直打印0
    while(true)
    {
         int n = sigpending(&pset);
         if(n < 0) continue;
         //打印位图
         PrintPending(pset);
         sleep(1);
         cnt++;
         if(cnt == 15)
         {
            cout << "unblock 2 signo" << endl;
            // 打印15次位图后解除阻塞
            sigdelset(&bset, 2);//将bset的第2位设置为0,也就是给bset去除2号新号,不阻塞2号
            //设置当前信号屏蔽字为oset指向的值,也就是0
            sigprocmask(SIG_SETMASK, &oset, nullptr);
         }
    }

    return 0;
}

查看一下效果,在3秒后,我按下ctrl+c,然后我们的捕捉信号函数没有工作,说明信号被阻塞了,然后15秒后我们自动放开阻塞,瞬间打印出handler函数定义要打印的信息,再按ctrl+c就正常进行handler行为了

在这里插入图片描述


今日分享就到这里了~
在这里插入图片描述

Logo

助力广东及东莞地区开发者,代码托管、在线学习与竞赛、技术交流与分享、资源共享、职业发展,成为松山湖开发者首选的工作与学习平台

更多推荐