信号的概念
信号是一个软件中断
信号的产生
硬件产生
ctrl + c:SIGINT(2)
ctrl + z:SIGTSTP(20)
ctrl + |:SIGQUIT(3)
操作系统对信号的处理动作:man 7 signal 查看
term:终止
ign:忽略
core:终止并产生coredump文件
stop:停止
cont:继续运行
如果产生下面的错误可以去看看这个博客,下载后就可以解决了
https://blog.csdn.net/qq_28779503/article/details/54893745
信号具体的信息:信号的名称+信号的值(整数)+action+描述
软件产生
kill命令:
kill [pid] 可以终止一个进程
kill -[num] [pid] 给进程号为 pid 的进程发送一个信号值为 num 的信号
kill函数:
int kill(pid_t pid, int sig);
功能:给 pid 进程发送 sig 信号
需要包下面的头文件:
#include <signal.h>
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <signal.h>
4
5 int main()
6 {
7 printf("-----begin-----\n");
8 kill(getpid(), 9);
9 printf("------end------\n");
10 return 0;
11 }
raise函数:
int raise(int sig);
功能:谁调用给谁发送 sig 信号
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <signal.h>
4
5 int main()
6 {
7 printf("-----begin-----\n");
8 raise(2);
9 kill(getpid(), 9);
10 printf("------end------\n");
11 return 0;
12 }
说明此时在 raise 就结束掉了,并没有走到 kill
信号的种类:
目前linux的信号数量为62个,分为两种类型
1、非实时信号(非可靠信号) 1~31
特点:有可能信号会丢失
2、实时信号(可靠信号) 34~63
特点:信号不会丢失
信号的注册
注册:一个进程收到一个信号的过程称为注册
信号的注册和信号的注销并不是一个过程,是两个独立的过程
信号的注册分为两种情况:非可靠信号的注册和可靠信号的注册
1、在操作系统内核“struct task_struct”结构体内部有一个变量:
“struct sigpending pengding”
2、内核定义的结构体“struct sigpending”当中有两个变量
一个是内核定义的双向链表
第二个是:sigset_t signal
3、内核定义的类型“sigset_t”为一个结构体,在结构体内部有一个变量,该变量为一个数组(无符号长整型数组)
unsigned long sig[_NSIG_WORDS];
信号的注册本质上就是在使用sig数组,但是并不是按照数组类型的方式在使用,而是按照位图(比特位)的方式在使用:
eg:某个信号注册,则将某个信号队友的比特位置为1
sig数组的比特位远远大于62,剩下的比特位为保留位
struct sigpending ⇒ sigset_t signal ⇒ sig[xxx]
一般在信号注册的时候,称之为操作sig位图,即sig[xxx]
内核当中对于注册的时候,还有一个sigqueue队列,信号的注册逻辑为将信号队友的sig位图当中的比特位置为1,并且在sigqueue队列当中
添加一个sigqueue节点
通过注册第一个信号两次,来区分可靠信号和非可靠信号的注册逻辑
非可靠信号的注册
第一次:
1、更改信号对应在sig位图当中的比特位(0 --> 1)
2、在sigqueue队列当中添加sigqueue节点
第二次:
1、更改信号对应的sig位图当中的比特位(1 --> 1)
2、对于第二次的信号,不添加sigqueue节点到sigqueue队列当中
如果有多次同一个信号来注册,对于非可靠信号而言,只会添加一次sigqueue节点,换言之,只注册了一次
可靠信号的注册
第一次:
1、更改信号对应在sig位图当中的比特位(0 --> 1)
2、在sigqueue队列当中添加sigqueue节点
第二次:
1、更改信号对应的sig位图当中的比特位(1 --> 1)
2、再次在sigqueue队列当中添加sigqueue节点
如果有多次同一个信号来注册,对于可靠信号而言,会添加多次sigqueue节点,即注册多次
信号的注销
非可靠信号的注销
1、将信号对应到sig位图当中的比特位置为0
2、将对应的非可靠信号的sigqueue节点进行出队操作
可靠信号的注销
1、先将可靠信号对应的sigqueue节点进行出队操作
2、判断sigqueue队列当中是否有同类的可靠信号的sigqueue节点
如果有:不会将sig位图当中对于的比特位置为0
如果没有:将sig位图当中对于的比特位置为0
信号的处理方式
默认处理方式
在操作系统内核当中已经定义好了
宏:SIG_DFL
忽略处理方式
操作系统定义进程收到某个信号后忽略掉(进程即使收到了某个信号,进程也不会做任何事情)
宏:SIG_IGN
僵尸进程:子进程先于父进程退出,子进程会向父进程发送SIGCHLD信号,父进程对SIGCHLD信号是忽略处理的,所以父进程并不会做任何事情,导致子进程的资源没有进程进行回收,从而子进程变成僵尸进程
自定义处理方式
程序员可以定义某个信号的处理方式
函数
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
signum:待要更改的信号的值
handler:函数指针,接收一个函数的地址,这个函数没有返回值,有一个int类型的参数
自定义signum这个信号的处理方式,定义为handler这个函数指针保存的函数地址对应的函数
换句话说,当进程收到signum这个信号的时候,就会调用handler当中保存的函数
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <signal.h>
4
5 void sigcallback(int signo)
6 {
7 printf("i am sigcallback, signo is %d\n", signo);
8 }
9
10 int main()
11 {
12 signal(SIGINT, sigcallback);
13
14 while(1)
15 {
16 printf("i am main\n");
17 sleep(1);
18 }
19 return 0;
20 }
signal函数向内核注册了一个信号的处理函数,调用signal函数的时候,并没有调用注册的函数(注册的函数在进程收到信号之后才调用),
这种方式被称为“回调”
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
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;
//当进程在处理某一个信号时,有可能还会收到其他的信号,此时其他的信号暂时存放在sa_mask中
int sa_flags;
void (*sa_restorer)(void);//保留字段
};
signum:待要自定义处理的信号
act:要将信号处理方式更改为act
oldact:原来的处理方式
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <signal.h>
4
5 void sigcallback(int signo)
6 {
7 printf("i am sigcallback, signo is %d\n", signo);
8 }
9
10 int main()
11 {
12 struct sigaction sa;
13 sa.sa_handler = sigcallback;
14 //int sigemptyset(sigset_t *set);
15 sigemptyset(&sa.sa_mask);
16 sa.sa_flags = 0;//默认就等于0
17
18 sigaction(2, &sa, NULL);
19
20 while(1)
21 {
22 printf("i am main\n");
23 sleep(1);
24 }
25
26 return 0;
27 }
信号的捕捉流程
信号注册在进程的PCB当中,是操作系统和PCB打交道
捕捉流程指的是信号什么时候进行处理
信号的处理是在内核态完成的
1、从内核态切换回用户态的时候,一定会调用do_signal函数,来处理进程收到的信号
①sig位图当中有信号注册,则执行信号注销的逻辑
②sig位图当中没有信号注册,则直接返回用户态
2、处理信号(信号注销)
①默认处理方式(直接在操作系统内核的代码中就完成了)
②忽略处理方式(直接在操作系统内核的代码中就完成了)
③自定义处理方式(调用程序员自己定义的代码)
3、自定义处理方式
①在用户态执行程序员自己定义的函数
②调用sigreturn函数再次回到操作系统内核
③再次调用do_signal函数判断是否有信号注册,如果有,返回到处理信号
④调用sys_sigreturn函数回到用户态继续去执行代码
信号的阻塞
信号的阻塞,不会影响信号的注册
接口:
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
how:想让sigprocmask函数做什么
SIG_BLOCK:设置某个信号为阻塞状态
SIG_UNBLOCK:设置信号为非阻塞状态
SIG_SETMASK:设置新的block位图
set:使用set去设置block位图
SIG_BLOCK:
block(new) = block(old)| set
eg:block(old):0101 0000
set:0000 1000
==》 0101 1000
SIG_UNBLOCK:
block(new) = block(old)& (~set)
eg:block(old):0101 0000
&
~set:1011 1111
==》 0001 0000
SIG_SETMASK:
block(new) = set