目录
- 信号是什么
- Linux通过kill -l查看指令
- 信号的产生
- signal系统调用捕捉信号
- 键盘产生信号
- 系统调用产生信号
- 进程异常产生信号
- 软件条件发送信号
- Code Dump
- 信号保存
- 信号抵达
- 信号产生到信号抵达之间的状态叫信号未决。
- 进程可以对信号进行阻塞
- 使用sigprocmask()系统调用接口阻塞block信号集表
- sigpending(sigset_t*)检查pending未决信号集
- 解除信号阻塞
- 信号的处理时机
- 信号的捕捉sigaction
信号是什么
如果有一个由C语言编写的无限循环的程序,该进程会一直占据内存一直运行,这个时候再键盘按下Ctrl 和 C一起按,进程就会退出,程序结束。这个Ctrl 和 C的键盘信息就被操作系统识别成一个信号。在Linux中其实是操作系统给进程发送了kill -2的信号。如果一个整形有除0的操作,进程会直接退出,会显示浮点数出错,对一个空指针的解引用,会有段错误。操作系统会给进程分别发送8和11号信号的错误。
进程实际上没有产生信号的时候,就已经知道了每个信号的处理方式,就好比一个人知道交通的红绿灯规则,即使没有看到交通信号等的信号,但已经知道红灯停,绿灯行的规则。
当信号产生到来的时候,进程不一定知道该信号的到了,所以信号对于进程正在工作是异步的,并不同步。就算是知道了,进程也不一定要对他进行处理,就好比绿灯亮了,有的人也不一定要过马路。所以进程一定暂时保存了这些产生的信号。
Linux通过kill -l查看指令
每种信号对应着不同的数字。
信号的产生
函数指针数组,都有每个信号的处理方法,所以说每个进程在创建的时候就已经知道了每个信号处理的方式。信号发送的本质是操作系统对发送信号的硬件信号进行处理,然后操作系统向进程的一个信号位图表的一个位置由0变1,实际是硬件的信号通过操作系统写到进程的进程控制块的信号位图进行写入。然后进程在合适的时候进行信号的操作。
Linux进程的task_struct中也确实采用了位图这种数据来保存信号,用int表示:
uint32_t signals:
00000000 00000000 00000000 00000000
下标位置对应信号的编码。
signal系统调用捕捉信号
signal系统调用可以对进程的信号进行捕捉,修改方法,但9号和19号信号进行捕捉是无意义的。可以使用signal(2,SIG_DFL)对信号恢复默认的处理方式。
键盘产生信号
一个计算机,只要一个前台进程,和n多个后台进程,前台进程一旦停止就会变成后台进程,即前台的进程是一直运行的。只要前台进程可以接收到我们键盘发送的信号,一旦键盘识别到键盘的组合键,就会产生信号。1到31号信号是普通信号,34到64是实时信号。没有0信号。进程处理信号的方式有操作系统自己默认的方式,或者自己自定义的方式,和进程可以忽略该信号着三种处理方法。
系统调用产生信号
int kill(pid_t pid, int sig);
进程异常产生信号
如果一个整数除以0,或者对一个空指针解引用,进程都会发送异常信号。进程退出
软件条件发送信号
Code Dump
Code Dump位文件的转储,对进程异常时保存进程的上下文,Linux虚拟机一般时打开的,Linux的服务器的code dump一般不打开,默认为0。
code dump保存了异常退出进程的pid。code dump保存了进程异常退出的信息。
信号保存
因为进程不是立即对信号进行处理的,使用进程要对信号进行保存。
信号抵达
当进程在处理信号对应的处理方式的时候,叫做信号的抵达,进程对信号的操作方式可能有忽略signal(int sign,SIG_IGN),默认的处理方式signal(int sign,SIG_DFL),和自定义捕捉的处理方式。
信号产生到信号抵达之间的状态叫信号未决。
信号未决即信号还在进程task_struct的信号位图中保存着。
进程可以对信号进行阻塞
如果信号未决,进程可以对该信号阻塞,暂时信号不进行抵达,直到进程解除阻塞。
对于普通信号的pending未决位图表,只有能一个信号。在linux中有sigset_t类型的数据来表示这两张信号位图表。也叫信号集。
使用sigprocmask()系统调用接口阻塞block信号集表
1.使用sigemptyset(sigset_t* )对一个位图进行初始化。
2.使用sigaddset(sigset_t*,int sign)对位图的某一个位置为1。
3.使用sigprocmask(int how,sigset_t* set,sigset_t* oldset);通过这个函数才把进程task_struct的阻塞信号位图进行写入,这个时候进程的某个信号才被屏蔽。set为要对那一个信号进行屏蔽,而oldset为输出型参数,其实是set改变后对被block进行恢复。
#include <iostream>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
using namespace std;
void handler(int sign)
{
cout << "收到信号:" << sign <<endl;
sleep(1);
exit(0);
}
int main()
{
cout << "pid: " << getpid() << endl;
signal(2,handler);
signal(3,handler);
sigset_t block,oblock;
sigemptyset(&block);//对一个变量位图进行初始化
sigemptyset(&oblock);
sigaddset(&block,2);//把位图对应的位置置为1
sigaddset(&block,3);//把位图对应的位置置为1
sigprocmask(SIG_BLOCK,&block,&oblock);//让进程的block表的对应信号进行屏蔽
//这里是对2号信号的屏蔽,发送2号信号,信号无法抵达,进程不终止
while(true)
{
sleep(1);
}
return 0;
}
9号信号无法被阻塞屏蔽。
sigaddset不是把进程的信号屏蔽,只是把一个局部变量进行修改,sigprocmask才是对阻塞信号集进行写入来屏蔽信号。
sigpending(sigset_t*)检查pending未决信号集
首先我们需要对进程的阻塞的信号集对某几个信号进行屏蔽,我们使用sigpending()获取未决信号集,然后使用sigismember(sig_set* pending,int sign)的每一个未决信号集的位图进行判断。
#include <iostream>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
using namespace std;
void Print(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 block,oldblock;
sigemptyset(&block);
sigemptyset(&oldblock);
for(int i = 0;i < 32;i++)
sigaddset(&block,i);//把位图全置为1
sigprocmask(SIG_BLOCK,&block,&oldblock);//对所有信号进行屏蔽,9or19无法屏蔽
sigset_t pending;//未决信号表
while(true)
{
sigpending(&pending);//获取未决信号表
Print(pending);
sleep(1);
}
return 0;
}
该程序除了9号和19号无法屏蔽。
解除信号阻塞
void handler(int sign)
{
cout << "收到信号:" << sign <<endl;
sleep(1);
}
void Print(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 block,oldblock;
sigemptyset(&block);
sigemptyset(&oldblock);
for(int i = 0;i < 32;i++)
{
sigaddset(&block,i);//把位图全置为1
signal(i,handler);
}
sigprocmask(SIG_BLOCK,&block,&oldblock);//对所有信号进行屏蔽,9or19无法屏蔽
sigset_t pending;//未决信号表
int count = 0;
while(true)
{
sigpending(&pending);//获取未决信号表
Print(pending);
sleep(1);
count++;
if(count == 15)
{
sigprocmask(SIG_SETMASK,&oldblock,NULL);
//使用旧的block表重新设置,把全部的信号屏幕解除
}
}
return 0;
}
结果时先发送2号信号再发送3号,信号屏蔽被解除后,是最近的信号先抵达的,这里使用了自定义捕捉,所以3号信号抵达时并未退出,可以知道进程的信号只有最近一次产生的有效。进程信号抵达时,pending表的比特位由1变0是执行信号对应发放前置位0的。
信号的处理时机
进程在内核态到用户态的时候,进行信号的检测和信号的处理。
用户态:是受到操作系统的控制,访问的资源是有限的。访问32位机器的0到3GB的地址空间。
内核态:操作系统的工作状态,能访问大部分资源。让用户用系统调用接口以操作系统的身份访问系统的资源。
用户的进程的代码和数据可以在进程地址空间进行跳转,进行调用和返回。在linux系统当中,CPU有一个CS寄存器,有两个比特位,二进制01和11,分别表示内核态和用户态。
信号的捕捉sigaction
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);函数来对信号进行屏蔽。
当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字(进行阻塞),当信号处理函数返回时自动恢复原来的信号屏蔽字,这样就保证了在处理某个信号时,如果这种信号再次产生,那么 它会被阻塞到当前处理结束为止。 如果在调用信号处理函数时,除了当前信号被自动屏蔽之外,还希望自动屏蔽另外一些信号,则用sa_mask字段说明这些需要额外屏蔽的信号,当信号处理函数返回时自动恢复原来的信号屏蔽字。
#include <iostream>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
using namespace std;
void Print(sigset_t &pending)
{
for(int i = 31;i > 0;--i)
{
if(sigismember(&pending,i))
{
cout << "1";
}
else
{
cout << "0";
}
}
cout << endl;
}
void handler(int sign)
{
cout << "收到信号:" << sign <<endl;
while(true)
{
sigset_t pending;
sigpending(&pending);
Print(pending);
sleep(1);
}
}
int main()
{
cout << "pid: " << getpid() << endl;
struct sigaction act,oldact;
act.sa_handler = handler;
sigemptyset(&act.sa_mask);
sigaddset(&act.sa_mask,3);
sigaction(2,&act,&oldact);//对2号信号进行屏蔽
while(true)
{
sleep(1);
}
return 0;
}