Linux之进程信号

1. 信号概念

信号是进程之前事件异步通知的一种方式,属于软中断。

2. 信号的种类

2.1 信号个数

在Linux操作系统中,用kill -l命令可以查看系统定义的信号列表,查看结果如下:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20201025091829794.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2FhYWFhdWFhbg==,size_16,color_FFFFFF,t_70#pic_center

在Linux操作系统中,总共有62个信号,其中1-31号信号有可能丢失,叫==非可靠信号==;32、33,信号不存在;34-64号信号属于可靠信号,进程收到多少可靠信号就处理多少次。

2.2 信号是如何产生的

信号的产生分为硬件产生和软件产生。

  • 硬件产生
    例如,Ctrl+c,产生SIGINT,也就是2号信号,为中断信号。
    Ctrl+z,产生SIGSTP,终止信号,一般慎用。
    Ctrl+|,产生SIGQUIT,退出信号。
  • 软件产生
    例如kill函数,可以给任意进程发送,只要知道进程的pid即可。raise函数,其实是封装的kill函数,只能给自己知道的进程发送信号。这两个函数都是成功返回0,错误返回-1。
#include <signal.h>
int kill(pid_t pid, int signo);
int raise(int signo);

3. 信号的注册

3.1 信号注册示意图:

在这里插入图片描述

3.2 非可靠信号的注册

  • 第一次注册一个信号
  1. 更改sig位图当中信号对应的比特位,将该比特位置位1。
  2. 给sigqueue队列中添加对应信号的节点。
  • 第二次注册相同的信号(前提是之前的信号没有被处理)
  1. 更改sig位图,想让对应的比特位置为1。
  2. 如果发现sig位图当中之前比特位为1,则不再添加sigqueue节点。

3.3 可靠信号的注册

  • 第一次注册一个信号
  1. 更改sig位图当中信号对应的比特位,将该比特位置位1。
  2. 给sigqueue队列中添加对应信号的节点。
  • 第二次注册相同的信号(前提之前的信号没有被处理)
  1. 更改sig的位图,想让对应的节点的比特位置位1。
  2. 直接在添加对应信号的节点到sigqueue的对列当中,直白点说就是有多少可靠信号来注册就添加多少sigqueue节点。

4. 信号的注销

  • 非可靠信号的注销
  1. 在sigqueue队列中将对应信号的节点进行出队操作。
  2. 将对应的比特位置位0。
  • 可靠信号的注销
  1. 在sigqueue队列中将对应信号的节点进行出队操作。
  2. 需要判断sigqueue节点当中是否存在相同的节点。如果存在,则sig位图当中对应的比特位保持为1,如果不存在,则将对应的比特位置位0。

5. 信号的捕捉流程

流程图:

在这里插入图片描述
假设代码如下:

int main()
{
	signal(2, sigcallback);
	while(1)
	{
		sleep(1);
	}
}

其中执行上述代码的流程为:

  1. 当收到ctrl+c,也就是2号信号的时候。
  2. 当程序执行sleep函数的时候,从用户态切换到内核态,执行内核的代码。
  3. 执行完sleep函数的逻辑后,需要调用一个函数do_signal函数,处理程序所收到的信号;当程序没有收到2号信号时,直接调用sysreturn函数返回用户态;当程序收到了2号信号,切换到用户态去执行用户自定义的函数。
  4. 执行完毕后,调用sigreturn函数切换回内核态,再次调用do_signal函数,重复3逻辑,知道程序收到的信号被处理完。
  5. 调用sysreturn函数返回用户态继续执行程序代码。

信号的阻塞

前提:在task_struct结构体当中保存了一个block位图。
在这里插入图片描述
注意:信号的阻塞并不是说信号不能被注册,不会影响信号更改pending位图和增加sigqueue节点。

操作系统处理信号的逻辑:
当程序从用户态切换到内核态之后,处理do_signal函数的时候,发现收到某个信号,想要处理这个信号之前,先判读block位图当中对应信号的bit位是否为1;当block当中对应bit位为1时,则不处理该信号,sigqueue当中对应的信号的节点还是存在的。当block当中对应的bit位为0时,则处理该信号。其中更改该bit位的函数如下:

int sigprocmask(int how, const sigset_t * set, sigset_t *oldset);

功能:更改sigset_t位图当中的bit位的值。
参数howh中的3个宏常量:
SIG_BLOCK:设置某个信号位阻塞状态,用修改达到目的,方法:block(new)= block (old) | set
SIG_UNBLOCK:设置某个信号位非阻塞状态,方法:block(new)= block(old)&(~set)
SIG_SETMASK:设置新的阻塞的sigset_t位图,方法:block(new)= set
其中set是要设置的新的阻塞位图;oldset是之前程序当中阻塞的位图,是出参的。
例子如下:
在这里插入图片描述
几点说明:

  • 信号阻塞的时候,必不会干扰信号的注册;
  • 同时受到多个同样的非可靠信号只会添加一次sigqueue节点,也就是说只会处理一次。
  • 同时受到多个可靠信号,会添加多次sigqueue节点,每一个可靠信号都会被处理。

6. 自定义信号的处理方式

  • 信号的处理方式
  1. SIG_DEF:默认处理方式。
  2. SIG_IGN:忽略处理。

SIGCHILD信号就是默认处理的方式,子进程在退出的时候,会给父进程发送一个SIGCHLD信号,而父进程对SIGCHILD信号的处理方式为忽略。
僵尸进程:子进程退出的时候,给父进程发送一个SIGCHLD信号,但是操作系统对SIGCHLD信号的处理方式为忽略处理,而导致父进程不去处理信号,从而子进程变成了僵尸进程。
  3. 自定义处理–程序员自己定义处理的函数
typedef void(*sighandler_t)(int);
定义自定义处理函数,其中void表示没有返回值。
int参数值的是哪一个信号触发操作系统调用该函数。

sighandler_t signal(int signum, sighander_t handler);
signum:需要更改自定义处理函数的信号。
handler:接受一个函数的地址,将信号的处理函数更改为什么函数。

9号信号是不能被定义信号处理方式的。

int sigaction(int signum, const struct sigaction *act, struct sigaction * oldact)

自定义信号的处理流程:

  1. 在task_struct结构体中,有一个指向sighand_struct的结构体指针,在该结构体指针中有一个action的数组,数组当中每一个元素都是struct k_sigaction结构体,数组中每一个元素对应一个信号的处理逻辑。
  2. 在struct k_sigaction结构体中有一个元素是struct sigaction sa,在struct sigaction结构体当中有一个sighandler_t类型的元素,这个sighandler_t是一个函数指针类型,typedef void(*sighandler)(int),保存信号默认执行的函数。

操作系统默认对信号的处理:
当sig位图中收到一个信号的时候,意味着sig位图当中的某一个比特位被置位1,系统处理该信号的时候,就会从PCB当中寻找sighang_struct这个结构体的指针,从而找到sa_handler,进而操作系统内核去调用sa_handler保存的函数地址,完成信号功能。
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值