赞
踩
信号的概念
产生方式:
其他概念
查看信号
kill -l
一共62个信号(32,33不存在),本文只讨论前32个信号的处理
我们来看这样一个例子
程序在执行for循环时其实已经发生了数组越界的行为,OS也已经向程序发送了信号(后面打印了段错误)但是程序并没有立即终止程序,而是继续向下执行代码打印了"hello",然后才处理信号的。
所以,我们得出一个结论,信号可能会在进程执行的任意时候产生,但是进程并不一定在信号产生时直接去处理信号,所以进程必须得把接收到的信号保存起来。
信号在进程中是以位图的方式来保存的。在进程中有三个位图,分别为appending表,block表,hander表;appending表。每张表共有32位,每一位对应一种信号,appending表对应位置表示该进程是否收到对应信号,block表对应位置表示该进程对该信号是否阻塞(收到信号,但不处理),hander表对应位置表示对该信号的处理方式。
对于一个信号的处理方式一共有三种:
SIG_DFL:默认处理方式
SIG_IGN:忽略该信号
函数指针:自定义处理信号
第一行:进程未收到1号信号,1号信号并未被阻塞,1号信号的处理方式为默认处理方式。
第二行:进程收到2号信号,2号信号被阻塞,所以2号信号暂时不被处理(未决),一但解除阻塞,2号信号的处理方式为忽略。
第三行:进程未收到3号信号,2号信号未被阻塞,3号信号的处理方式为自定义。
我们知道了信号在进程中的存储方式以及处理方式,那我们就可以尝试着去控制信号的阻塞状态以及信号的处理方式。
信号的阻塞状态表就是进程中的block表,我们可以创建一个同类型的表,然后通过系统调用函数将我们创建的表赋值到进程中的表,然后就可以控制进程信号的阻塞状态了。
block的类型为"sigset_t"
该类型不是内置类型,不可以直接进行操作,所以要通过函数来进行。
#include <signal.h>
int sigemptyset(sigset_t *set);
//将set每个位置都置0
int sigfillset(sigset_t *set);
//将set的每个位置都置1
int sigaddset (sigset_t *set, int signo);
//将signo号信号所对应的位置置1
int sigdelset(sigset_t *set, int signo);
//将signo号信号所对应的位置置0
int sigismember(const sigset_t *set, int signo)
//判断signo号信号在set中所对应的位置是否为1
上面这些函数只是对我们自己创建的表进行修改,并未对进程中实际的block表做任何改动!
现在我们有了自定义的表,就可以使用这张表来修改进程内的block表了。
修改block表使用sigprocmask函数
第一个参数:要修改block表的方式
假设原表为mask
SIG_BLOCK:在原表的基础上加上我们希望阻塞的信号,相当于mask|set
SIG_UNBLOCK:去除我们希望解除的信号,相当于mask&~set
SIG_SETMASK:将原表设置为set,相当于mask=set
第二个参数:
我们已经设置好的新表
第三个参数:
输出型参数,可以将进程中原始表带出,不关心则设置为NULL;
上面是设置信号的阻塞状态,下面接受一个函数,可以获取当前进程的appending表,即获取当前进程收到的信号
参数:输出型参数,获取当前进程的appending表。
下面是一个实例
#include <stdio.h> #include <unistd.h> #include <signal.h> void show(sigset_t* set){ int i = 1; for(; i < 32; i++){ if(sigismember(set, i)){//如果收到对应信号,输出1,否则输出0 printf("1"); } else{ printf("0"); } } printf("\n"); } int main(){ sigset_t set,oset; sigemptyset(&set);//先将set所有位置置0 sigaddset(&set, 2);//将2号信号对应位置置1 sigprocmask(SIG_BLOCK, &set, &oset);//设置进程内block表,当前进程2号信号被阻塞 while(1){ sigpending(&set);//获取当前进程appending表 show(&set);//输出appending表 sleep(1); } return 0; }
该进程阻塞了2号信号(ctrl + c就是给进程发送的2号信号),所以在该进程执行过程中,发送2号信号进程不会退出。我们通过循环打印appending表来显示当前进程收到的信号。
信号捕捉是的就是进程产生信号时,自定义信号处理方式。
自定义信号处理方式,使用signal()函数。
第一个参数:要自定义处理方法的信号
第二个参数:函数指针,收到信号时执行该函数。
#include <unistd.h> #include <signal.h> void show(sigset_t* set){ int i = 1; for(; i < 32; i++){ if(sigismember(set, i)){//如果收到对应信号,输出1,否则输出0 printf("1"); } else{ printf("0"); } } printf("\n"); } void hander(int sig){ printf("get a sig:%d\n",sig); } int main(){ sigset_t set,oset; sigemptyset(&set);//先将set所有位置置0 sigaddset(&set, 2);//将2号信号对应位置置1 sigprocmask(SIG_BLOCK, &set, &oset);//设置进程内block表,当前进程2号信号被阻塞 signal(2, hander);//自定义2号信号处理方式 int count = 0; while(count < 20){ sigpending(&set);//获取当前进程appending表 show(&set);//输出appending表 sleep(1); if(count == 10){ sigprocmask(SIG_SETMASK, &oset, NULL);//10秒之后,2号信号不在被阻塞,处理2号信号 } count++; } return 0; }
当前进程开始时就自定义了2号信号的处理方式,且阻塞了二号信号,进程收到2号信号后不会做出任何处理,10秒时候取消2号信号的阻塞,进程处理2号信号,由与2号信号已经被自定义处理方式,所以程序依旧不会退出,直到循环结束正常退出。
前面提到过,信号产生后进程不一定立即处理(即使信号未被阻塞),那进程到底是什么时候处理信号呢?其实进程在执行过程中分为两种状态:用户态与内核态,用户态可以执行用户级别的代码,当进程出现异常或中断,或者使用系统调用函数时就会从用户态切换到内核态。内核态执行内核级代码,执行完毕后在返回到用户态,每当从内核态切换到用户态时系统会检测当前进程的信号表,对信号做出处理。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。