一、信号的概念:
要理解信号,我们先来进入一个场景。用户在shell下打开一个前台进程,正在运行。在键盘上按下ctrl+C的组合键,当前前台进程会中断。是因为键盘上输入的信号通过硬件传输给驱动程序,将ctrl+C转化为SIGNAL传给该进程的PCB,修改了PCB里的某些字段,也就是说给进程发送了一个信号。
输入指令:kill -l 可以查看信号
二、信号产生的几个主要条件:
1.硬件异常,硬件检测到异常之后会通知内核(kernel),由内核发送给进程。例如如果访问了非法地址,会导致硬件异常。有些人会疑惑为什么非法地址和硬件扯上关系,这里要说明。系统内所说内存的地址都是虚拟地址,需要通过页表和MMU通过映射关系找到物理内存(硬盘)。访问地址出错,MMU就会报错,输入硬件错误。内核讲这个错误解释为SIGSEGV。
2.通过键盘输入某些指令,产生信号。比如Ctrl+C 、Ctrl+Z等等。
3.一个进程调用kill 函数,就可以发送该信号给进程。也可以在命令行上输入kill 加要发送的信号加上进程号来发送信号。其实kill命令实质就是调用kill函数。
三、接收到信号处理动作:(有以下三种)
1.忽略此信号。
2.执行该信号的默认动作(大多数情况下是关闭该进程)。
3.提供一个信号处理函数,要求内核在处理该信号时切换到用户态执行该函数,这种方式成为catch一个信号。
❤通过终端按键产生:
上面说到接收到信号默认处理动作是终止进程并且Core Dump (核心转储)。core dump是当一个进程被终止时,用来保存进程的用户空间上所有的数据保存到磁盘上。文件名通常上core 。事后调试器会排查错误信息。这个过程叫做Post-mortem Debug(事后调试)。但系统是默认不尝试core这个文件的。需要ulimit命令来解除限制。允许产生。
就可以产生core文件。
❤通过系统函数向进程发信号:
在命令行发送kill 指令可以发生信号,比如说创建一个死循环 让其在后台运行(只需要在运行时后面加& 就可,例如运行mysig:后台:./mysig &)。发送kill -9 (pid)。
发送kill -9 (pid),杀死进程。
或者可以通过以下函数:
#include<signal.h>
int kill(pid_t pid,int signo);
int rasie(int signo);
//参数1:进程号,参数2:信号。成功返回0,失败返回-1。
void abort(void);
//类似于exit()。
四、信号在内核里的表示:
上面讨论了信号产生的各情况。而实际处理信号的动作叫做递达(Delivery)。而信号从产生到递达的过程的状态叫做未决(Pending)。进程可以选择阻塞(block)某个信号。被阻塞的信号将一直处于未决状态,直到进程解除对该信号的阻塞,才能递达。
注意:这里的阻塞不是忽略的意思,而是暂时“屏蔽”后面还可以解除阻塞。
操作系统提供了三个4字节的位图,分别是阻塞表,未决表,抵达表来维护信号的状态。
五、信号集操作函数:
siget_t 类型对于每个信号都有“有效”,“无效”两个状态。
#include <signal.h>
int sigemptyset(sigset_t *set);//初始化set所指向的信号集,使其中所有信号的对应bit位清零,表示该信号集不包含任何有效信号。
int sigfillset(sigset_t *set);//初始化set所指向的信号集,使其中所有信号的对应bit置位1,表示该信号集的有效信号包括系统支持的所有信号
int sigaddset(sigset_t *set, int signo);//为信号集中添加
某种有效信号
int sigdelset(sigset_t *set, int signo);//为信号集中删除某种有效信号
int sigismember(const sigset_t *set, int signo);//判断⼀一个信号集的有效信号中是否包含某种信号,若包含则返回1,不包含则返回0,出错返回-1
❤调用函数sigprocmask可以读取或者更改进程的信号屏蔽字(阻塞信号集)。
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
返回值:若成功则为0,若出错则为-1。
参数:
oset:如果oset是非空指针,则读取进程的当前信号屏蔽字通过oset参数传出。如果set是非空指针,则更改进程的信号屏蔽字,参数how指示如何更改。
set:如果oset和set都是非空指针,则先将原来的信号屏蔽字备份到oset里,然后根据set和how参数更改信号屏蔽字。
how:how有三个值可选
SIG_BLOCK /SIG_UNBLOCK 分别是添加和删除当前信号屏蔽字。
SIG_SETMASK 设置当前信号屏蔽字为set指向的值。
如果调用sigprocmask解除了对当前若干个未决信号的阻塞,则在sigprocmask返回前,至少将其中一个信号递达。
❤sigpending读取当前进程的未决信号集,通过set参数传出。
#include <signal.h>
int sigpending(sigset_t *set);
返回值:调用成功则返回0,出错则返回-1。