信号递达(Delivery):实际执行信号的处理动作;
信号未决(Pedning):信号从产生到递达之间的状态;信号未决就是进程收到了一个信号,但是未处理,就是临时保存到了进程PCB中的对应的位图中;
进程可以选择阻塞(block)某个信号;
被阻塞的信号产生时将保持在未决状态,直到进程解决对此信号的阻塞,才执行递达的动作;
注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在抵达之后可选的一种处理动作;
在进程PCB内部有3张表:
其中,pending就是信号未决的位图,进程在收到一个信号后,会将pending表中相应的位置位;
handler是函数指针数组 – 数组下标对应信号的编号,就是对应信号的处理方式;signal自定义捕捉就是将信号对应的方法填入handler表;
也可以设置信号的忽略和默认;IGN是忽略;DFL是默认;
block表是阻塞表,结构和pending一摸一样,代表的含义是对应的信号是否被阻塞;
信号的处理过程:
进程在接受一个信号后,会将pending表中相应的位置位,然后先去block表中查看该进程是否被阻塞,如果被阻塞,就不做任何动作,如果没有阻塞,再去handler表中查询处理方法;
(1)语言会为我们提供.h.hpp和语言的自定义类型;
同时,操作系统也会给我们提供.h和自定义类型;
(2)OS向我们提供了接口,一定要提供相对应的类型;
语言提供了访问系统调用的接口,也一定会提供相对应的类型;
sigset_t类型:
未决和阻塞标志可以使用相同的数据类型(位图),sigset_t称为信号集,这个类型可以表示每个信号的有效或无效状态;
在阻塞信号集中有效和无效的含义是该信号是否被阻塞,阻塞信号集也叫做信号屏蔽字;
而在未决信号集中有效和无效的含义是该信号是否处于未决状态;
注:
sigset_t不允许用户自己进行位操作,OS为我们提供了对应的操作方法;
sigset_t使用者可以直接使用该类型,和用内置类型、自定义类型没有任何差别;
sigset_t一定需要对应的系统接口,来完成对应的功能,其中系统接口需要的参数,可能就包含了sigset_t定义的变量或者对象;
OS提供的对sigset_t操作的接口:
分别是:
全部位清0;
全部位置1;
某个信号置位;
某个信号复位;
判断信号是否存在;
sigpending函数:获取当前调用进程的pending信号集;
set是输出型参数;
成功返回0,失败返回-1;
sigprocmask函数:检查并更改block信号集;
how参数:
set:根据how的不同的宏,有不同的功能;
oldset:输出型参数,返回老的信号屏蔽字,不需要可以传空指针;
#include
#include
#include
using namespace std;
void catchSig(int signum)
{
cout << "获得了一个信号:" << signum << endl;
}
int main()
{
for(int i = 1; i <= 31; i++)
{
signal(i, catchSig);
}
while(true)
{
sleep(1);
}
return 0;
}
运行结果:
可以发现,其他信号都被自定义捕捉了,只有9号信号杀死了该进程,因为9号信号是不能被捕捉的;
#include
#include
#include
#include
using namespace std;
static void showPending(sigset_t &pending)
{
for(int sig = 1; sig <= 31; sig++)
{
if(sigismember(&pending, sig))
{
cout << "1";
}
else
{
cout << "0";
}
}
cout << endl;
}
int main()
{
//1.定义信号集对象
sigset_t bset, obset;
sigset_t pending;
//2.初始化
sigemptyset(&bset);
sigemptyset(&obset);
sigemptyset(&pending);
//3.添加要进行屏蔽的信号
sigaddset(&bset, 2);
//4.设置set到内核中对应的进程内部
int n = sigprocmask(SIG_BLOCK, &bset, &obset);
assert(n == 0);
(void)n;
cout << "block 2号信号成功 " << endl;
//5.重复打印当前进程的pending信号集
while(true)
{
//获取当前进程的pending信号集
sigpending(&pending);
//显示当前进程的pending信号集
showPending(pending);
sleep(1);
}
return 0;
}
运行结果:
当发送了2号信号后,pending表中对应的位置1了,2号信号是被阻塞了,应该一直在pending表中,无法被递达;
在一定时间后恢复2号信号的block
#include
#include
#include
#include
using namespace std;
static void showPending(sigset_t &pending)
{
for(int sig = 1; sig <= 31; sig++)
{
if(sigismember(&pending, sig))
{
cout << "1";
}
else
{
cout << "0";
}
}
cout << endl;
}
int main()
{
//1.定义信号集对象
sigset_t bset, obset;
sigset_t pending;
//2.初始化
sigemptyset(&bset);
sigemptyset(&obset);
sigemptyset(&pending);
//3.添加要进行屏蔽的信号
sigaddset(&bset, 2);
//4.设置set到内核中对应的进程内部
int n = sigprocmask(SIG_BLOCK, &bset, &obset);
assert(n == 0);
(void)n;
cout << "block 2号信号成功 " << endl;
//5.重复打印当前进程的pending信号集
int count = 0;
while(true)
{
//获取当前进程的pending信号集
sigpending(&pending);
//显示当前进程的pending信号集
showPending(pending);
sleep(1);
count++;
if(count == 20)
{
int n = sigprocmask(SIG_SETMASK, &obset, nullptr);//将原来的信号集附上去
assert(n == 0);
(void)n;
cout << "接触对2号信号的block " << endl;
}
}
return 0;
}
运行结果:
结果是没有看到pending表从1变为0;
默认情况下,回复对于2号信号block的时候,确实会进行递达;
但是2号信号的默认处理动作是终止进程,将进程直接终止;
我们需要对2号信号进行捕捉:
#include
#include
#include
#include
using namespace std;
void catchSig(int signum)
{
cout << "获得了一个信号:" << signum << endl;
}
static void showPending(sigset_t &pending)
{
for(int sig = 1; sig <= 31; sig++)
{
if(sigismember(&pending, sig))
{
cout << "1";
}
else
{
cout << "0";
}
}
cout << endl;
}
int main()
{
signal(2, catchSig);
//1.定义信号集对象
sigset_t bset, obset;
sigset_t pending;
//2.初始化
sigemptyset(&bset);
sigemptyset(&obset);
sigemptyset(&pending);
//3.添加要进行屏蔽的信号
sigaddset(&bset, 2);
//4.设置set到内核中对应的进程内部
int n = sigprocmask(SIG_BLOCK, &bset, &obset);
assert(n == 0);
(void)n;
cout << "block 2号信号成功 " << endl;
//5.重复打印当前进程的pending信号集
int count = 0;
while(true)
{
//获取当前进程的pending信号集
sigpending(&pending);
//显示当前进程的pending信号集
showPending(pending);
sleep(1);
count++;
if(count == 20)
{
int n = sigprocmask(SIG_SETMASK, &obset, nullptr);//将原来的信号集附上去
assert(n == 0);
(void)n;
cout << "接触对2号信号的block " << endl;
}
}
return 0;
}
注:
没有一个接口时用来设置pending位图的,这是因为所有信号的发送方式,都是修改pending位图的过程;
#include
#include
#include
#include
using namespace std;
static void showPending(sigset_t &pending)
{
for (int sig = 1; sig <= 31; sig++)
{
if (sigismember(&pending, sig))
{
cout << "1";
}
else
{
cout << "0";
}
}
cout << endl;
}
static void blockSig(int sig)
{
sigset_t bset;
sigemptyset(&bset);
sigaddset(&bset, sig);
int n = sigprocmask(SIG_BLOCK, &bset, nullptr);
assert(n == 0);
(void)n;
}
int main()
{
for(int sig = 1; sig <= 31; sig++)
{
blockSig(sig);
}
sigset_t pending;
while(true)
{
sigpending(&pending);
showPending(pending);
sleep(1);
}
return 0;
}
当发到9号信号的时候,进程停止,9号信号是不能被屏蔽的;
跳过9号信号:
19号也是无法屏蔽
#include
#include
#include
using namespace std;
void handler(int signum)
{
cout << "获取了一个信号:" << signum << endl;
}
int main()
{
//内核数据类型,用户栈定义的
struct sigaction act, oact;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
act.sa_handler = handler;
//设置进当前调用进程的PCB中
sigaction(2, &act, &oact);
while(true) sleep(1);
return 0;
}
#include
#include
#include
using namespace std;
static void showPending(sigset_t &pending)
{
for (int sig = 1; sig <= 31; sig++)
{
if (sigismember(&pending, sig))
{
cout << "1";
}
else
{
cout << "0";
}
}
cout << endl;
}
void handler(int signum)
{
cout << "获取了一个信号:" << signum << endl;
sigset_t pending;
int c = 10;
while(true)
{
sigpending(&pending);
showPending(pending);
c--;
if(!c)
{
break;
}
sleep(1);
}
}
int main()
{
//内核数据类型,用户栈定义的
struct sigaction act, oact;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
act.sa_handler = handler;
//设置进当前调用进程的PCB中
sigaction(2, &act, &oact);
while(true) sleep(1);
return 0;
}
如果需要同时添加对其他信号的屏蔽:
#include
#include
#include
using namespace std;
static void showPending(sigset_t &pending)
{
for (int sig = 1; sig <= 31; sig++)
{
if (sigismember(&pending, sig))
{
cout << "1";
}
else
{
cout << "0";
}
}
cout << endl;
}
void handler(int signum)
{
cout << "获取了一个信号:" << signum << endl;
sigset_t pending;
int c = 10;
while(true)
{
sigpending(&pending);
showPending(pending);
c--;
if(!c)
{
break;
}
sleep(1);
}
}
int main()
{
//内核数据类型,用户栈定义的
struct sigaction act, oact;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
act.sa_handler = handler;
//同时添加对其他信号的屏蔽
sigaddset(&act.sa_mask, 3);
sigaddset(&act.sa_mask, 4);
sigaddset(&act.sa_mask, 5);
sigaddset(&act.sa_mask, 6);
sigaddset(&act.sa_mask, 7);
//设置进当前调用进程的PCB中
sigaction(2, &act, &oact);
while(true) sleep(1);
return 0;
}
运行结果:
在main函数调用insert方法时,信号来了,调用handler,handler也去调用insert,那么像这样被多个执行流调用insert就叫做函数重入;
函数重入出问题的叫做不可重入函数;
不出问题的叫做可重入函数;
函数的可重入性是函数的一种特征,我们目前使用的大多数函数,都是不可重入的;
当接收到2号信号时,将flag置1,进程退出;
运行结果:
如果我们更改编译选项,让g++对代码作出一定的优化:
运行结果:
现在进程就无法退出了,但是flag还是变成了1;
这是因为在优化了代码之后,后面的语句没有更改flag,在后面检测flag的时候,就不访问内存中的flag了,而是检测寄存器edx中的flag;而寄存器中的flag是第一次读取的0,因此进程就不会退出了;
在变量定义的时候加上volatile关键字:
这个关键字的作用是**保持变量在内存中的可见性;**
运行结果:
注:优化是在编译时就完成的;
如果我们需要等待子进程退出,10个子进程5个退出,后面的信号还需要进行wait检测是否退出;
因为5个进程都发送了sigchild信号,但是OS只能收到一个;
这时主进程只能阻塞等待该子进程退出;
我们也可使用vector保存进程pid,来进行非阻塞遍历所有进程,这样不会被阻塞;
也可以在waitpid时候传入-1, 就可以等待任意一个退出的进程,进程也不会被阻塞;