Linux信号
1.信号的介绍
- 什么是信号:
(1) 简单来说就是操作系统内核区响应某些条件而产生一个事件,操作系统可以把该事件发给进程,对于这个进程来说,这个事件就是信号。
(2) 进程在接收到信号后会采取相应的动作。
- 信号的本质:就是软件层次对中断的模拟,signal机制可以被理解为进程的软中断,它是一种异步通信机制。
例子:
(1) 假设用户启动了一个交互式的前台进程,然后通过ctrl+c结束它,系统通过键盘产生一个硬件中断。
(2) cpu受硬件中断的影响暂停用户空间的代码,
(3) 操作系统将该硬件中断解释为一个SIGINIT信号,并将其记录在*进程的PCB的信号位图*上(信号位图就是一个PCB中表示信号的位图)。
(4) 该进程收到信号后会返回内核区,在进程从内核区返回用户区继续执行之前,会检查PCB中的信号位图,就可以检测到SIGINT信号,SIGINT信号默认处理动作为终止进程,所以就将进程直接终止了。
2.信号的发送
1. 使用硬件发送信号。
比如在终端运行可执行文件时,按ctrl+c。此时会由键盘发送中断信号。
2. 进程异常产生的信号
(1) 进程崩溃的本质:CPU内部有一个状态寄存器。如果CPU的计算过程出现了错误,错误信息就会被存储在这个状态寄存器中,CPU也会立马终止进程的代码。
(2) 操作系统随时在监视CPU的状态,CPU的状态寄存器记录了异常,操作系统立马就会去给造成异常的进程发送信号,不同的信号包含了不同的错误信息,我们就看到了进程的报错信息。
额外的知识点:如何定位程序究竟是在哪一行崩溃的
调试原理:信号之中都有一个core dump标志位。这个标志位的默认值为0,对于大多数进程终止信号,都会将该标志位设置为1,然后将进程的信息转存到core文件中,方便我们后期调试。
- 写一个执行中会产生段错误的程序
//signal.cpp
#include<iostream>
int main(void)
{
int* p = nullptr;
*p = 1;
return 0;
}
调试步骤:
- ulimit -c 查看core文件的大小上限,如果为0,表示不会生成core文件,我们需要给core文件设置一个合适的大小,直接设置为不限制大小也可以。(因为进程终止后需要将进程信息转存到core文件中,如果core文件大小上限为0则无法存入,需要我们手动设置:ulimit -c unlimited)
- g++ -g xxx.cpp -o xxx来生成一个可调试的执行文件。
- 执行该可执行文件,发生段错误,生成core文件。
- 使用gdb进行调试。
[root@localhost project]# gdb signal
(gdb) core-file core.42876
[New LWP 42876]
Core was generated by `./signal'.
Program terminated with signal 11, Segmentation fault.
#0 0x00000000004006ed in main () at signal.cpp:5
5 *p = 1;
Missing separate debuginfos, use: debuginfo-install glibc-2.17-157.el7.x86_64 libgcc-4.8.5-44.el7.x86_64 libstdc++-4.8.5-44.el7.x86_64
有时会出现没有core文件的情况,core文件是由core文件的配置文件控制生成的。配置文件为/proc/sys/kernel/core_pattern中,可以设置pid,uid等属性,有兴趣可以研究一下,一般来说,这些属性用不到的。
3. 使用系统调用发送信号
(1) kill函数。
例子:
- 执行demo.cpp,每秒输出一行linux
//demo.cpp
#include<iostream>
#include<unistd.h>
int main(void)
{
while(1)
{
sleep(1);
std::cout<<"linux"<<std::endl;
}
return 0;
}
- 执行ps -ef | grep demo,获取demo的pid
[root@localhost project]# ps -ef | grep demo
root 43465 3573 0 01:14 pts/0 00:00:00 ./demo
root 43585 43485 0 01:14 pts/1 00:00:00 grep --color=auto demo
- 执行kill.cpp,kill掉demo
//kill.cpp
#include<iostream>
#include<sys/types.h>
#include<signal.h>
int main(int argc,char* argv[])
{
if(argc != 2)
{
std::cout<<"process needs a pid parament"<<std::endl;
return -1;
}
if(kill(atoi(argv[1]),SIGINT) == -1)
{
perror("kill func error");
return -1;
}
return 0;
}
(2) 也可以直接使用kill命令发生信号,其实kill命令就是对kill函数的封装。
- 执行demo.cpp,每秒输出一行linux
- 执行ps -ef | grep demo,获取demo的pid
- 执行kill pid(kill 10596),杀掉进程
3. 信号的接收处理
signal函数:
signal函数作用非常简单,就是对信号进行处理。
对信号的处理方式一共有三种:
- 默认处理:每一种信号都有默认的处理方式。
//SIG_DFL约等于这段话没写
if(signal(SIGINT,SIG_DFL) == SIG_ERR)
{
perror("signal func error");
return -1;
}
- 忽略处理:可以让进程忽略某些信号,注意SIGKILL和SIGSTOP信号不能被忽略,也就是9号和19号信号。
#include<iostream>
#include<signal.h>
#include<unistd.h>
//接收信号
int main(void)
{
//第一个参数表示处理哪种信号,第二个参数表示对信号的处理方式
// signal() returns the previous value of the signal handler, or SIG_ERR on error.
//In the event of an error, errno is set to indicate the cause.
if(signal(SIGINT,SIG_IGN) == SIG_ERR)
{
perror("signal func error");
return -1;
}
while (1)
{
//此时ctrl+c无法停下来,因为ctrl+c发送的是SIGINT信号,SIG_IGN忽略了SIGINT信号
//kill命令能停下,因为kill命令发送的是9号信号
std::cout<<"Linux is great"<<std::endl;
sleep(1);
}
return 0;
}
- 对信号进行捕捉:注意,SIGKILL和SIGSTOP信号不能被捕捉。
#include<iostream>
#include<signal.h>
#include<unistd.h>
//捕捉信号
void signal_handler_func(int signalNum)
{
//捕捉到SIGINT信号后打印
//运行结果就是ctrl+c后会执行这条语句,并且程序不会停下来
std::cout<<"catch SIGINT signal"<<std::endl;
}
int main(void)
{
//第一个参数表示处理哪种信号,第二个参数表示对信号的处理方式
// signal() returns the previous value of the signal handler, or SIG_ERR on error.
//In the event of an error, errno is set to indicate the cause.
if(signal(SIGINT,signal_handler_func) == SIG_ERR)
{
perror("signal func error");
return -1;
}
while (1)
{
//此时ctrl+c也无法停下来,因为SIGINT信号被捕捉,只会打印而不会中断
//要用kill停下来
std::cout<<"Linux is great"<<std::endl;
sleep(1);
}
return 0;
}
4. 用信号处理僵尸进程
- 两种处理僵尸进程的方法:
(1) 直接将父进程kill掉,这在绝大多数项目中当然是不行的,我们还指望父进程完成任务呢。
(2) 父进程调用wait函数,但这样父进程就阻塞了,严重浪费资源,waitpid函数也比较麻烦,如果设置为阻塞状态会存在和wait函数同样的问题,不阻塞又涉及到进程间通信了,比较麻烦。
- 用信号+wait函数处理僵尸进程
#include<unistd.h>
#include<iostream>
#include<cstring>
#include<signal.h>
#include<sys/types.h>
#include<sys/wait.h>
void SIGCHILD_handler_func(int sigChild)
{
if(wait(nullptr) == -1)
{
perror("wait func error");
exit(1); //退进程
}
}
int main(void)
{
pid_t pid = fork();
if(pid == -1)
{
perror("fork func error");
return -1;
}
else if(pid > 0)
{
//使用信号的好处就在于,wait是同步的,信号是异步的
//信号本来就是用于异步通信
signal(SIGCHLD,SIGCHILD_handler_func);
while (1)
{
sleep(1);
std::cout<<"Hello World"<<std::endl;
/* code */
}
}
else
{
}
return 0;
}