Linux 信号相关知识

什么是Linux中的信号

信号实际上是软件中断, 信号的存在提供了一种处理异步事件的方法。
给进程发信号 实际上是给PDB的信号字段的 对应信号的二进制位置 1;
是让进程终止 。
信号可以让进程暂停。
信号是有有限种类的 有编号的。

本质上为什么kill -9 (进程id)命令 杀了一个进程 9号信号对进程的默认处理方式
进程即使在没有收到对应信号之前,也有默认的对每一个信号的处理方法。

一个熟悉的场景

当我们在shell中运行一个程序时,可以使用Ctrl + C来终止进程,这实际上是向正在执行的进程发送了一个SIGINT信号,当这个进程收到了SIGINT信号以后,进程从用户态进入到内核态,要从内核态返回到用户态继续执行进程没有跑完的代码时,首先要处理该进程PCB中的信号,而SIGINT信号的默认处理方式时终止进程,这时候进程被终止了而不再返回用户态去执行未被执行的代码。

注意:
在这个场景中 被Ctrl + C终止的进程是前台进程(该操作只能终止前台进程),一个shell可以同时运行一个前台进程和若干个后台进程, 一个命令后面加一个 & 就可以将该操作放到后台运行,这样shell就可以继续接受并处理其他命令。因为不论进程代码执行到什么地方都有可能收到SIGINT信号使进程终止,所以信号相对于进程的控制流程来说是异步。

kill -l

kill -l 命令可以用来查看所有的信号种类和编号。

这里写图片描述

每个信号在singal.h文件中都有自己的编号和宏定义名称,编号1 — 31是普通信号 34 以上是实时信号 本文只讨论普通信号。 man 7 singal 可以查看各个信号的产生原因和默认处理方式。

信号的产生

1、 终端按下某些键时,终端驱动程序会自动发送信号给前台进程。按下ctrl C 操作系统向前台进程发送了一个信号 2号信号 ctrl \ 3号信号 生成core dump 文件 向进程发信号实际山是对PCB中信号字段对应的那一位 信号编号相同的置1.

/.test & 把test可执行程序变成一个后台进程 并在屏幕上打印出后台进程的 id
每个进程的PCB 有 ulimit -a 要查找到的属性 其中有 open file ——fd_array[] 的大小
stack size 栈的大小 ulimit 命令可以更改bash进程的属性 bash上 运行的进程 是bash的子进程 gdb (exe) core.xxxx 直接显示出错位置
ctrl + Z 把一个前台进程 变成后台进程 fg 把一个后台进程放在前台进程

2、在代码中出现除零错误 CPU运算单元发生异常后,内核将这个异常解释为SIGFPE信号发送给进程。对指向非法内存段的指针解引用,内存映射单元MMU把这个异常解释为SIGSEGV信号发送给进程。

3、kill 命令产生的信号发送给某个进程 进程调用kill函数可以发送信号给某个指定的进程。kill函数未制定发送给某个进程的信号时默认发送SIGTERM信号,进程对该信号的默认操作是终止进程。

4、由软件条件产生的信号, 比如使用管道进行进程间通信时如果管道读端已经被关闭这时写端再写数据内核就会产生 SIFPIPE信号发送给写数据的进程。闹钟超时产生SIGALAM信号。

信号的处理方式

信号处理方式:

1.   忽略
2.   系用默认
3.   信号的捕捉    自定义处理方式   
Core Dump

当一个进程异常终止时, 可以把其用户地址空间的内存数据全部保存到磁
盘上。磁盘上的这个文件叫core 。这就是 Core Dump ,事后可以通过调试该文件方便的找到进程代码中的Bug。进程默认是不产生core文件的,可以通过更改进程PCB中的Resource limit 信息更改允许产生core文件的大小。ulimit -c 1024 命令可以更改 core文件的大小 最大(1024k)。

发信号的函数

在命令行使用kill命令 或者 进程内调用kill函数给指定进程发指定信
raise()函数给自己进程发信号。

  #include <sys/types.h>
  #include <signal.h>
  int kill(pid_t pid, int sig);
  int  raise(int  signal);

代码:
https://github.com/xym97/Linux/blob/master/Sig/myKill.c

成功返回0 失败返回-1.
abort函数让进程收到信号而常终止。

#include <stdlib.h>

void abort(void);

函数不会失败 没有返回值。
alarm()函数

#include <unistd.h>
unsigned int alarm(unsigned int seconds);

代码:
https://github.com/xym97/Linux/blob/master/Sig/alarm.c
该函数意为设置一个时钟, seconds秒后给当前进程发送SIGALARM信号,该信号的默认处理操作是终止进程。

信号相关概念

抵达:进程收到信号后(进程PCB表示信号字段的对应二进制位置1)对信号的处理动作叫抵达。
未决:进程收到了信号但未对其做处理。
阻塞:当进程阻塞了某个信号时,进程收到的信号会保持在未决状态不会对其处理,直到进程取消阻塞该信号,才会对该信号处理。
忽略:信号抵达后,可选的一种对信号的处理动作。

操作系统内核对信号的表示

这里写图片描述

进程收到信号实际上是对进程PCB中Pending表中表示收到信号的那一位置1(抵达后清零),之后再与block表中对应的那一位比较,如果block对应位置没有被置1,则表示该信号没有被阻塞。然后在hander表中找到对应的位置储存的函数指针,该函数指针指向的函数就是对收到的信号的处理动作。

普通信号在抵达之前产生多次之记一次, 实时信号在抵达之前产生多次存放在一个队列中。

sigset_t

sigset_t类型表示Pending 和 Blocking 两个位图中的那些为是0 , 那些位是1
这个类型也称为信号集。sigset_t类型用来存储阻塞和未决信号集。

信号集操作函数
 #include <signal.h>

 int sigemptyset(sigset_t *set);

 int sigfillset(sigset_t *set);

 int sigaddset(sigset_t *set, int signum);

 int sigdelset(sigset_t *set, int signum);

 int sigismember(const sigset_t *set, int signum);

sigemptyset()函数和sigfillset()函数用来把set指针指向的保存所有类型信号在Pending表或Block表中是否有效(被置1)的sigset_t类型(信号集)所有信号对应的bit位清零或置1。

sigaddset()和sigdelset()函数在set指定的信号集中加上或删除signum号信号。

注意:在使用sigset_t类型之前,必须调用sigempty或sigfillset函数将信号集处于确定状态,再调用sigaddset()和sigdelset()函数在信号集中添加或删除某个有效信号。

以上介绍的函数成功返回0,出错返回-1.
sigismember()函数是个bool类型的函数,查看set指向的信号集中signum信号是否是有效的,是返回1,不是返回0,失败返回-1。

sigprocmask()函数用于读取或更改进程的信号屏蔽字。

#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

oldset是非空指针的时候,保存获取的当前信号屏蔽字。
set是非空指针的时候,和how结合更改信号屏蔽字。

how是SIG_BLOCK mask|set
how是SIG_UNBLOCK mask &~ set
how是SIG_SETBLOCK时 mask=set

sigpending()函数获取当前进程的未决信号集。

#include <signal.h>
int sigpending(sigset_t *set);

通过set参数传出 成功返回0, 失败返回-1。

代码:
https://github.com/xym97/Linux/blob/master/Sig/block.c

捕捉信号

这里写图片描述

如图所示捕捉信号的流程 ,注意执行Myhander函数自定义处理信号时Func()是阻塞的 这两个函数是两个执行流不存在调用和被调用的关系

1、signal()函数

#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

函数第一个参数是要捕捉的信号,第二个参数是对捕捉信号的自定义处理方法。
返回值是这个处理的函数指针。

2、 sigsction()函数

#include <signal.h>
int sigaction(int signum, const struct sigaction *act,
                     struct sigaction *oldact);

第一个参数是要捕捉的信号,第二个参数是对捕捉信号的自定义处理函数,第三个参数是为了保存之前的处理函数。

当某个信号正在被处理时,内核自带屏蔽该信号,直到处理动作被完成,内核自动撤销对该信号的屏蔽。

3、 pause()函数  

#include<unistd.h>
int pause(void);

除非有信号抵达 否则就让进程挂起  
只有出错的返回值-1,errno的值为EINTR。 在信号被捕捉时回到该函数的栈帧时返回-1.如果收到信号进程退出则没有机会返回。

竞态条件与sigsuspendind()函数

这里写图片描述

https://github.com/xym97/Linux/blob/master/Sig/mysleep.c

#include <signal.h>
int sigsuspend(const sigset_t *mask);

该函数把解除信号屏蔽的挂起等待信号作为一不不可分割的原子操作执行。
通过mask屏蔽字暂时解除某个对应的信号的屏蔽(在该函数内mask是信号屏蔽字)。
使用适当时间屏蔽信号的方法与sigsuspend结合。

可重入与不可重入

一次调用还没有结束就再次进入该函数成为重入,这样做不会造成混乱叫可重入。

符合以下条件之一则不可重入。
调用malloc/free   因为malloc是用全局双向链表来管理堆空间的。
标准I/0库函数很多都是不可重入的全局数据结构。

SIGCHLD信号

子进程结束时默认向父进程发送SIGCHLD信号,父进程对该信号的默认处理方式是忽略该信号。
父进程对子进程的处理可以不必阻塞式wait也不必轮询,可以捕捉SIGCHLD信号
在信号处理函数中wait清理子进程。 
https://github.com/xym97/Linux/blob/master/Sig/sigchld.c

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值