目录
一.信号的概念
在Linux系统中,信号的存在是必然的,信号可以理解为是一个软件中断,在某个条件下,系统会发出某个信号给正在运行的进程,通知进程需要执行某一特定的事件。
二.信号的种类
在终端中输入命令"kill -l"可列出Linux系统中的所有信号,如下:
- 非可靠信号(非实时信号).非可靠信号为1~31信号,信号可能会丢失。
- 可靠信号(实时信号)。可靠信号为34~64信号,信号绝对不会丢失。
三.信号的产生方式
3.1硬件产生的三种方式
- ctrl + c:可以产生SIGINT信号
- ctrl + |:可以产生SIGQUIT信号
- ctrl + z:可以产生SIGTSTP信号
3.2软件产生
- kill命令
- kill -9命令
- kill() ;主要用于向指定的进程或进程组发送信号,它的定义如下:
#include<stdio.h> #include<signal.h> int kill(pid_t pid , int sig);
pid 参数pid为进程号或进程组号
pid = 0,将信号发送到当前进程所在的进程组中的每一个进程
pid = -1,将信号发送给除了init进程外的当前进程中有权发送的所有进程
pid < -1,将信号发送给进程组(-pid)中的每一个进程
若pid为一个有效的进程或进程组,信号将发送给pid代表的进程或进程组
sig 要发送信号的类型编号
若蚕食sig为0,就无信号可发送,但会进行错误检查
- raise() ;主要用于将信号发送给当前进程,该函数原型为:
#include<stdio.h> #nclude<signak.h> int rasie(sig);
参数:sig为发送的信号类型的编号。如果函数调用成功,返回值为0;失败则为非0。
- abort();6号信号,使当前进程接收到信号而异常终止 该函数原型:
#include <stdlib.h> void abort(void); //就像exit函数一样,abort函数总是会成功的,所以没有返回值
- alarm();14号信号,该函数主要用于为发送的信号色丁一个时间警告,是系统在设定的时间之后发送信号,该函数原型:
#include<unistd.h> unsigned int alarm(unsigned int seconds);
参数seconds设定为时间值。如果seconds设置为0,那么alarm()设置的警告始终将无效。alarm()安盼在seconds时间之后,发送一个信号SIGALRM给进程。在默认情况下,进程接收到这个信号会终止运行。
四.信号注册
信号注册前提:
- 在内核当中task_struct结构体中,保存一个struct sigpending的对象pending,struct sigpending这个结构体当中保存了两个元素。第一个元素是:struct list_head list;第二个元素:sigset_t signal;sigset_t类型而是一个结构体类型,struct sigset_t保存了一个unsigned long sig[];在这里sig这个数字是按照比特位来使用的,我们称之为位图。
- 内核当中还维护了一个sigqueue队列,队列当中的每个元素对应信号的一个处理节点。
与信号注册相关的两个点:sig位图,sigqueue队列
catgs . -R
ctrl + ] ==>跳转到光标所在类型的定义地方
ctrl + o==>回到光标上一次所在位置
typedef struct{
usingned long sig[_NSIG_WORDS];
//并不是按照正常的数组下标方式进行访问,他是按照位图方式进行访问的;每一个比特位代表一个信号,但是还是还有预留
//:64 / 32;_NSIG_WORDS是由两个宏相除得来的
//_NSIG / _NSIG_BPW
}sigset_t;
sigquene队列
前提-->同一个信号注册两次
4.1非可靠信号的注册
- 一次:将信号对应的比特位改称1 + 在sigqueue队列当中添加sigqueue结点
- 第二次:自会将型号对应的比特位从1改为1,不会添加sigqueue结点
4.2可靠信号注册
- 第一次:将信号对应的比特位改称1 + 在sigqueue队列当中添加sigqueue结点
- 第二次:还会再sigqueue队列当中添加可靠信号的sigqueue结点
五.信号注销
5.1非可靠信号的注销
在sigqueue队列当中将对应信号的节点进行出队操作
将对应的比特位值为0
5.2可靠信号的注销
将可靠信号对应的sigqueue结点从sigqueue队列当中进行出队操作
判断sigqueue队列当中是否还有当前可靠信号的sigqueue结点。如果没有,则会将sig位图当中可靠信号队形的比特位置为0;如果有,则不会将sig位图当中的可骄傲信号对应的比特为置为0。
六.信号的处理方式
- 默认处理方式:SIGDFL
- 忽略处理方式:SIGING,SIGCHLD(子进程退出时会给父进程发送一个SIGCHLD信号)
- 自定义处理方式:想要完成更改信号的处理方式。在Linux中可以使用signal()与sigaction()对默认的信号处理方法进行修改。
自定义信号处理前提:
- 在task_struct结构体当中,有一个指向sighand_struct的结构体指针,在该结构体当中有一个action数组,数组当中的每一个元素都是一个struct k_digaction结构体,数组当中每一个元素对应一个信号处理逻辑。
- 在struct k_sigaction结构体当中有一个元素是dtruct sigaction sa,在struct sigaction结构体当中有一个sighandler_t类型的元素,这个sighandler_t是一个函数指针类型,typedef void(*sighandler)(int),保存吸纳后默认执行的函数。
操作系统对信号的处理:
当sig位图当中收到某一个信号的时候,意味着sig位图当中的某一个比特位被置为1了,操作系统处理该信号的时候,就会从PCB当中去寻找sighang_struct结构体的指针,从而找到da_handler,进而操作系统内核条用sa_handler保存函数地址,完成信号功能。
6.1signal()
typedef void(*sighandler_t)(int);
sigandler_t signal(int signum,sighandler_t handler);
参数:
-
signum:待自定义处理函数的信号handler:接受一个函数地址,该函数的返回值为void,参数为int,参数的哈桑难以为当接受进程收到莫格信号触发发调用了该函数的时候,会将改信号的值,传递给该函数。
- signal函数是调用sigaction函数
- signal函数是更改函数指针当中保存的函数地址
- sigaction函数是更改struct sigaction结构体的‘
注意:在系统提供的信号类型种,SIGKILL和SIGSTOP信号不能被捕获或者忽略。
6.2sigaction()
int sigaction(int signum,const struct sigaction* act,struct sigaction* oldact);
struct sigaction{
void (*sa_handler)(int);//默认保存信号处理函数地址的函数指针
void (*sa_sigaction)(int, siginfo_t* ,void*);
int sa_flags;//如果说sa_flags的值为SA_SIGINFO,则在处理信号的时候会用sa_sigaction函数指针保存的函数地址来进行处理信号
sigset_t sa_mask;//当正在处理信号的时候,会将注册的信号先放到sa_mask当中进行过度,当处理完成该信号之后,会将刚才注册信号放到sig位图当中
void (*sa_restorer)(void);//预留信息
};
参数:
- signum:待自定义处理函数的信号
- act:想要将喜好更改成为什么处理动作
- oldact:信号处理之前的动作
int sigemptyset(sigset_t* set);
//会将位图当中的比特位全部清空为0
int sigfillset(sigset* set);
//会将位图当中的比特位全部清空为1
注意:如果两个ligaction结构体类型指针act和oldact都指向空,则连个指针参数不会实现上述功能。
总结:
- signal函数是调用sigcation函数
- signal函数是更改函数指针中保存的函数地址
- sigaction函数是更改struct sigaction结构体的
七.信号的捕捉
从前面信号中有3种对型号的处理方法,一种是系统对信号的默认处理方法,一种是忽略信号,还有一种是捕捉信号。
- 执行流从内核态切换到用户态之前一定会调用do_signal函数处理信号
- 从用户态切换到内核态的时候,是是调用了系统调用函数,或者进程异常。
- 在进行系统调用or程序员调用库函数且库函数底层调用了系统调用or程序出现异常(访问空指针,内存访问越界,double free),此时会进入内核态。
八.信号的阻塞
信号的阻塞指的是准备处理信号的时候,会判断当前信号是否为阻塞,如果该信号为阻塞,则暂时不去处理信号。
信号的阻塞并不是说信号不能被注册,不会影响更改限号pending位图和增加sigqueue节点
操作系统处理信号的逻辑:
- 当程序从用户态切换到内核态之后,处理do_signal函数的时候,发现收到某个信号,想要处理这个信号之前,先判断block位图当中对应信号的bit位是否为1
- block当中对应bit位为1:则不处理该信号,sigqueue当中对应的信号节点还在;block当中对应bit位为0,则处理该信号
8.1设置接口阻塞
int sigprocmask(int how,const sigset_t* set,sigset *oldset);
参数:
- how:告诉sigprocmask函数以什么方式进行工作,以下是三个方式;
SIG_BLOCK | 设置某个信号为阻塞状态 block(new) = blick(old) | set |
SIG_UNBLOCK | 解除信号为阻塞状态 block(new) = blick(old) & (~set) |
SIG_SETMASK | 替换原来的block位图 block(new) = set |
- oldset:在没有更改之前老的block位图
8.2验证可靠信号和非可靠信号
结论:
- 信号的阻塞不会干扰信号的注册的
- 可靠信号在收到多次,会处理多次,而非可靠信号收到多次只处理一次。非可靠信号可能会导致信号丢失。(处理原则:实时信号先处理,非实时信号后处理,处理时按照队列的出队操作进行处理,先进先出策略)
- 9号信号和19号信号是不能被阻塞的
其他知识点:
volatile关键字:保持内存可见性