认识信号
根据生活中的经验,信号就是向一个事物传递某些信息,在Shell下启动一个前台进程,用户输入Ctrl+c
,这个过程就相当于是再给内核传递一个进程取消的信号。
那么用户按下Ctrl+c后,发生了什么?
- 如果CPU当前正在执行某个进程的代码,接收到
Ctrl+c
信号之后用户空间的代码暂停执行,CPU从用户态切换到内核态处理该信号 - 终端驱动程序将
Ctrl+c
解释成一个SIGINT信号,记在进程的PCB中 - 当某个时刻从内核态返回用户态时,首先要处理PCB中记录的信号,发现有一个SIGINT信号需要处理,这个信号的默认处理动作就是终止进程,所以直接终止进程而不返回用户空间执行代码。
使用kill -l
来查看Linux中的信号
Linux下一共有62个信号,每个信号都有一个编号和一个宏定义名称,例如#define SIGINT 2
,前31个为不可靠信号,后面31个为可靠信号。
信号产生
硬件中断
在键盘上按下Ctrl+c等来产生信号
使用系统调用
- 在后台执行死循环程序,使用
kill
命令给进程发送SIGSEGV信号
在发送信号之后我们需要多按一次回车才会显示Segmentation fault
,这是因为在后台进程终止之前已经回到Shell提示符等待用户输入命令,Shell不希望Segmentation fault
信息和用户输入交错在一起,所以等待用户输入命令之后才显示。
kill命令是基于系统调用kill函数实现的,kill函数可以给一个指定的进程发送指定的信号
#include <signal.h>
int kill(pid_t pid, int signo);
// 成功返回0,错误返回-1
软件产生
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
//调用alarm函数可以设定一个闹钟,告诉内核在second秒之后给当前进程发送SIGALRM信号,该信号默认处理方式为终止当前进程
//second为0表示取消之前设定的闹钟
程序异常
在当前进程执行了除以0的指令,CPU的运算单元会产生异常,内核将这个异常解释为SIGFPE信号发送给进程,或者当前进程访问了非法内存地址,MMU产生异常,内核将这个异常解释为SIGSEGV信号发送给程序
阻塞信号
实际执行信号处理的动作称作信号递达
信号从产生到递达之间的状态称为信号未决
进程可以选择阻塞某一个信号,被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞才执行递达动作
- 每个信号都有两个标志位分别表示阻塞(block)和未决(pending),还有一个指针表示该信号的处理动作。
- 信号产生时,内核在PCB中设置该信号的未决标志,直到信号递达才清除该标志
如上如所示,SIGHUP信号未产生,SIGINT信号已经产生但是正在被阻塞,所以暂时不能递达,SIGQUIT信号从未产生,但是一旦产生它将会被阻塞。
进程在解除某一个信号的阻塞之前,这个信号产生过多次,如果是不可靠信号,产生多次会当做一次处理,而对于可靠信号来说,产生的信号会依次放在一个队列里,将来一一处理。
信号集操作函数
每个信号都有一个未决状态的标志,不记录该信号产生了多少次,可以用sigset_t
来存储,sigset_t
就称作为信号集,这个类型可以表示信号的有效或无效状态。
#include <signal.h>
int sigemptyset