理解进程信号
OS向进程传递信号,进程会根据不同的信号,做出不同的反映。
linux输入kill -l可以查看所有信号类型:
产生方式
- 通过终端按键产生信号(ctrl+c)
- 调用系统函数向进程发信号(int kill(pid_t pid, int signo))
- 由软件条件产生信号(管道没有读端,alarm函数)
- 硬件异常产生信号(除0发生溢出,硬件向操作系统传递消息,操作系统传递信号)
但所有信号都是OS向进程传递产生的。
捕捉信号
其实所有信号的处理操作都是一个**void handler(int)**类型的函数,我们可以通过signal函数修改对应信号的操作
第一个参数是对应的信号的编号,第二个参数是一个函数指针,就是修改后的处理信号操作
int count=0;
void handler(int s)
{
std::cout<<"signal is "<<s<<" count is "<<count<<std::endl;
}
int main()
{
signal(SIGALRM,handler);
alarm(1);
while(true)
{
count++;
sleep(20);
alarm(1);
}
return 0;
}
这个程序就会一直循环打印count的值
三个概念和三张表
- 实际执行信号的处理动作称为信号递达(Delivery)。
- 信号从产生到递达之间的状态,称为信号未决(Pending)。
- 进程可以选择阻塞 (Block )某个信号。
上述我们处理信号的函数的调用就是信号递达,而所有递达的函数都是存储在一个函数指针数组当中,一个 void ( [ ])(int)* 数组中,这就是第一张表。
而我们可以操作系统向进程传递信号,信号被进程接收并存储在另一张表中,这个表的信号表示需要处理的信号,这个表中的信号处于未决状态。
当信号处于未决状态时,表示需要有递达动作,但这时进程可以选择不处理这个信号,这个信号就会被阻塞而一直处于未决状态,而判断是否需要阻塞,那就需要最后一张表,这个表里标注需要被阻塞的信号编号。
操作
每个信号有一个bit位决定一个信号,其中block和pending都是位图结构,linux专门自定义了个sigset_t类型的数据在用户层面表示这两张表,我们可以通过sigset_t类型数据的修改,在将修改后的传入系统调用函数来修改内核的block表,pending表不需要修改,因为OS传递信号就是在修改pending表。
#include <signal.h>
int sigemptyset(sigset_t *set);//置空
int sigfillset(sigset_t *set);//填满
int sigaddset (sigset_t *set, int signo);//设置一个信号
int sigdelset(sigset_t *set, int signo);//取消一个
int sigismember(const sigset_t *set, int signo);//检测某个信号是否被设置
这些函数都是用户层面上的操作
下面看一个系统层面操作的函数(设置block)
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
//how 是设置的方式
//set 是输入的表,是输入型参数
//oldset 是对原始表的备份,是输出型参数
在看一个函数
int sigpending(sigset_t *set);//获取pending表内容
验证一下
void printpendingmap(sigset_t* pendingmap)
{
for(int i =31;i>=1;i--)
{
if(sigismember(pendingmap,i))std::cout<<1;
else std::cout<<0;
}
std::cout<<std::endl;
}
int main()
{
sigset_t newset,oldset;
sigemptyset(&newset);
sigaddset(&newset,2);
sigprocmask(SIG_SETMASK,&newset,&oldset);
while(true)
{
sigset_t pendingmap;
sigpending(&pendingmap);
printpendingmap(&pendingmap);
sleep(1);
}
return 0;
}
但并不是所有信号都可以阻塞,比如kill 9信号就不行。
信号捕捉
当某一个信号正处于递达状态时,则相同的信号会阻塞,即相同的信号不会递达而一直处于未决状态。
而其它信号在默认状态下不会阻塞,我们可以通过下述函数设置要阻塞的信号
#include <signal.h>
int sigaction(int signo, const struct sigaction *act, struct sigaction *oact);
//signo是处理的信号编号
//act是输入型参数,是要处理的内容
//oact是输出型的参数,是备份
struct sigaction {
void (*sa_handler)(int);//信号对应的函数
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;//设置当前信号调度时要阻塞的信号,调度完后会移除
int sa_flags;
void (*sa_restorer)(void);
};
这个函数和signal函数类似,但它功能更强大。