在Linux操作系统中通过kill -l
命令可查看所有的信号:
信号是从1号开始的的,从信号1到信号31是普通信号,从信号35到信号64称之为实时信号,一般不考虑实时信号。
信号是Linux系统提供的一种向指定进程发送特定事件的一种方式,系统在收到信号时会做识别和处理。
信号产生是异步的:信号的产生和目标进程的运行是两条线,信号可以在程序的任意时刻产生,并且会打断当前正在执行的代码,转而执行信号处理函数。这种异步性质使得信号处理在编程中需要特别注意,因为信号可能会随时打断程序的正常执行流程。
这三种方案只能选择一种,统一称之为信息好处的方式
对信号的捕捉,捕捉一次,后续一直有效:
#include<iostream>
#include<signal.h>
#include<unistd.h>
void hander(int sig)
{
std::cout<<"get a sig: "<<sig<<std::endl;
}
int main()
{
signal(2,hander);
while(1)
{
std::cout<<"hello gwj,pid: "<<getpid()<<std::endl;
sleep(1);
}
return 0;
}
进程有自己的PCB,是一个结构体,在结构体中有很多的成员变量,信号是给进程发送的,信号在进程中是用位图保存收到的信号的。
变量signals
:
uint32_t signals;
0000 0000 0000 0000 0000 0000 0000 0000
假设当前需要发送1号信号,只需要将0000 0000 0000 0000 0000 0000 0000 0000
变成0000 0000 0000 0000 0000 0000 0000 0001
。如此一来,就可以将所有普通信号保存起来。
发送信号:修改指定进程PCB中的信号的指定位图,简单来说其实就是写信号。
PCB是内核数据结构,只有操作系统可以修改内核结构对象中的值。
ctrl+c
、ctrl+\
#include<iostream>
#include<signal.h>
#include<unistd.h>
#include<sys/types.h>
int main(int argc,char *argv[])
{
if(argc!=3)
{
std::cerr<<"Usage: "<<argv[0]<<" signum pid"<<std::endl;
return 1;
}
pid_t pid=std::stoi(argv[2]);
int signum=std::stoi(argv[1]);
kill(pid,signum);
return 0;
}
系统调用还有一个产生信号的函数raise
:
#include<iostream>
#include<signal.h>
#include<unistd.h>
#include<sys/types.h>
void hander(int sig)
{
std::cout<<"get a sig: "<<sig<<std::endl;
}
int main()
{
int cnt=0;
signal(3,hander);
while(true)
{
sleep(2);
raise(3);
}
}
上述代码使用raise
,是的程序每隔2秒向自己发送一个信号。
使用abort
系统调用:
#include<iostream>
#include<cstdlib>
#include<signal.h>
#include<unistd.h>
#include<sys/types.h>
void hander(int sig)
{
std::cout<<"get a sig: "<<sig<<std::endl;
}
int main()
{
int cnt=0;
signal(SIGABRT,hander);
while(true)
{
sleep(2);
abort();
}
}
abort
自定义捕捉后,但是依然会终止程序。
如果把所有信号都捕捉了,会出现什么现象: 无论哪一个信号都无法终止程序,为了避免这种情况,系统中9号信号不允洗自定义捕捉
真正发送信号的是操作系统,只有操作系统可以发送信号。
SIGPIPE(13号信号)
,就会直接终止目标进程。
上面是管道的只是,现在要介绍的是alarm
函数:
调用alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程发SIGALRM
信号, 该信号的默认处理动作是终止当前进程。
#include<iostream>
#include<cstdlib>
#include<signal.h>
#include<unistd.h>
#include<sys/types.h>
void hander(int sig)
{
std::cout<<"sig: "<<sig<<std::endl;
exit(1);
}
int main()
{
int cnt=1;
signal(SIGALRM,hander);
alarm(1); //设定1秒后的闹钟 1s后会收到SIGALARM
while(1)
{
std::cout<<"cnt: "<<cnt<<std::endl;
cnt++;
}
return 0;
}
注意:
alarm(0)
表示取消闹钟,它的返回值表示上一个闹钟的剩余时间。
闹钟设置好后,默认只会触发一次。
int main()
{
while(1)
{
std::cout<<"hello gwj,pid: "<<getpid()<<std::endl;
int a=10;
a/=0;
}
}
int main()
{
while(1)
{
std::cout<<"hello gwj,pid: "<<getpid()<<std::endl;
int *p=nullptr;
*p=100;
}
}
上述程序直接崩溃,那么程序为什么会崩溃?
程序非法访问导致操作系统给进行发送信号,由于收到信号,程序会退出。野指针对应发送的信号时SIGSEGV
,除0对应的信号为SIGFPE
。
除0错误:在计算机的CPU中,有一个eflag寄存器,这个寄存器中有一个溢出标记位,当10
和0
进行除法运算时,在计算机中其实相当于做了多次加法运算,此时溢出标记位标记为1,表示溢出,此时CPU内部报错。操作西永是软硬件资源的管理者,操作系统要随时处理这种操作,操作系统就是向目标进程发送信号。
寄存器只有一套,但是寄存器里面的数据是属于每一个进程的。
野指针错误:当前进程访问了非法内存地址,MMU会产生异常,内核将这个异常解释为SIGSEGV信号发送给进程。
总结: 终止进程实际上是释放进程的上下文数据,包括溢出标志数据或者其他异常数据。
core文件:当一个进程出现了异常,其实进程还在,但是他会帮我们形成一个debug文件,core文件里面存的是进程退出的时候的进程镜像数据,称之为核心转储。
通过ulimit -a
我们可以查看当前用户的资源限制情况:
修改core
大小为10240,命令:ulimit -c 10240
此时我们运行上述除0的程序,程序退出细节不一样,并且形成一个新的文件
为什么云服务器要关闭核心转储:
Term是异常终止