Linux:带你理解信号的生命周期


信号是什么?

信号就是一个软件中断:通知进程发生了某个事件,打断进程当前的操作,去处理这个事件。

信号是多种多样的,并且一个信号对应一个事件,这样才能做到收到一个信号后,知道到底是一个是什么事件,应该如何处理。(但是要保证必须识别这个信号)

信号种类:

一共62种(使用kill -l命令进行查看)

  • 1~31是非可靠信号 (Mac终端只能看到这31个)
  • 34~64是可靠信号
    在这里插入图片描述
信号的生命周期

产生->进程中的注册->进程中的注销->信号的捕捉处理

信号的产生

硬件:

  • ctrl+c 强制中断程序的执行(进程已经终止)
  • ctrl+l 发送一个exit信号(从管理员root退回到你的普通用户)
  • ctrl+z 任务中止(暂停的意思)

软件:

kill -signum 

pid命令:kill默认发送15号信号 kill -signum pid 向指定的进程发出指定信号

kil杀死一个进程的原理:

向进程发送一个信号,信号有对应的时间,进程放下手头工作去处理这个事件,然而事件的处理结果就是让进程退出

通过终端按键产生信号
  • SIGINT的默认处理动作是终止进程
  • SIGQUIT的默认处理动作是终止进程并且Core Dump
core dumped:核心转储--程序异常退出时,保存程序的运行信息,便于事后调试—默认关闭

ulimit -c 设置核心转储文件的最大大小 ulimit -c1024[kb] 则最大大小为1M

gdb ./main -> core-file core.pid -> 事后的命令调试
调用系统函数向进程发信号

首先在后台执行死循环程序,然后用kill命令给它发SIGSEGV信号(Mac发送SEGV)。

在这里插入图片描述

  • ./signal 后面为什么要加 &?

& 的作用是将进程切换到后台,我们把它称为job。切换到后台时会输出相关job信息,以前面的输出为[1]27753例:[1]表示jobID是1,27753表示进程ID是27753。切换到后台的进程,仍然可以用ps命令查看。

  • 指定发送某种信号的kill命令可以有多种写法,上面的命令还可以写成 kill -SIGSEGV 4568 或 kill -11 4568 , 11是信号SIGSEGV的编号。以往遇到的段错误都是由非法内存访问产生的,而这个程序本身没错, 给它发SIGSEGV也能产生段错误。

kill命令是调用kill函数实现的。kill函数可以给一个指定的进程发送指定的信号。raise函数可以给当前进程发送指定的信号(自己给自己发信号)。

#include <signal.h>
int kill(pid_t pid, int signo); 
int raise(int signo); 
这两个函数都是成功返回0,错误返回-1

abort函数使当前进程接收到信号而异常终止。

#include <stdlib.h>
void abort(void); 
就像exit函数一样,abort函数总是会成功的,所以没有返回值。
由软件条件产生信号
#include <unistd.h>
unsigned int alarm(unsigned int seconds); 
调用alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程发SIGALRM信号, 该信号的默认处理动作是终止当前进程。
硬件异常产生信号

硬件异常被硬件以某种方式被硬件检测到并通知内核,然后内核向当前进程发送适当的信号。

例如:

  • 当前进程执行了除以0的指令,CPU的运算单元会产生异常,内核将这个异常解释为SIGFPE信号发送给进程。
  • 当前进程访问了非法内存地址,MMU会产生异常,内核将这个异常解释为SIGSEGV信号发送给进程。

信号在程序中的注册

如何让进程知道自己收到了某个信号?

pcb->struct sigpending-> struct sigset_t
  • sigset_t 这个结构体中只有一个数组成员,这个数组实现了一个位图 — 称之为未决信号集合(收到了但是没有处理的信号集合)
  • 给一个进程发送一个信号,就会将这个位图对应位置1,表示进程当前收到了这个信号

在pcd中有一个未决的信号集合,pending集合,信号的注册就是指在这个pending集合中标记对应信号数值的二进制位为1

  • 位图只有0/1,也就是表示是否收到了这个信号,但是无法表示收到多少个这样的信号
  • 信号的注册不仅会修改位图,还会为信号组织一个sigqueue节点添加到pcb的sigqueue链表中

是否可靠信号注册的区别:

  • 1~31非可靠信号的注册:

    • 若信号还未注册(位图为0),则会创建一个sigqueue节点并修改位图为1

    • 若已经注册(位图为1),则什么都不做

  • 34~64可靠信号的注册:

    • 每次注册信号,不管是否已经注册,每次都会添加一个sigqueue节点(信号信息),添加到链表中,并修改位图

信号的注销

为了保证一个信号只会被处理一次,因此是先注销再处理;在pcb中删除当前信号信息

将pending位图置0,删除信号节点

  • 若信号是非可靠信号(只有一个节点),因此删除节点后,直接将pending位图置0(非可靠信号在没有处理之前只会注册一次)
  • 若信号是可靠信号(可能注册多次,有多个节点),则删除后,需要判断是否还有相同节点,没有的话才会重置位图为0;

信号的捕捉处理

信号的处理

其实每一个信号都对应有自己的事件处理函数,信号到来,去处理这个事件就是去执行这个处理函数;执行完毕事件就处理完了

信号的捕捉处理:

信号的递达,当信号到来的时候去回调信号的处理函数

  • 默认处理:操作系统中原定义好的每个信号的处理方式
  • 忽略处理:处理方式就是忽略,什么都不做
  • 自定义处理:自己定义一个事件函数,使用这个函数替换内核中默认的处理函数(信号到来就会调用我们定义的函数)
自定义信号的捕捉流程:

信号的处理是在程序运行从内核态切换回用户态之前,默认/忽略直接在内核中完成处理,而用户自定义信号处理方式,则需要返回用户态执行回调函数,完成后返回内核态,最终没有信号处理了,再返回程序主控程序

  • 当程序在用户态主控流程运行的时候,因为系统调用/中断/异常(切换到内核态运行)
  • 完成功能后,在返回用户态主控流程之前,调用do_signal函数去处理未决信号
    • 若信号的处理方式是忽略/默认,则直接在内核态完成
    • 若信号的处理方式是用户自定义,会切换到用户态执行用户自定义的处理函数,执行完毕后
  • 调用 sigreturn 返回内核态运行,当没有信号待处理的时候,则返回用户态主控流程

在这里插入图片描述

信号捕捉函数signal
sighandler_t signal(int signum, sighandler_t handler) - 修改信号的回调函数

参数:

  • typedef void (*sighandler_t)(int signum); 定义了一个名称叫sighandler_t的函数指针类型
  • handler:SIG_DFL(默认处理方式),SIG_IGN(忽略处理方式)/ 用户自己定义的一个没有返回值,有一个int型参数的函数地址

sigaction函数相比signal函数更为复杂,但更具灵活性,下面具体介绍她的结构和用法:

#include <signal.h>

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

参数:

  • signum:要操作的信号。
  • act:要设置的对信号的新处理方式。
  • oldact:原来对信号的处理方式。 如果不需要设置

返回值:0 表示成功,-1 表示有错误发生。


如果本文有帮助到您,留个赞呐~~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值