目录
7.2.1.sighandler_t signal(int signum , sighandler_t handler); //自定义信号的调用函数
7.2.2.typedef void (*signhandler_t)(int); //自定义信号
7.2.3.int sigaction(int signum , const struck sigaction *act , struct sigaction *oldact);
1.信号的概念
信号是一个软件中断:只是告诉你有这样一个信号,但具体信号如何传递信息,怎么处理什么时候处理是由进程决定的,所以是软中断。
2.信号的产生
硬件产生:
ctrl+c:2号信号--SIGINT
ctrl+z:20号信号--SIGTSTP
ctrl+|:3号信号--SIGQUIT
软件产生:
kill函数:
int kill(pid_t pid , int sig);
给pid进程发送的sig信号,例如:kill(getpid(),2);
raise函数:
int raise(int sig);
谁调用给谁发送信号
kill命令:
kill -[信号值] pid
3.信号的种类
非可靠信号(非实时信号):1 ~ 31
可靠信号(实时信号):34 ~ 64
总共有62个信号,两类信号的差别就是实时信号不会丢失信号,非实时信号可能会丢失信号。
4.信号的处理方式
1)操作系统对信号的处理方式(man 7 signal)
信号可以分为五种动作term、core、ign、stop、cont
每一个硬件产生的信号都分在这五大类动作之下,有着各自的功能。
默认处理方式:SIG_DFL,操作系统当中已经定义好信号的处理方式了;
忽略处理方式:SIG_IGN,该信号为忽略被处理(例如僵尸进程的形成过程)
在这里结合进程信号的内容,更新一下僵尸进程形成原因的回答模板:
子进程先于父进程退出,子进程在退出的时候,
会告知父进程,父进程接收到消息之后是忽略处理的。会向父进程发送SIGCHLD信号,而SIGCHLD信号的处理方式为SIG_IGN忽略处理!导致了父进程没有回收子进程的退出状态信息,从而致使子进程变成了僵尸进程。
2)自定义处理方式:程序员可以更改信号的处理方式,定义一个函数,当进程收到该信号的时候,调用程序员自己写的函数。
5.信号的注册
基础概念:
一个进程接收到一个信号,这个过程称之为注册;信号的注册和注销并不是一个过程,是两个相互独立的过程。
每一个进程都拥有自己独立的注册位图和sigqueue队列
而信号的注册,就是将信号在位图中对应的比特位改成1,添加sigqueue节点到队列当中去
区分:
非实时信号:
第一次注册:修改sig位图(0->1),添加sigqueue节点
第二次注册:同一个非实时信号,在前一个还没有被处理的情况下,再次注册,会修改sig 位图(1->1),但是不会再添加sigqueue节点
实时信号:
第一次注册:修改sig位图(0->1),添加sigqueue节点
第二次注册:修改sig位图(1->1),并再次添加sigqueue节点
6.信号的注销
非可靠信号:
1)将信号对应的sig位图当中的比特位置0;
2)将对应的信号的sigqueue节点进行出队操作。
可靠信号
1)将对应的信号的sigqueue节点进行出队操作;
2)判断sigqueue队列当中还有没有相同的信号节点:如果有则比特位不置0;反之比特位置0。
7.信号的自定义处理方式
7.1.概念
自定义处理方式就是让程序员(使用者)自己定义某一个信号的处理方式。
7.2.函数
7.2.1.sighandler_t signal(int signum , sighandler_t handler); //自定义信号的调用函数
sighandler_t handler:一个函数指针,接收一个函数,没有返回值,参数类型是int;将信号的处理方式替换成为函数指针所保存的函数地址,对应的函数。
参数:
signum:信号值
handler:更改为哪一个函数处理,接受一个函数地址
7.2.2.typedef void (*signhandler_t)(int); //自定义信号
void sigcallback(int sig) //定义信号
{
...
}
signal(2,sigcallback);
//调用,当执行signal函数的时候,并不是调用sigcallback函数,而是将sigcallback函数的地址保存在函数指针当中
9号信号(强杀信号):是不能被自定义处理的
咱们写一串代码来更改二号终止信号ctrl+c试试看
代码如下:
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<signal.h>
4 void sigcallback(int sig){
5 printf("sigcallback func recv sig:%d\n",sig);
6 }
7 int main()
8 {
9 signal(SIGINT,sigcallback); //SIGINT就是crtl+c给出的中断信号,直接改成咱们的自定义信号
10 while(1) //while(1),sleep(1)进程
11 {
12 printf("i am main func,sleep(1)\n");
13 sleep(1);
14 }
15 return 0;
16 }
运行结果:
果然无法终止while(1)进程,改写成了输出一段小废话了...
7.2.3.int sigaction(int signum , const struck sigaction *act , struct sigaction *oldact);
参数:
signum:信号值
act:将信号的处理方式更改为act //输入型参数
cldact:原来信号的处理方式 //输出型参数
struct sigaction{
void (*sa_handler)(int); //保存信号处理方式(默认)的函数指针
void (*sa_sigaction)(int , siginfo_t * , void *); //(也是保存信号的处理方式的函数指针),但是没有使用。当要使用的时候,配合sa_flags一起使用。当sa_flags的值为SA_SIGINFO的时候,信号按照sa_sigaction保存的函数地址进行处理。
sigset_t sa_mask; //当进程在处理信号的时候,如果还在收到信号。则放倒该信号位图当中,后续放到进程的信号位图当中。
int sa_falgs;
void (*sa_restorer)(void); //保留字段
};
这个函数也是更改自定义函数的一个方法,代码举例:
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<signal.h>
4 struct sigaction oldact;
5 void sigcallback(int sig){
6 printf("sigcallback recv sig num:%d\n",sig);
7 sigaction(2,&oldact,NULL);
8 }
9 int main(){
10 struct sigaction sig;
11 sig.sa_handler = sigcallback;
12 sigaction(2,&sig,&oldact);
13 while(1){
14 printf("i am main func,sleep(1)\n");
15 sleep(1);
16 }
17 return 0;
18 }
结果如下:
第一次调用sigcallback函数时:ctrl+c被4.2.1的函数更改为废话函数,然后在这个废话函数里面又把sigcallback函数本身修改成为中断函数。
8.信号的捕捉
介绍之前咱们先来回顾一下几个问题
1、进程的代码在用户空间还是内核空间?
struct tast_struct { ... };内核空间
2、注册的原理:
操作系统修改tast_struct结构体。
3、程序有可能在用户空间执行,也有可能在内核空间执行
8.1.捕捉的过程
进程进入到内核空间之后才能处理信号。
进程从内核返回到用户态的时候,一定会调用do_signal处理信号
进程没有收到信号,直接返回到用户空间
进程收到信号了就进行默认处理、忽略处理以及自定义处理
8.2.信号的处理时机
当从内核态切换到用户态的时候会调用do_signal函数处理信号
-有,就处理信号
-没有,就直接返回用户态
8.3.处理信号的时候,不同的处理方式
默认、忽略:直接在内核当中就处理结束
自定义处理:调用程序员自己定义的处理函数进行处理
->执行用户自定义的处理函数(用户空间)
->调用sigreturn()再次回到操作系统内核(内核空间)
->再次调用会调用do_signal函数去处理信号
->调用sys_sigreturn函数回到内核空间,继续执行代码
9.信号的阻塞
9.1.要理解的点
信号的注册时信号注册,信号阻塞是信号阻塞。信号的阻塞并不会干扰信号的注册,而是说进程收到这个信号之后,由于阻塞,暂时不处理这个信号。
9.2.再次理解信号的处理时机
加入信号阻塞这个知识点之后,操作系统在处理信号时的处理时机又有了变化:
当从内核态切换到用户态的时候会调用do_signal函数处理信号
-有,就处理信号
判断信号是否被阻塞
-是,暂时不处理
-不是:直接处理
-没有,就直接返回用户态
9.3.接口
int sigprocmask(int how , const sigset_t *set , sigset_t *oldset);
参数:
how:想让sigprocmask做什么事情
SIG_BLOCK:设置某个信号为阻塞状态
当how为这个状态的时候,函数会根据set计算新的阻塞位图,方式为:block(new) = block(old) | set;
SIG_UNBLOCK:设置某个信号为为非阻塞状态
当how为这个状态的时候,函数会根据set计算新的阻塞位图,方式为:block(new) = block(old) & (~set);
SIG_SETMASK:第二个参数“set”,替换原来的阻塞位图。(替换)
当how为这个状态的时候,函数会根据set计算新的阻塞位图,方式为:block(new) = set;
set:新设置的阻塞位图
oldset:原来老的阻塞位图
int sigemptyset(sigset_t *set); //清空位图,将比特位全部置为0
int sigfillset(sigset_t *set); //将比特位全部置为1
int sigaddset(sigset_t *set , int signum); //将某个信号的对应比特位置为1
int sigdelset(sigset_t *set , int signum); //将某个信号的对应比特位置为0
int sigismember(const sigset_t *set , int signum); //判断某个信号的位图位置是否为1
咱们来写一段代码印证阻塞位图的设置,代码如下:
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<signal.h>
4 int main(){
5 sigset_t set;
6 sigemptyset(&set);
7 sigaddset(&set,2);
8 sigprocmask(SIG_BLOCK,&set,NULL);
9 while(1){
10 printf("i am main func,sleep(1)\n");
11 sleep(1);
12 }
13 return 0;
14 }
运行结果:
可以明显看出^C已经被阻塞掉了,暂不处理所以无法让进程停止下来。