个人主页:Lei宝啊
愿所有美好如期而遇
前言
Linux信号(产生)-CSDN博客,上一个章节我们详细介绍了信号是什么,为什么要有信号,怎样产生信号,以及信号产生的几个问题,这个章节我们将介绍信号的保存。
我们说,信号被发送给一个进程时不一定要立即处理,如果进程在处理临界区时,那么信号就会被保存在进程PCB中,怎么保存?使用位图,结合上个章节,我们说当操作系统通过中断向量表中的方法将键盘按下的数据读上来后进行判定,如果是普通字符,那么就写到键盘的缓冲区中,如果是控制命令,那么就解释成信号发送给进程,那么这句标红的话是什么意思?我们将图贴过来解释:
其实就是将2号信号写到task_struct中的pending位图中,因为没有0号信号,第一位就不使用。
解决上节的遗留问题:Core和Term区别,同时提出一个新问题。
这两个信号,我们分别测试一下。
Core和Term好像没什么区别,都终止了进程,SIGFPE信号仅仅只是在后面多加了core dumped,但core dumped是什么意思?
core dump:核心转储,将进程在内存中的核心数据(与调试有关)转储到磁盘上形成,也就是说,action动作为Core的信号,在进程收到这类信号时,会生成一个core文件,这个文件中包含着,进程为什么崩溃,在第几行崩溃,这个文件可以协助我们进行调试。
那么,另一个问题,下面这张图中的core dump是什么意思?
这个标志位的意思是,标志进程是否发生核心转储,在进程退出时,我们可以通过status变量来获取进程的状态,我们来获取一下两者的core dump标志,再看看Term和Core有什么区别:
#include <iostream>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
using namespace std;
int main()
{
int id = fork();
if(id == 0)
{
int a = 10;
a/= 0;
}
int status;
int rid = waitpid(id,&status,0);
cout << "core dump: " <<((status >> 7) & 0x1) << endl;
return 0;
}
#include <iostream>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
using namespace std;
int main()
{
int id = fork();
if(id == 0)
{
kill(getpid(),9);
}
int status;
int rid = waitpid(id,&status,0);
cout << "core dump: " <<((status >> 7) & 0x1) << endl;
return 0;
}
在云服务器上,生成核心转储的功能默认是被关闭的,需要我们主动打开,至于为什么会默认关闭,我们后面点说。
现在我们来看看核心转储如何打开和关闭:
核心转储文件大小为0表示关闭。
使用命令ulimit -c 文件大小 开启核心转储功能。
现在,我们再来跑一下可执行程序,注意,我们仅仅只是打开了核心转储,其余多余动作都没有做。(core文件在ubantu5.15中被保存在/corefile下)
我们可以看到,Core动作的8号信号产生了core文件,同时core dump标志位被置1,而Term动作的9号信号没有产生core文件。
也就是说,core文件的产生需要这样的条件:首先动作为Core,再一个就是ulimit -c 文件大小,这个文件大小不为0。
接下来我们使用core文件协助我们进行调试:
小总结:
Term和Core的主要区别就在于core文件上,Core信号在核心转储功能打开的情况下,可以产生core文件来协助调试,得到报错信息以及位置,同时,对这个信号进行递达的进程会将core dump标志位设为1。
信号的保存
- 实际执行信号的处理动作称为信号递达,而信号递达的方式有三种:默认,自定义,忽略。
- 信号从产生到递达之间的状态称为信号未决(Pending),其实也就是信号的保存。
- 进程可以选择阻塞某个信号。
我们仍然要提起,信号不是一定要立即处理的,所以有了信号的保存。
在内核中,信号的保存实际上是有三张表的,pending表:临时存储信号,block表:表示要阻塞哪些信号,函数指针数组表:存储各种信号处理方法的地址,我们看图:
- Pending位图:bit位的位置表示信号编号,bit位的内容表示是否收到指定信号。
- Block位图: bit位的位置表示信号编号,bit位的内容表示是否阻塞指定信号。
- handler表: 根据Pending位图比特位为1的编号,在表中索引处理方法地址,调用方法。
如果一个信号被阻塞,那么这个信号永远不会被递达,除非被解除阻塞,那么有人就会这样:我将信号全部阻塞,进程是不是就无法退出了?这个问题我们后面实践一下。
阻塞和忽略的区别:阻塞是在递达前,而忽略是递达的动作,是递达后,简单来说,就是被屏蔽和已读不回的区别。
阻塞一个信号,是需要我们进行设置的,handler表中的处理方法我们也可以通过signal系统调用进程更改,下面我们将对这三张表进行操作。
首先我们提出一个场景:
阻塞2号信号,并发送2号信号,获取Pending表。
先来看阻塞信号的系统调用:
第一个参数how,意为如何阻塞一个信号,有三种宏来决定:
- SIG_BLOCK,相当于向Block表中或上我们要新增的阻塞信号。
- SIG_UNBLOCK,相当于解除某些信号的阻塞。
- SIG_SETMASK,相当于直接使我们希望阻塞的信号集覆盖信号屏蔽字。
第二个参数,我们在栈上定义一个sigget_t类型的对象,然后通过和他配套的方法将我们想阻塞的信号设置进去,然后通过这个参数将他们设置进内核。
信号集操作函数:
#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset (sigset_t *set, int signo);
int sigdelset(sigset_t *set, int signo);
int sigismember(const sigset_t *set, int signo);
第三个参数,老的信号屏蔽字,我们可以通过这个参数带出来。
获取Pending表的系统调用:
接下来我们通过代码来理解上面这些:
#include <iostream>
#include <signal.h>
#include <unistd.h>
using namespace std;
void Print(sigset_t pending)
{
cout << "pid: " << getpid() << " pending: ";
for (int i = 31; i > 0; i--)
{
if (sigismember(&pending, i))
cout << "1";
else
cout << "0";
}
cout << endl;
}
void handler(int signo)
{
cout << signo << "号信号被递达--";
sigset_t pending;
sigpending(&pending);
Print(pending);
}
int main()
{
// 阻止2号信号使进程退出
signal(2, handler);
// 栈上我们想要阻塞的信号
sigset_t block;
sigemptyset(&block);
sigaddset(&block, 2);
// 设置进入内核
sigprocmask(SIG_SETMASK, &block, nullptr);
// 打印Pending表信息
int cnt = 0;
while (true)
{
cnt++;
sigset_t pending;
sigpending(&pending);
Print(pending);
sleep(1);
//解除对2号信号的屏蔽
if(cnt == 20) sigprocmask(SIG_UNBLOCK,&pending,nullptr);
}
return 0;
}
那么有人就会这样搞:我让所有信号被屏蔽,那么是不是这个进程就退出不了了呢?我们来试试:
#include <iostream>
#include <signal.h>
#include <unistd.h>
using namespace std;
void Print(sigset_t pending)
{
cout << "pid: " << getpid() << " pending: ";
for (int i = 31; i > 0; i--)
{
if (sigismember(&pending, i))
cout << "1";
else
cout << "0";
}
cout << endl;
}
void handler(int signo)
{
cout << signo << "号信号被递达--";
sigset_t pending;
sigpending(&pending);
Print(pending);
}
int main()
{
// 阻止2号信号使进程退出
signal(2, handler);
// 栈上我们想要阻塞的信号
sigset_t block;
sigfillset(&block);
// sigemptyset(&block);
// sigaddset(&block, 2);
// 设置进入内核
sigprocmask(SIG_SETMASK, &block, nullptr);
// 打印Pending表信息
int cnt = 0;
while (true)
{
cnt++;
sigset_t pending;
sigpending(&pending);
Print(pending);
sleep(1);
//解除对2号信号的屏蔽
//if(cnt == 20) sigprocmask(SIG_UNBLOCK,&pending,nullptr);
}
return 0;
}
我们这里就不做演示了,直接给出结论:
9号,19号信号无法被屏蔽,18号信号会做特殊处理。