之前我们按下Ctrl+C会直接终止一个进程,其实是个当前的前台进程发送了一个SIGINT信号
一、信号的基本概念
信号的几种产生方式:
1.键盘键入,用户在终端按下某些键时,终端程序会自动发送信号给前台进程。例如Ctrl+C产生SIGINT信号,Ctrl+\产生SIGQUIT信号,Ctrl+Z产生SIGTSTP 信号(将该进程放在后台执行)
2.硬件异常,程序执行过程中触发某些异常,由硬件检测到并通知给内核,然后内核向进程发送相应的信号。例如,当前正在执行一个除以0的指令,cpu的运算单元会产生异常,内核将这个异常解释为SIGFPE发送给进程,再比如对空指针进行解引用时,(访问非法地址),MMU会产生异常,内核将这个异常解释为SIGSEGV信号发送给进程。
3.调用kill函数,可以发送信号给另外一个进程。常用的函数有,kill()给某个进程发送信号;raise()给自己发送某个信号;abort()给自己发送SIGABRT信号(assert()断言)。
4.软件条件产生,例如闹钟超时产生SIGALARM信号,向所有读端关闭的管道中写数据时产生SIGPIP信号。
常见的几种信号处理方式:
1.忽略此信号
2.执行该信号的默认动作(大多数信号的默认动作为终止该进程)
3.自定义,用户提供一个信号处理函数,即在收到信号时切换到用户态执行这个处理函数,这种方式称为捕捉一个信号
二、产生信号
1.键盘键入
SIGINT的默认处理动作为终止进程,而SIGQUIT的默认处理动作是终止进程并Core Dump(核心转储)生成一个Core.xxxx,这里的文件后缀为进程的pid,即把进程的用户空间内存的数据全部保存到磁盘上,也可以用在gdb中调试中查看错误原因。
因为默认的Core dump文件大小为0,这里需要先改一些参数
这里的信息可以看出,默认的core文件的大小为 0, 一个进程中所能申请的文件描述符的个数默认为1024个,栈的大小为8M,这些数值都是可以更改的。
#include <stdio.h> #include <unistd.h> int main() { printf("pid is :%d\n",getpid()); while(1); return 0; }
2.调用系统函数向进程发信号
#include <stdio.h> #include <unistd.h> #include <stdlib.h> int main() { abort(); return 0; }
3.由硬件异常产生信号
#include <stdio.h> #include <signal.h> int main() { int a=2; a=a/0; while(1); return 0; }
4.由软件条件产生
这里介绍alarm()函数SIGALRM信号
unisigned int alarm(unisigned int seconds); 调用该函数,即设定一个闹钟,也就是告诉内核在seconds秒后向进程发送SIGALRM信号(终止进程)
#include <stdio.h> #include <unistd.h> #include <stdlib.h> int main() { int i=0; alarm(1); for(i=0;1;i++) { printf("%d\n",i); } abort(); return 0; }//这个程序就是在1秒之内数数,闹钟到了就被SIGALRM信号终止
小小一面:解引用空指针会有什么后果?
每个执行中的进程都会有自己的虚拟地址空间,在访问变量时,需要将逻辑地址经页表映射转化为为物理地址,在对空指针进行解引用时,页表映射过程中,MMU在进行虚拟地址到物理地址的转化中会识别出这个一个非法地址则产生硬件异常,内核将这个异常解释为SIGSEGV信号发送给进程,也就是我们看到的程序崩溃。
三、阻塞进程
关于信号的其他相关概念:
1.信号在内核中的表示
2.一些基本概念
实际执行信号的处理动作(已经处理了)称为 信号递答
信号从产生到递答的状态 称为 信号未决
当进程收到一个信号就会将未决信号集中那一位设置为1,若没有来的及处理该信号,就会将阻塞信号集中该信号对应的那一位设置为1,当该信号不再阻塞时,就会将阻塞信号集中该信号对应的那一位置0,当信号处理完成时,就会将未决信号集中那一位置0。还有一种情况是,当该信号正在阻塞时,又收到一个该信号,常规信号是在信号处理之前只计一次,位图的每一位只表示是否产生该信号,不记录信号的个数(实时信号不同)。
3.信号操作函数
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <signal.h> void MyHandler(int sig) { printf("sig :%d\n",sig); } void PrintSigSet(sigset_t *set) { int i=0; for(i=1;i<32;i++) { int ret=sigismember(set,i); printf("%d",ret); } printf("\n"); } int main() { //int sigprocmask(int how,const sigset_t *set,sigset_t *oset); //这里的how由三个选项: //SIG_BLOCK,添加一个信号到信号屏蔽字中 //SIG_UNBLOCK,解除一个信号从信号屏蔽字中 //SIG_SETMASK,将信号屏蔽字设置为set所指的指 //oset不为空时,即将之前的信号屏蔽字备份,之后还原要用到 //int sigpending(sigset_t *set) //获取信号未决集,通过参数set返回 //1.捕捉SIGINT信号 signal(SIGINT,MyHandler); //2.将SIGINT信号屏蔽掉 sigset_t set; sigset_t oset; sigemptyset(&set);//初始化 sigaddset(&set,2);//将信号SIGINT进行设置 sigprocmask(SIG_BLOCK,&set,&oset);//将信号屏蔽字更改 //3.循环读取未决信号集 int n=0; while(1) { n++; if(n%4==0) { printf("解除信号屏蔽字\n"); sigprocmask(SIG_SETMASK,&oset,NULL); sleep(1); printf("再次设置信号屏蔽字\n"); sigprocmask(SIG_BLOCK,&set,&oset);//将信号屏蔽字更改 } sigset_t pending; sigpending(&pending); sleep(1); PrintSigSet(&pending); } return 0; }
信号的捕捉过程
注意这里的 执行sighandler()时是不同的执行流,与main()函数是没有相互调用过程的。
四、可重入函数