搜索引擎推广的网络营销渠道,成都网站优化推广,做网站的科技公司,河南省建设劳动学会网站目录 前言
一、信号的常见概念
1.信号递达
2.信号未决
3.信号阻塞
二、Linux中的递达未决阻塞
三、信号集
四、信号集的处理
1.sig相关函数
2.sigprocmask()函数
3.sigpending()函数
五、信号的处理时机
六、信号处理函数 前言
在之前#xff0c;我们学习了信号…目录 前言
一、信号的常见概念
1.信号递达
2.信号未决
3.信号阻塞
二、Linux中的递达未决阻塞
三、信号集
四、信号集的处理
1.sig相关函数
2.sigprocmask()函数
3.sigpending()函数
五、信号的处理时机
六、信号处理函数 前言
在之前我们学习了信号的概念以及信号的产生方式知道进程在何种情况下会收到信号进程收到并不一定会立刻执行因此需要对信号进行保存再来处理信号。
一、信号的常见概念
在学习信号的保存前 我们先来看看信号的常见概念 实际执行信号的处理动作称为信号递达(Delivery)信号从产生到递达之间的状态,称为信号未决(Pending)。进程可以选择阻塞 (Block )某个信号。被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作.注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。 1.信号递达
执行信号的处理动作称之为信号递达具体操作中对信号一般有三种处理方法 信号默认处理信号忽略信号自定义捕捉 我们先来看看信号的默认处理代码中首先是先自定义捕捉了当我们前sleep(5)之前输入ctrlc信号就会被自定义捕捉而再后输入就是默认的处理方式了。 因此5秒之前输入ctrlc会先打印自定义内容然后2号信号变默认处理方式开始循环现在我们给2号新号就会终止进程 那如果我们选择忽略2号新号 自然ctrlc就不能终止了。 SIG_DFL和SIG_IGN的本质就是被函数指针强转的0和1因为用户传递的地址是非常大的0和1地址处是代码区用户是没有设置权限的因此不用担心传递的自定义函数地址是0和1如果是0和1编译器就会特殊处理知道你想要的是默认还是忽略。 注意忽略也是一种处理方式 2.信号未决
信号从产生到递达之间的状态称之为信号未决。
信号产生时可能当前进程再做更重要的事情不能及时递达这种情况就叫做信号未决那么就要求进程对信号进行保存进程使用了信号位图的方式进行保存。那么信号如果被处理信号位图中肯定就要置0因此当还在信号位图为1的时候也可以称作信号未决。
3.信号阻塞
进程可以选择阻塞某个信号。
被阻塞的信号产生时将保持在未决状态直到进程接触对信号的阻塞才执行递达的操作。
这里我们看到阻塞和忽略好像差不多那么阻塞和忽略有什么区别呢
阻塞是进程根本没有处理该信号而是处于未决状态而忽略是阻塞通过后处理的一种操作只不过我的处理方式是忽略。 小总结 信号是未决的却不一定被阻塞因为进程可能还没来得及处理信号。 如果信号被阻塞且收到了当前信号那么信号一定是未决的。 二、Linux中的递达未决阻塞
我们说了这么多概念是有点绕但确实是比较重要的进程PCB实际中是真有这三张表的block阻塞、pending未决、handler递达处理方法。
如果我们想给进程2号信号阻塞那么就将进程task_struct中指针指向的block表的bit位12-1处置1就好了。因为没有0号信号因此要-1。 那么从此以后我们要查看进程对信号的设置以及处理方式只需要横向观看就好。block是否阻塞pending是否有信号handler处理方法是什么。 block 0 pending 0代表没有阻塞也没信号产生或进程已经处理完信号block 0 pending 1代表没有阻塞信号产生进程还没来得及处理。block 1 pending 0代表阻塞没有信号产生或进程已经处理完信号block 1 pending 1代表阻塞信号产生进程无法处理信号。同时如果当阻塞取消进程就会收到信号再做相应处理并将pending表置0。当然如果此时有很多该信号产生比如你一直ctrlc由于阻塞进程收不到信号当阻塞取消只会产生一次信号。 三、信号集
从上面可知,每个信号只有一个bit的未决标志,非0即1,不记录该信号产生了多少次,阻塞标志也是这样表示的。 因此,未决和阻塞标志可以用相同的数据类型sigset_t来存储,sigset_t称为信号集。
这个类型可以表示每个信号的“有效”或“无效”状态,在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞,而在未决信号集中“有效”和“无效”的含义是该信号是否处于未决状态。
sigset_t本质上就是位图。
四、信号集的处理
1.sig相关函数 #includesignal.h int sigemptyset(sigset_t *set); //使其中所有信号的对应bit清零 int sigfillset(sigset_t *set); //使其中所有信号的对应bit置1 int sigaddset (sigset_t *set, int signo); //该信号集中添加某种有效信号 int sigdelset(sigset_t *set, int signo); //该信号集中删除某种有效信号 int sigismemberconst sigset_t *set, int signo); //判断一个信号集的有效信号中是否包含某种信号 2.sigprocmask()函数 sigprocmaks可以读取或更改进程的信号屏蔽字 参数1how选项SIG_BLOCK、SIG_UNBLOCK、SIG_SETMASK 1.SIG_BLOCK添加到阻塞的信号 mask mask|set 2.SIG_UNBLOCK解除阻塞的信号 mask mask~set 3.SIG_SETMASK直接设置当前set的值 mask set 参数2set表示对那些信号进行处理参数3oset输出型参数设置为block表之前的信号信息 函数有点多我们直接使用起来我们让进程2号信号阻塞如果没阻塞肯定会打印消息阻塞了发送2号信号就没有反应。 现在无论我们是ctrlc还是发送2号信号该进程都不会有反应因为2号信号被阻塞了。 你可以阻塞绝大部分信号但是进程无法阻塞9号信号。9号信号是管理员信号操作系统发现恶意进程会直接发9号信号kill他可不管你有没有屏蔽。
3.sigpending()函数 sigpending读取当前进程的未决信号集,通过set参数传出。 参数1set输出型参数可以将进程的未决信号输出到set中。返回值成功返回0出错返回1 我们直接来使用sigpending并写了打印函数帮助我们观看pending表的变化。
#include iostream
#include unistd.h
#include signal.h
#include stringusing namespace std;void PrintPending(const sigset_t pending)
{for (int i 31; i 0; i--){if (sigismember(pending, i))cout 1;elsecout 0;}cout endl;
}int main()
{cout pid: getpid() endl;// 屏蔽2号信号sigset_t block, oblock;sigemptyset(block); // 初始化为全0sigemptyset(oblock); // 初始化为全0sigaddset(block, 2); // 将bit位置1sigaddset(block, 3); // 将bit位置1 // 将bit位置1sigprocmask(SIG_BLOCK, block, oblock); // 写入到block信号集中sigset_t pending;while (true){sigpending(pending); // 获取pending表里的内容PrintPending(pending); // 打印sleep(1);}
}
效果如下可以看到pending表的状态变化 我们知道当进程收到信号执行相关操作后pending表对应的信号bit位会被置为0那他是将处理操作做完再置0还是处理之前就置为0了呢
我们写如下代码验证一下 我们发现pending表在自定义捕捉函数中已经是0了证明在信号处理前pending表已经被置0。 我们学习了这么多都没有提到信号的保存但实际上什么block表pending表都是信号保存的实现。
五、信号的处理时机
我们一直在说信号会在合适的时候处理那么具体什么时候是合适的时候
其实是进程从内核态返回到用户态的时候会进行信号的检测和信号的处理。
其中用户态是一种受控的状态能够访问的资源是有限的内核态是一种操作系统的工作状态能访问大部分的系统资源。 我们所写的代码如果涉及到了系统调用那么就会将身份切换为内核那么此时就可以访问内核中的数据结构或字段当访问结束准备返回时并不会立刻返回而是利用内核身份先看看进程block和pending是否有内容发现有内容就会做相应的处理。 就算你没有代码里写得很干净确实没有系统调用但是当进程切换的时候进程也会在用户态和内核态转化因此不用担心你的代码不会被终止。但这里也涉及到一些问题这样可能会中断关键代码的执行因此还需要合理的信号函数或者设置关键代码适当的同步机制来确保其原子性执行。不懂没关系等学过多线程就明白了 如果检测的时候发现某个信号block没有被阻塞同时pending表为1就要根据handler表做相应的处理此时身份一定要切换为用户为了防止进程利用handler的内核身份去执行自己的恶意操作。那么执行完毕需要返回时如果直接返回到用户代码处那么你怎么知道代码执行到哪一步了该返回到那个地方你仅仅是个函数呀 那么你执行完sighandler函数后仍然需要执行系统调用sigreturn回到内核虽然我们代码中没写但是操作系统会帮我们执行之后就能访问到相关寄存器或者pc指针等等内容直到当前进程执行到哪里了回去继续执行代码。 六、信号处理函数
上一章信号的概念和产生我们学过signal自定义捕捉信号。今天我们再来学一个函数sigaction sigaction检查并改变信号的处理动作 参数1signum对那个信号进行自定义捕捉 参数2是一个结构体重要字段我们只看sa_handler和sa_mask 1.sa_handler跟signal的第二个参数一样自定义捕捉函数的地址 2.sa_mask设置其他信号屏蔽在当前自定义捕捉函数中将其他信号也屏蔽等到捕捉函数处理完再取消屏蔽 参数3输出型参数用来保存调用之前的结构体数据 返回值成功返回1失败返回0。 直接上代码我们让sa_handler进行2号信号自定义捕捉同时给sa_mask设置3号信号观察给2号信号后再发2号信号与3号信号pending表的变化。
#include iostream
#include unistd.h
#include signal.h
#include stringusing namespace std;void PrintPending(const sigset_t pending)
{for (int i 31; i 0; i--){if (sigismember(pending, i))cout 1;elsecout 0;}cout endl;
}void sighandler(int signo)
{cout 收到了 signo 号信号 endl;while(1){sigset_t pending;sigpending(pending);PrintPending(pending);sleep(1);}
}int main()
{cout pid: getpid() endl;struct sigaction act,oact;act.sa_handler sighandler; //自定义捕捉更改变量阶段还没进行写入sigemptyset(act.sa_mask); //sa_mask清空sigaddset(act.sa_mask,3); //sa_mask写入3号信号sigaction(2,act,oact); //更改信号的处理动作while(1)sleep(1);
}当我们ctrlc给了2号信号就开始执行自定义捕捉的代码了由于自定义捕捉是死循环因此你再发送2号信号只会让pending表置1同时由于我们设置了sa_mask的3号信号于是在执行2号信号的自定义捕捉时3号信号也无法处理于是pending表3号信号也置1了。