1. 概念
进程执行信号的处理动作,称为 信号递达(Delivery)
信号从产生到递达之间的状态,称为 信号未决(Pending)
进程可以选择 阻塞(Block)某个信号
过程:
信号产生 —— 信号未决 —— (是否阻塞)—— 信号递达
信号在未决状态,其实就是信号保存在进程PCB内
阻塞和忽略的区别:
忽略是一种信号的处理动作,在信号递达时才会体现;而阻塞,信号根本就不会递达
那么信号是如何保存在进程PCB(task_struct)中的呢?
2. 内核结构
内核中task_struct的会有三个指针指向三个位图
由pending位图决定信号是否产生,被保存;block位图决定信号是否被阻塞,handler位图决定信号被抵达时的处理动作
其中__sighandler_t类型是函数指针,void handler(int ),函数参数为int类型,返回值为void类型,其中SIG_DEF(默认)和SIG_IGN(忽略)是宏定义,分别表示__sighandler_t类型强转的0和1
当信号的默认处理动作为默认时,它会去调用对应的信号默认处理动作
信号处理流程:
3. 函数接口
默认情况下,阻塞block位图为全0,用户可以修改
OS提供自定义类型 sigset_t 和对应的操作该位图的方法,用户需要用这些方法和接口才能修改 sigset_t 类型的位图。用户想要修改block阻塞位图就必须利用 sigset_t 作为系统调用接口的参数。
sigset_t 被称为 信号集;block位图,被称为 阻塞信号集 或者 信号屏蔽字(signal mask)
修改信号集的接口
- sigemptyset:初始化set所指向的信号集,使其中所有信号的对应bit清零,表示该信号集不包含任何有效信号
- 函数sigfillset初始化set所指向的信号集,使其中所有信号的对应bit置位,表示该信号集的有效信号包括系统支持的所有信号
- 初始化sigset_t变量之后就可以在调用sigaddset和sigdelset在该信号集中添加或删除某种有效信号
- sigismember:确定 signum 信号对应的位置是否为1 返回值:真1,否0,出错-1
注意: 在使用sigset_ t类型的变量之前,一定要调用sigemptyset或sigfillset做初始化,使信号集处于确定的状态
读取或更改进程的信号屏蔽字:
set:
若set为空指针,读取进程当前的信号屏蔽字,oldset传出
若set为非空,根据how指示,通过set修改信号屏蔽字,原先的通过oldset(若为非空)传出
how:
如果调用sigprocmask解除了对当前若干个未决信号的阻塞,则在函数返回前,至少将其中一个信号递达(在下一篇博客中解答为什么)
SIG_BLOCK mask = mask | set 添加 SIG_UNBLOCK mask = mask & ~set 解除 SIG_SETMASK mask = mask 覆盖
读取当前进程的未决信号集:
读取当前进程的未决信号集,通过set参数传出。调用成功则返回0,出错则返回-1
通过这些接口做的小实验:
#include <iostream>
#include <signal.h>
#include <unistd.h>
#include <assert.h>
using namespace std;void handler(int sig)
{cout << "捕获signal:" << sig << endl;
}void Print(const sigset_t& set)
{for(int i = 1; i <= 31; ++i){if(sigismember(&set, i) == 1)cout << 1;else cout << 0;}cout << endl;
}int main()
{for(int i = 1; i <= 31; ++i){signal(i, handler);}sigset_t set;sigfillset(&set);sigprocmask(SIG_SETMASK, &set, nullptr);while(true){sigset_t oset;sigemptyset(&oset);sigpending(&oset);Print(oset);sleep(1);}return 0;
}
i=1
id=$(pidof mysignal)
while [ $i -le 31 ]
doif [ $i -eq 9 ];thenlet i++continuefiif [ $i -eq 19 ];thenlet i++continuefikill -$i $idecho "kill -$i $id"let i++sleep 1
done
完。