目录
上篇博客我们介绍了信号的产生,进程可能目前无法处理信号,所以要将信号进行临时保存。我们说:OS向进程发送信号本质上就是向PCB中写入信号,这个过程就是信号的保存过程,这篇博客我们就来详细介绍一下信号的保存。
core dump(核心转储)
但是谈之前我们需要介绍一下core dump(核心转储)的功能
我们知道man 7 signal可以查一些信号的信息
我们可以看到,Action中有很多都是Core和Term(termination),这两个都是终止进程。它们两个也是有区别的,Term就是直接终止进程,没有什么其他动作,但是Core就会触发core dump(核心转储)功能:简单来说,就是创建一个叫core的文件(内核要是老一点的话就是core.pid),文件中存着一些信息。
为什么要有这个功能呢?这个文件可以帮助我们知道进程为什么退出,以及执行到那行代码退出的
本质上就是将进程在内存中的有关调试的核心数据存到一个core文件中,它可以帮助我们进行调试
我们在gdb中就可以利用这个文件获取到一些信息,比如,我们写一个除零错误的代码试一试,因为浮点数错误的动作是Core
我就写了这样一个代码
我编译之后发现并没有core这个文件,这是因为,云服务器的core功能默认是关闭的,就是为了防止如果一直出现错误就会出现大量的core文件,导致磁盘空间被打满,服务器就会崩溃
我们可以通过ulimit -a查一下
size是0就是关闭这个功能的意思,我们可以ulimit -c 大小打开,比如:
当然把大小改成0就是关闭(ulimit -c 0)
打开之后再次运行程序就可以看到core文件了
我们用gdb打开可执行文件,然后输入core-file core就可以看到一些错误信息
我们之前说进程异常的时候有错误码和错误信号,说父进程等待子进程时子进程的错误代码和错误信号会通过一个整数传给父进程,但是我们没说第七位core dump标志位,其实就是如果我们开启了core功能并且信号的处理动作是core,那么这个位就为1,否则是0。我们可以写一个简单的代码验证一下
int main()
{
pid_t id = fork();
if (id == 0)
{
int a = 10;
a /= 0;
}
int status = 0;
pid_t rid = waitpid(id, &status, 0);
if (rid>0)
{
cout << "exit code is: " << ((status >> 8) & (0xff)) << endl;
cout << "exit signal is " << (status & (0x7f)) << endl;
cout << "core dump is " << ((status >> 7) & 0x1) << endl;
}
return 0;
}
信号的保存
首先我们需要了解几个概念:
实际执行信号的处理动作就叫做信号递达,其实就是处理信号的时候就叫做递达,递达有三种方式:默认 、自定义(捕捉)、忽略
信号从产生到递达之间的状态,称为未决(pending),其实就是OS向进程的PCB中写入信号到信号被处理这一阶段。pending本身就是待定的意思
进程可以选择阻塞(block)某个信号,这里的阻塞和进程的阻塞等待是不一样的,这里的阻塞是不让指定信号进程递达,这跟忽略也是不一样的,忽略也是处理的一种方式。阻塞可以理解为进程看不见这个信号,忽略是看见了,但是什么也不干
知道了上面的一些信号的概念,我们就可以推断出,进程的PCB中存在着两个位图,一个叫pending位图,用来放收到了哪个信号;一个叫block位图,用来放进程阻塞了哪个信号。还有一个函数指针数组,用来放各种信号的处理方法。
sigprocmask
那如果我们想人为的阻塞某些信号,就需要改block位图,我们肯定需要调用系统调用
man 2 sigprocmask
第一个参数就是一些选项,本质就是宏
SIG_BLOCK:第二个参数set包含了我们想要添加到当前信号屏蔽字(block位图)的信号,相当于mask=mask|set
SIG_UNBLOCK:第二个参数set包含了我们想要从当前信号屏蔽字中解除阻塞的信号,相当于mask=mask&~set
SIG_SETMASK:设置当前信号屏蔽字为set所指向的值,相当于mask=set
第二个参数是某个类型的一个指针,其实这个类型是OS给用户创建的数据类型,称为信号集,就是想让用户在这个类型创建的对象中设置数据,从而在结合第一个参数达到我们想要的效果
第三个参数是一个输出型参数,就是通过这个参数可以带出原来的信号屏蔽字
下面我们来介绍一下sigset_t这个数据类型
就是我们直接创建一个对象,然后利用一系列函数对这个对象进行操作,其实就这么几个函数:
int main() { sigset_t s; sigemptyset(&s);//将s的各个位变成0 sigfillset(&s);//将s的各个位变成1 sigaddset(&s, 2);//将s的第二位变成1 sigdelset(&s, 2);//将s的第二位变成0 sigismember(&s, 2);//判断s的第二位是否为1,为1返回1,为0返回0 return 0; }
sigpending
我们可以修改block位图,那pending位图呢?当然我们只能获取了,因为只有有信号的时候这个位图才会被修改,我们就可以利用下面的系统调用来获取pending位图
man sigpending
这也是一个输出型参数,我们只需要传一个sigset_t的对象指针就可以获得了
简单使用
下面我们来简单的使用一下上面说的一系列接口
我们可以先屏蔽二号信号,然后获取进程的pending位图并打印,然后可以再解除屏蔽,看看pending位图会如何变化
void printpending(sigset_t &pending)
{
for (int i = 31; i > 0; i--)
{
if (sigismember(&pending, i))
cout << "1";
else
cout << "0";
}
cout << endl;
}
void handler(int sig)
{
cout<<"pid: "<<getpid()<<" receive "<<sig<<" signal "<<endl;
}
int main()
{
signal(2,handler);
cout << "pid: " << getpid() << endl;
sigset_t block, old_block;
sigemptyset(&block);
sigemptyset(&old_block);
sigaddset(&block, 2);
sigprocmask(SIG_SETMASK, &block, &old_block);
cout << "block 2 signal success" << endl;
sigset_t pending;
sigemptyset(&pending);
int cnt = 5;
while (cnt--)
{
sigpending(&pending);
printpending(pending);
sleep(1);
}
sigprocmask(SIG_UNBLOCK, &block, &old_block);
cout << "unblock 2 signal success" << endl;
cnt = 5;
while (cnt--)
{
sigpending(&pending);
printpending(pending);
sleep(1);
}
return 0;
}
那如果我屏蔽所有信号呢?那是不是就没有信号可以杀死我了,其实不是的9号19号是无法屏蔽的,我们可以用下面的代码来验证一下
void printpending(sigset_t &pending) { for (int i = 31; i > 0; i--) { if (sigismember(&pending, i)) cout << "1"; else cout << "0"; } cout << endl; } int main() { cout<<"pid: "<<getpid()<<endl; sigset_t s; sigemptyset(&s); for (int i = 1; i < 32; i++) { sigaddset(&s, i); } sigprocmask(SIG_SETMASK, &s, nullptr);//全部信号都阻塞 sigset_t pending; sigemptyset(&pending); while (1) { sigpending(&pending); printpending(pending); sleep(1); } return 0; }
我们可以用一个shell脚本去不断地给进程发1-31的信号
c=1; while [ $c -le 31 ];do kill -$c 24923;echo "kill -$c 24923"; let c++; sleep 1; done
发现发到9号的时候进程就退出了,证明九号无法被屏蔽
然后我们从10号继续开始发
发到19号信号时进程会停止,证明也无法屏蔽
剩下的就都可以屏蔽了
在处理信号的时候,到底是先将pending位图置0,还是先递达呢?其实我们就可以看看在递达的时候pending位图是几,是0就证明先置0,是1就证明先递达
void printpending(sigset_t &pending) { for (int i = 31; i > 0; i--) { if (sigismember(&pending, i)) cout << "1"; else cout << "0"; } cout << endl; } void handler(int sig) { cout << sig << " signal begin deal"<<endl;; sigset_t s; sigemptyset(&s); sigpending(&s); printpending(s); cout << sig << " signal end deal"<<endl;; } int main() { signal(2, handler); while (1) ; return 0; }
我们可以看到是先置0