进程信号导图
信号概念
什么是信号?
信号是进程之间事件异步通知的一种方式,属于软中断。本质上是OS发送给进程,通知进程某些事已经发生,进程(程序员)可以选择适当的时候进行处理。
有哪些信号?
kill命令
查看信号列表:kill -l
本篇文章只讨论普通信号(1~31),名字中带有RT字母的都是实时信号(34~64)
注意,列表中没有32号和33号信号
向进程发送信号:kill -信号值 进程pid 或者 kill 宏(SIGINT) 进程pid
信号的具体含义
查看信号的动作以及描述:man 7 signal
Action字段:Term(终止进程)、Core(核心转储)、Ign(忽略)、Cont(持续)、Stop(也是终止)
信号的三种处理方式
1、默认处理(即以上图的Comment字段的描述进行处理)
2、忽略(啥也不干)
3、信号捕捉(利用系统接口signal(),或者sigaction()对信号进行捕捉,自行传递函数指针作为处理方法)
信号产生的四种方式
一、通过终端按键产生信号
以举例的三个组合键都可以用于终止进程
ctrl + c:向进程发送2号信号(SIGINT)-- Interrupt from keyboard
ctrl + \:向进程发送3号信号(SIGQUIT)-- Quit from keyboard
ctrl + z:向进程发送20号信号(SIGTSTP)-- Stop typed at terminal
二、调用系统函数向进程发信号
函数:int kill(pid_t pid, int sig);
头文件:<sys/types.h>、<signal.h>
用于向任何进程组或进程发送信号,成功返回0,失败则返回-1
函数:int raise(int sig);
头文件:<signal.h>
用于向进程自己发送一个信号,成功返回0,失败则返回-1
函数:void abort();
头文件:<stdlib.h>
用于向进程自己发送一个6号信号SIGABRT
三、 由软件条件产生信号
比如使用匿名管道的时候,读端不读且关闭,此时写端再进行写操作已经没有意义了,所以OS会自动终止写端进程,向写进程发送13号信号SIGPIPE。
函数:unsigned int alarm(unsigned int seconds);
头文件:<unistd.h>
用于调用alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程发12号信号SIGALRM信号, 该信号的默认处理动作是终止当前进程。
理解软件条件给进行发送信号:OS识别到某种软件条件出发或者不满足,然后构建信号发送给指定的进程
四、硬件异常产生信号
硬件异常
1、就算出硬件异常,进程也不一定会退出。一般情况下默认退出,但不退出对于程序员来说也做不了什么。
2、如果使用signal()函数对硬件异常的信号处理进行了修改,会使得进程陷入死循环,比如除0错误,自己写的处理函数中没有把寄存器的溢出标记位置为0,一直是1,OS会视为异常仍然存在,想要避免这种情况出现,在处理函数中执行完操作后记得exit()来退出进程即可。
除0错误
如何理解?(处理过程)
1、进行计算的是CPU
2、CPU内部存在寄存器,有一种叫做状态寄存器的寄存器,其中有对应的状态标记为和溢出标记位,计算完毕后CPU会自行进行检测,若溢出标记位为1,则存在溢出问题,OS通过pid向进程发送8号信号SIGFPE。
野指针解引用或者越界访问错误
如何理解?(处理过程)
1、通过语言级的地址找到目标位置
2、因为语言级的地址都是虚拟地址,所以将虚拟地址转换为物理地址(通过页表+MMU)
3、因为是野指针(越界)属于非法地址,在MMU转化的时候报错,OS向进程发送11号信号SIGSEGV
结论:所有的信号都有它的来源,但是最终都是被OS解释并发送的
这个结论的指导意义:以后学习通知道了新的信号来源时,思考点应该在于:这个场景所产生的信号时如何被OS解释的
思考
上面所说的所有信号产生,最终都要有OS来进行执行,为什么?
OS是进程的管理者,进程的调度时OS来进行的
信号的处理是否是立即处理的?
在合适的时候后面会解释(信号阻塞中)
信号如果不是被立即处理,那么信号是否需要暂时被进程记录下来?记录在哪里最合适呢?
是在进程PCB(Linux的task_struct结构体中)
一个进程在没有收到信号的时候,能否能知道,自己应该对合法信号作何处理呢? 如何理解OS向进程发送信号?能否描述一下完整的发送处理过程?
信号阻塞
预备概念
实际执行信号的处理动作称为信号递达(Delivery)
信号从产生到递达之间的状态,称为信号未决(Pending)
进程可以选择阻塞 (Block )某个信号。 被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作.
信号在内核中的表示
1、OS发送的信号放在pending未决表中,执行完对应信号的处理函数pending表的信号位置才会置为0。
2、如果一个信号在block阻塞表中置为1,说明该信号被阻塞了,代表暂时不能够执行其对应信号的处理函数。
3、SIGQUIT信号未产生过,一旦产生SIGQUIT信号将被阻塞,它的处理动作是用户自定义函数sighandler。 如果在进程解除对某信号的阻塞之前这种信号产生过多次,将如何处理(下面会详细解释)?POSIX.1允许系统递送该信号一次 或多次。Linux是这样实现的:常规信号在递达之前产生多次只计一次,而实时信号在递达之前产生多次可 以依次放在一个队列里。本章不讨论实时信号。
对信号集的操作接口(sigset_t类型)
sigset_t就是无符号长整型的typedef,每个信号只有一个bit的未决,非0即1,不记录该信号产生了多少次,阻塞标志也是这样表示的。 因此,未决和阻塞标志可以用相同的数据类型sigset_t来存储,sigset_t称为信号集,这个类型可以表示每个信号 的“有效”或“无效”状态。阻塞信号集也叫做当 前进程的信号屏蔽字(Signal Mask),这里的“屏蔽”应该理解为阻塞而不是忽略。
信号集操作接口
#include <signal.h>
int sigemptyset(sigset_t *set); 所有的标志位置为1
int sigfillset(sigset_t *set); 所有的标志位置为1
int sigaddset (sigset_t *set, int signo); 添加一个信号到一个数据集中
int sigdelset(sigset_t *set, int signo); 删除一个信号从一个数据集中
int sigismember(const sigset_t *set, int signo); 判断一个信号在数据集中是否存在
#include<signal.h>
int sigpending (sigset_t *set);
读取当前进程的未决信号集,通过set参数传出。调用成功则返回0,出错则返回-1。
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
调用函数sigprocmask可以读取或更改进程的信号屏蔽字(阻塞信号集)。
返回值:若成功则为0,若出错则为-1
如果oset是非空指针,则读取进程的当前信号屏蔽字通过oset参数传出。如果set是非空指针,则更改进程的信号屏蔽字,参数how指示如何更改。如果oset和set都是非空指针,则先将原来的信号 屏蔽字备份到oset里,然后 根据set和how参数更改信号屏蔽字。假设当前的信号屏蔽字为mask,下表说明了how参数的可选值。
SIG_ BLOCK | set包含了我们希望添加到当前信号屏蔽字的信号,相当Fmask=mask|set |
SIG _UNBLOCK | set包含了我们希望从当前信号屏蔽字中解除阻塞的信号,相当Fmask=mask&~set |
SIG _SETMASK | 设置当前信号屏蔽字为set所指向的值,相当于mask=set |
上面提到,信号产生之后进程不一定会立即进程处理,是在合适的时候处理:
1、合适的时候是什么时候?
用户态是一个受管控的状态,权限较低
内核态是可以OS执行自己代码的抓鬼太,优先级权限很高
其实在系统中还存在一个内核级别的页表
2、信号处理的整个流程?(如下图)
理解记忆版本的上图
信号捕捉
signal函数
#include<signal.h>
sighandler_t signal(int signum, sighandler_t handler);
参数signum:代表要修改处理函数的信号
参数:handler:代表自己定义的处理函数
该函数通过回调的方式修改对应信号的捕捉方法。注意:signal()函数仅仅修改对特定信号的处理动作,而不是直接调用,如果之后没有收到该信号,则处理函数永远不会被调用。
sigaction()函数
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
sigaction函数可以读取和修改与指定信号相关联的处理动作。调用成功则返回0,出错则返回- 1。
signo 是指定信号的编号。若act指针非空,则根据act修改该信号的处理动作。若oact指针非 空,则通过oact传 出该信号原来的处理动作。act和oact指向sigaction结构体:
sigaction 是一个结构体
struct sigaction
{
void (*sa_handler)(int); //对应处理函数的函数指针
sigset_t sa_mask; //信号集(用于操作block阻塞表,当阻塞某个信号时同 时用于屏蔽其他信号)
…………
}
sigaction()代码实例
在处理信号时,又收到相同的信号,OS如何处理?
· 当处理某一个信号的时候,内核将block阻塞表对应信号标志位自动置为一,当执行处理函数完毕时,在置为0,以保证一次只会执行一次信号处理。
除了自动屏蔽之外,还有会希望同时屏蔽其他信号的场景,可以使用sigaction结构中的sa_mask信号集成员变量,说明这些信号也需要屏蔽,当处理函数执行完毕时同时一起恢复。
可重入函数
什么是重入和重入函数?
一种在特定时间内被多个执行流进入了的现象称为重入,这种函数叫做重入函数。
重入会出现时序问题的函数-->称为不可重入函数。
重入不会出现时序问题的函数-->称为可重入函数。
函数是否可重入的条件
调用了malloc或free,因为malloc也是用全局链表来管理堆的。
调用了标准I/O库函数。标准 比特科技 I/O库的很多实现都以不可重入的方式使用全局数据结构。
所以是否可重入的的关键在于在函数中是否使用了全局数据或者结构