Linux进程信号的发送和保存指南
<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li>一、信号发送<ul class="second_class_ul"><li>1、信号动作</li><li>2、信号发送的本质</li><ul class="third_class_ul"><li>普通信号</li><li>实时信号</li></ul><li>3、core dump</li><ul class="third_class_ul"></ul></ul></li><li>二、信号的保存<ul class="second_class_ul"><li>1、前置概念</li><ul class="third_class_ul"></ul><li>2、阻塞信号</li><ul class="third_class_ul"></ul><li>3、保存信号</li><ul class="third_class_ul"></ul><li>4、信号递达</li><ul class="third_class_ul"></ul><li>5、总结</li><ul class="third_class_ul"></ul></ul></li><li>三、信号集操作函数<ul class="second_class_ul"><li>1、设置block位图</li><ul class="third_class_ul"></ul><li>2、设置pending位图</li><ul class="third_class_ul"></ul><li>3、设置handler行为</li><ul class="third_class_ul"></ul></ul></li><li>四、验证信号保存行为<ul class="second_class_ul"></ul></li></ul></div><p class="maodian"></p><h2>一、信号发送</h2><p class="maodian"></p><h3>1、信号动作</h3>
<p>通过指令<code>man -7 signal</code>查看信号的手册,然后往下翻翻可以看到普通信号发出后对应的操作,以及它们的信号编号,和详细描述信息</p>
<p style="text-align:center"><img alt="" src="https://zhuji.jb51.net/uploads/allimg/20250919/2-2509191F64V47.png" /></p>
<p class="maodian"></p><h3>2、信号发送的本质</h3>
<p class="maodian"></p><h4>普通信号</h4>
<p>信号发送的本质实际上是写信号,把信号写到进程PCB结构体对应的位图上去,在进程的PCB中有这么一个位图(是pending位图,下面会说)正好对应着我们从1 ~ 31的普通信号编号,收到哪个信号就将哪一位对应的比特位置为1,表示收到信号,然后PCB再做对应的工作</p>
<p>值得注意的是,如果连续发普通信号,那么进程只会处理最后一次的信号,每次写都是覆盖写的</p>
<p class="maodian"></p><h4>实时信号</h4>
<p>我们前面说过信号分为31个普通信号和31个实时信号,实时信号的作用类似于我们嵌入式RTOS实时运转场景,要保持实时性,实时信号发送的本质类似于普通信号,不过此时我们保存信号的载体不再是一个位图,而是一个结构体,它们被组织在信号队列当中,谁先发送谁就先入队,队列遵循先入先出的规则,所以先发送也代表着先被处理</p>
<p>值得注意的是,如果连续发实时信号,那么进程会将队列中的信号一个个全部处理</p>
<p class="maodian"></p><h3>3、core dump</h3>
<p>我们在一文中解释了wait函数的status参数,其中第7位就是core dump标志,这里简单解释一下<br />当程序在运行过程中发生崩溃(如段错误、除零错误等),<code>Core dump</code> 会记录下程序崩溃瞬间的内存状态,包括寄存器的值、调用栈信息、全局变量和局部变量的值等,开发人员可以使用调试工具(如 GDB)加载 <code>Core dump</code> 文件,通过分析这些信息,准确地找到程序崩溃的位置和原因</p>
<p>我们可以通过<code>ulimit -c 10240</code>将<code>core</code>文件的大小限制修改为<code>10240</code>字节,出现错误的时候<code>core</code>文件可能瞬间会被打满的,所以我们云服务器上一般默认<code>core</code>文件的大小限制为0,我们要是用的话再修改它的大小限制即可<br />形成的文件叫做<code>core.pid</code>,<code>pid</code>就是出错进程的<code>pid</code>,假设<code>test</code>进程出现错误,<code>12314</code>是它的<code>pid</code>,我们可以通过在gdb模式下输入<code>gdb test core.12314</code>打印错误信息和原因</p>
<p class="maodian"></p><h2>二、信号的保存</h2>
<p class="maodian"></p><h3>1、前置概念</h3>
<p><strong>实际执行信号的处理动作</strong>称为<strong>信号递达</strong></p>
<p><strong>信号从产生到递达之间的状态,称为信号未决</strong></p>
<p><strong>被阻塞的信号产生是将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作</strong></p>
<p style="text-align:center"><img alt="" src="https://zhuji.jb51.net/uploads/allimg/20250919/2-2509191F64T63.png" /></p>
<p class="maodian"></p><h3>2、阻塞信号</h3>
<p>信号被阻塞就是将信号阻塞在信号未决状态,具体实现阻塞功能的,是一个位图<code>block</code></p>
<p><code>block</code>是一个位图,共31位,对应1 ~ 31号信号,当对应比特位为1时,表示该编号信号被阻塞,为0则表示不阻塞</p>
<p>如果信号被阻塞则进入阻塞态,若没有被阻塞那么信号进入未决状态</p>
<p class="maodian"></p><h3>3、保存信号</h3>
<p>未决状态的作用就是保存信号,它保存信号的方式也是通过位图<code>pending</code></p>
<p><code>pending</code>也是一个位图,与<code>block</code>一致,对应的下标和信号编号也是一一对应,当对应比特位为1时,表示该编号信号处于未决状态,为0则信号递达</p>
<p><strong>实际上<code>block</code>和<code>pending</code>都属于保存信号,只不过因为有两个位图,我们分开来说罢了</strong></p>
<p class="maodian"></p><h3>4、信号递达</h3>
<p>信号递达后的信号,会执行相对应的行为,有<code>SIG_DFL</code>:默认处理动作,<code>SIG_IGN</code>:忽略,和自定义处理<code>sighandler</code>,这在前面提到过</p>
<p style="text-align:center"><img alt="" src="https://zhuji.jb51.net/uploads/allimg/20250919/2-2509191F64XG.png" /></p>
<p class="maodian"></p><h3>5、总结</h3>
<p style="text-align:center"><img alt="" src="https://zhuji.jb51.net/uploads/allimg/20250919/2-2509191F64W31.png" /></p>
<p>一个信号,首先要经过<code>block</code>,<code>block</code>为0来到<code>pending</code>,<code>pending</code>为0来到<code>handler</code>执行动作,其中,9号和19号新号还是特例,它们是不能被阻塞和保存的,这两个信号一旦发出就是直接handler,其实也不用handler了,它们对应的不可能为忽略和信号捕捉后的自定义函数,只能为默认动作终止和暂停</p>
<p class="maodian"></p><h2>三、信号集操作函数</h2>
<p>信号集操作函数顾名思义就是操作信号集的函数,<code>sigset_t</code>被称作信号集,是操作系统提供的数据类型,用于描述位图,下面就是信号集操作函数</p>
<div class="dxycode"><pre class="brush:cpp;">#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</pre></div>
<p class="maodian"></p><h3>1、设置block位图</h3>
<p><code>sigprocmask</code>是一个在信号处理中非常重要的系统调用,主要用于检查、修改进程的信号掩码(阻塞信号集)</p>
<div class="dxycode"><pre class="brush:cpp;">#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset); </pre></div>
<p>返回值:若成功则为0,若出错则为-1</p>
<p><code>how</code>:指定对信号掩码的操作方式</p>
<p><code>how</code> 取值 含义 示例说明</p>
<table><tbody><tr></tr></tbody><tbody><tr><td><code>SIG_BLOCK</code></td><td>将 <code>set</code> 所指向的信号集中的信号添加到当前的信号掩码中,即阻塞 <code>set</code> 中的信号</td><td>若当前信号掩码已阻塞 <code>SIGINT</code>,使用 <code>SIG_BLOCK</code> 并传入包含 <code>SIGTERM</code> 的信号集,<code>SIGTERM</code> 也会被阻塞</td></tr><tr><td><code>SIG_UNBLOCK</code></td><td>从当前的信号掩码中移除 <code>set</code> 所指向的信号集中的信号,即解除对 <code>set</code> 中信号的阻塞</td><td>若当前信号掩码阻塞了 <code>SIGINT</code> 和 <code>SIGTERM</code>,使用 <code>SIG_UNBLOCK</code> 并传入包含 <code>SIGINT</code> 的信号集,<code>SIGINT</code> 信号的阻塞状态将被解除</td></tr><tr><td><code>SIG_SETMASK</code></td><td>将当前的信号掩码设置为 <code>set</code> 所指向的信号集,覆盖原来的信号掩码</td><td>若原信号掩码阻塞 <code>SIGINT</code>,使用 <code>SIG_SETMASK</code> 并传入包含 <code>SIGTERM</code> 的信号集,信号掩码将只阻塞 <code>SIGTERM</code></td></tr></tbody></table>
<p><code>set</code>:指向一个<code>sigset_t</code>类型的信号集,该信号集包含了要操作的信号,如果<code>how</code>的值为<code>SIG_BLOCK</code>或<code>SIG_UNBLOCK</code>,则<code>set</code>表示要添加或移除的信号集;如果<code>how</code>的值为<code>SIG_SETMASK</code>,则<code>set</code>表示要设置的新的信号掩码,若该参数为<code>NULL</code>,则不改变当前的信号掩码,仅获取当前信号掩码,此时<code>oset</code>不能为<code>NULL</code></p>
<p><code>oset</code>:指向一个<code>sigset_t</code>类型的信号集,用于存储调用<code>sigprocmask</code>之前的信号掩码,如果不需要保存旧的信号掩码,可以将该参数设置为<code>NULL</code></p>
<p class="maodian"></p><h3>2、设置pending位图</h3>
<p><code>sigpending</code>是一个用于获取进程当前未决信号集的系统调用</p>
<div class="dxycode"><pre class="brush:cpp;">#include <signal.h>
int sigpending(sigset_t *set);</pre></div>
<p>返回值:成功返回0,失败返回-1</p>
<p><code>set</code>:<code>sigpending</code>函数会将当前进程中处于未决状态(即已发送但由于被阻塞而尚未被处理)的信号集存储到<code>set</code>所指向的<code>sigset_t</code>对象中</p>
<p class="maodian"></p><h3>3、设置handler行为</h3>
<p>三种情况,默认,忽略和自定义,自定义那当然是<code>signal</code>函数,前面有,不再赘述</p>
<p class="maodian"></p><h2>四、验证信号保存行为</h2>
<div class="dxycode"><pre class="brush:cpp;">#include<iostream>#include<signal.h>#include<unistd.h>
usingnamespace std;//打印出位图voidPrintPending(const sigset_t &pset){
for(int i =31; i >=1; i--)
{
cout <<sigismember(&pset, i);
}
cout << endl;}
voidhandler(int signum){
cout <<"catch a signum: "<< signum << endl;}
intmain(){
//自定义捕捉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);
}
}
return0;}</pre></div>
<p>查看一下效果,在3秒后,我按下<code>ctrl+c</code>,然后我们的捕捉信号函数没有工作,说明信号被阻塞了,然后15秒后我们自动放开阻塞,瞬间打印出<code>handler</code>函数定义要打印的信息,再按<code>ctrl+c</code>就正常进行<code>handler</code>行为了</p>
<p style="text-align:center"><img alt="" src="https://zhuji.jb51.net/uploads/allimg/20250919/2-2509191F64c14.png" /></p>
<p>今日分享就到这里了</p>
<p>以上就是【Linux】进程信号的发送和保存的详细内容,更多相关资料请阅读琼殿技术社区其它文章!</p>
頁:
[1]