进程信号
信号概念
- 信号就是一个软件中断,通知进程发生了某个事件,打断进程当前的操作,去处理这个事件。
- 信号是多种多样的,并且一个信号对应一个事件,这样才能做到收到一个信号后,知道到底是一个什么样的事件,应该如何处理(但是要保证必须识别这个信号)
信号种类
kill -l 可以查看所有的信号。
一共62种信号,1~31 是非可靠信号,34~64是可靠信号。
信号的生命周期
整个流程可以分为 :产生,进程中的注册,进程中的注销,信号的捕捉处理
信号的产生
硬件: ctrl+c ctrl+l ctrl+z
软件: kill命令 kill -signum pid
kill函数 int kill(pid_t pid,int sig); 给pid发送一个sig信号。
int raise(int sig); 给当前调用的进程或线程发送一个sig信号。
abort()没有参数,直接给调用进程发送一个SIGABRT信号,表示程序出现异常,程序退出。
alarm(int seconds) seconds秒后产生时钟信号 。
core dumpe: 核心转储 - 程序异常退出时,保存程序的运行信息,便于事后调试。----默认关闭。
信号在进程中注册
在pcb中有一个未决的信号集合,pending集合, 信号的注册就是指在这个pending集合中标记对应信号数值的二进制为1,
- 1~31非可靠信号的注册: 若信号还未注册,则注册一下,添加一个sigqueue节点,若已经注册过了,则什么都不做。
- 34~64可靠信号的注册: 每次注册信号,不管是否已经注册,每次都会添加一个sigqueue节点(信号信息)
信号的注销
删除要处理的信号sigqueue节点,
- 若信号是非可靠信号,则直接把位图置0(非可靠信号在没有处理之前只会注册一次)
-若是可靠信号,则删除后,需要判断是否还有相同节点,没有的话才会重置位图为0,
信号的捕捉处理
默认处理 SIG_DFL 默认处理方式
就是用系统原本定义好的默认处理方式
忽略处理 SIG_IGN 忽略处理
就是接受到这种信号时,什么也不做,忽略掉
自定义处理
就是用自己定义的函数来替换原本的默认处理方式.
函数接口:
sighandler_t signal(int signum,sighanler_t handler);
修改信号的回调函数.
signum 为信号,
handler 为回调函数
sigaction(int signum,struct sigaction *new ,struct sigaction *old);
struct sigaction{
void (*sa_handler) (int signum);
void (*sa_sigaction)(int signum,siginfo_t**siginfo,void * con);
int sa_flag;
sigset_t sa_mask;
}
sa_flag0 使用 (*sa_handler)(int sugnum);回调
sa_falgSA_SIGINFO 则使用sa_sigaction回调函数
修改信号的整个处理动作,就时把sigaction结构体换为new,把原来的保存在old
下面来看实际代码
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<signal.h>
void sigcb(int signum){
printf("recv signum :%d\n",signum);
}
int main(){
//signal 修改信号的处理方式.
//SIG_IGN 忽略处理 SIG_DFL 默认处理方式
// signal(SIGINT,SIG_IGN); //忽略处理
signal(SIGINT,sigcb);
signal(SIGQUIT,sigcb);
while(1){
printf("hello\n");
sleep(1);
}
return 0;
}
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<signal.h>
void sigcb(int signum){
printf("recv signum:%d\n",signum);
}
int main(){
struct sigaction newact;
struct sigaction oldact;
newact.sa_handler=sigcb;
newact.sa_flag=0;
sigemptyset(&newact.sa_mask);
//sigaction修改信号的处理动作为newact,原来的动作使用oldact保存
sigaction(SIGINT,&newact,&oldact);
while(1){
printf("hello\n");
sleep(1);
}
return 0;
}
自定义信号的捕捉流程:
信号的处理是在程序运行从内核态切换回用户态之前,默认/忽略直接在内核中完成处理,而用户态自定义信号处理方式,则需要返回用户态执行回调函数,完成后返回内核态,最终没有没有信号处理了,再返回程序主流程.
也就是在返回用户态主控流程之前,调用do_signal函数处理未决信号.然后调用sigreturn 返回内核运行,当没有信号等待处理的时候,则返回用户态主控流程.
信号的阻塞
阻止一个信号的抵达,信号依然可以注册,只是暂时不处理
在pcb中一个block位图(信号阻塞集合),凡是添加到这个集合中的信号,都表示需要阻塞,暂时不处理.
int sigprocmask(int how,sigset_t *set,sigset_t *old);
当前要对block集合进行的操作.
SIG_BLOCK 将set集合中的信号添加到block进程阻塞信号集合中, block =block | set
表示阻塞set集合中的信号以及原有的阻塞信号,并且将原有的阻塞信号返回到old集合中.(便于还原);
SIG_UNBLOCK 将set集合中的信号从block集合中移除,将set集合中的信号解除阻塞。
block=block&(~set)
SIG_SETMASK直接将block集合中的信号修改为set集合中的信号,
block=set
接下来我们写个实例
1.先修改指定信号的处理方式SIGINT SIGRTMIN+5 为了当信号到来能够体现到信号的处理。
2.定义一个信号集作为将要阻塞的信号集合 sigset_t set
3.将所有信号都添加到set集合中
4.sigprocmask(SIG_BLOCK,&set,&old);将所有的信号都阻塞起来
5.getchar() 等待一个回车,否则程序就卡在这里。—按下回车则会解除信号的阻塞。
6.sigprocmask(SIG_UNBLOCK,&set,NULL);对set集合的信号解除阻塞
体会信号的阻塞与解除阻塞函数操作;
体会可靠信号与非可靠信号之间的区别;、
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<signal.h>
void sigcb(int signum){
printf("recv a signal:%d\n",signum);
}
int main(){
signal(SIGINT,sigcb);
signal(40,sigcb);
//阻塞所有信号,
sigset_t set,old;
sigemptyset(&set); //清空信号集合
sigemptyset(&old);
sigfillset(&set); //将所有的信号都添加到set集合
//sigaddset(int signum,sigset_t *set) 将指定信号添加到集合。
sigprocmask(SIG_BLOCK,&set,&old);//阻塞所有信号。
printf("press enter to continue\n");
getchar();//在按下回车之前,程序卡在这里
//按下回车后,然后在解除阻塞
sigprocmask(SIG_UNBLOCK,&set,NULL);//解除阻塞。
return 0;
}
在所有的信号中,有两个信号不可被阻塞,不能被自定义修改处理方式,也不能被忽略,这两个信号SIGKILL 9号信号
SIGSTOP 19信号不能不能阻塞。