目录
一.信号与硬件异常
二.阻塞信号和信号结构
(一).信号状态
(二).信号结构
①pending表
②block表
③handler表
(三).信号处理过程
(四).阻塞与忽略的区别
(五).不可阻塞和自定义的信号
三.信号函数
①sigpending
②sigprocmask
③sigemptyset
④sigfillset
⑤sigaddset
⑥sigdelset
⑦sigismember
⑧使用示例
一.信号与硬件异常
硬件产生异常并使程序崩溃的方式有很多,比如除0错误、空指针、数组越界等。
大致上,当硬件产生异常后,会“汇报”给操作系统,再由操作系统发送相关信号给进程,促使进程异常退出。
以除0错误为例,当cpu检测到除0后,其中状态寄存器会标记异常,操作系统识别到该寄存器标记了异常后,发送信号给当前进程,进而进程崩溃退出。
再比如空指针问题,当虚拟地址为空时,页表通过MMU(硬件)映射时会出错,MMU内部寄存器会标记异常,操作系统识别后发送信号给进程,进程崩溃退出。
二.阻塞信号和信号结构
(一).信号状态
信号在发送过程中有三种状态:未决、递达、阻塞。
状态 | 含义 |
---|---|
信号未决 | 信号产生后,进程接收但未执行具体方法 |
信号递达 | 执行信号方法的状态或正在处理信号 |
信号阻塞 | 进程收到信号,但不允许递达,即不允许处理信号 |
如果某信号处于阻塞态,那么该信号将无法调用对应的信号函数。
(二).信号结构
首先,我们需要知道的是,信号在操作系统中以位图的形式记录,普通信号是1 - 31,正好可以使用位图对照,0代表进程此时没收到该信号,1代表收到该信号。
实际上,linux的信号有三个信号表构成,即pending表、block表、handler表。
这三个表属于进程PCB结构体,记录在task_struct中。
①pending表
该表本质上是一个位图结构,记录进程是否收到该位对应的信号。0代表没有收到,1代表收到。
②block表
该表也是位图结构,每一位记载该信号是否阻塞,0代表未阻塞,1代表阻塞。
③handler表
该表是函数指针数组,数组每一个下标对应一个信号,每个下标对应的指针指向该信号的处理函数。
SIG_IGN表示处理方式为忽略。
SIG_DFL表示处理方式为默认。
比如2号信号使用默认函数可以表示为:
signal(SIGINT, SIG_IGN);
(三).信号处理过程
(四).阻塞与忽略的区别
阻塞是进程接收信号后,“故意”不执行方法。
忽略是进行接收信号后,执行了方法,但方法是忽略。
本质上,阻塞是将block表该位置1;忽略的该位为0,在handler表中记载的是SIG_IGN。
(五).不可阻塞和自定义的信号
9号信号SIGKILL、18号信号SIGCONT、19号信号SIGSTOP不可被阻塞和自定义。
换句话说,即便是手动添加阻塞信号或自定义处理方法也是无效的,依旧执行默认的方法。
三.信号函数
①sigpending
用于获取当前进程的pending表。
操作系统中定义了sigset_t类型的数据,按位图的形式保存信号,即信号集。
第一个参数是输出型参数,通过该函数获得当前进程的pending位图。
返回值0代表获取成功,-1代表获取失败。
②sigprocmask
用于改变阻塞信号
第一个参数有三个可选项:
选项 | 含义 | 操作 |
---|---|---|
SIG_BLOCK | set中所有置1的都设置成阻塞 | block = block | set |
SIG_UNBLOCK | set中所有置1的都设置成非阻塞 | block = block & ~set |
SIG_SETMASK | 进程block位图表与set表一致 | block = set |
第二个参数是输入型参数,用于确定哪些位阻塞/解除阻塞
第三个参数是输出型参数,获取旧的block位图表。
返回值含义与sigpending一致。
③sigemptyset
用于将信号集全部置0
参数为输出型参数,set内各位将置为0。
返回值含义与sigpending一致。
④sigfillset
用于将信号集全部置1
参数为输出型参数,set内各位将置为1。
返回值含义与sigpending一致。
⑤sigaddset
用于将信号集某一位设置为1
第一个参数为输出型参数,set中指定位将置1。
第二个参数为指定的信号值。
返回值含义与sigpending一致。
⑥sigdelset
用于将信号集某一位设置为0
参数和返回值含义与sigdelset相同,唯一不同就是该位设置为0。
⑦sigismember
用于确定信号集特定位的数值
第一个参数为输入型参数,输入的待查看的信号集。
第二个参数为指定的信号值
返回值1代表该位为1,0代表该位为0,-1代表错误。
⑧使用示例
int main(){
sigset_t block, oldBlock;
//将1 - 31号信号全部阻塞
for(int i = 1; i <= 31; i++){
sigaddset(&block, i);
}
int n = sigprocmask(SIG_BLOCK, &block, &oldBlock);
assert(n == 0);
//循环打印当前进程的pending位图
while(true){
sleep(1);
sigset_t set;
sigpending(&set);//获取pending位图
//循环判断1 - 31位是0还是1并打印
for(int i = 1; i <= 32; i++){
if(sigismember(&set, i)) cout << "1";
else cout << "0";
}
cout << endl;
}
return 0;
}
Software is like sex: it's better when it's free——Linus Torvalds
如有错误,敬请斧正