程序在正常执行过程中出现的异常情况, Linux可以响应这些异常的情况,当这些异常情况发生时,我们的程序就要做出相应的动作
用户按键
非法的内存访问
硬件故障
浮点数溢出
软件条件产生
缺省处理
忽略(SIGKILL SIGSTOP 不能忽略)
捕捉信号(SLGKILL SIGSTOP 不能被捕获处理)
13号信号 SIGPIPE 管道破裂信号
18号信号 SIGCONT 继续执行信号
29号信号 SIGIO 异步IO信号
函数原型:
void (*signal(int signum, void (*handler)(int)))(int);
void (*signal(int signum, //要进行处理的信号
void (*handler)(int)) //信号处理函数
//SIG_IGN 忽略
//SIG_DEF 缺省处理动作
//SIG_ERR
)(int);
例子:先让Ctrl+C不执行中断功能,当按Ctrl+\后让Ctrl+C恢复原功能
#include
#include
#include
void handler1(int s)
{
printf("收到%d号信号,但是我不死\n", s);
}
void (*handler_t)(int) = NULL;
void handler_quit(int s)
{
printf("收到%d号信号\n", s);
signal(SIGINT, handler_t);
}
int main()
{
handler_t = signal(SIGINT, handler1);
signal(SIGQUIT, handler_quit);
for(; ;)
{
printf(".");
sleep(1);
fflush(stdout);
}
return 0;
}
不可靠信号(1~31):
1. 会出现丢失
2. 执行完信号处理函数之后,会恢复到缺省动作(Linux解决)
可靠信号(34~64):
1. 不会出现信号丢失
2. 不会出现恢复到缺省动作
非实时信号:不可靠信号都是非实时信号,在信号递达之前产生多次只计一次
实时信号:可靠信号都是实时信号,在递达之前产生多次可以依次放在一个队列里
对于不可靠信号,在进程的PCB中有一个32bit的位图,位图的每一位代表了一个相应编号的信号。操作系统在向某个发送信号时,首先找到该进程的PCB,然后将PCB中位图的相应位置值1。信号对于进程的控制流来说是异步的。
alarm函数:
#include
unsigned int alarm(unsigend int seconds);
当seconds秒后向当前进程发送闹钟信号SIGALARM。
信号递达 delvery:实际执行信号的处理动作
信号未决 pending:信号从产生到递达的中间状态
信号阻塞 block:被阻塞的信号将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达动作。
三张表:
block(位图) 为1表示信号被屏蔽,不会被抵达
pending(位图) 为1表示产生该信号
handler(函数指针数组) 对应位置表示对信号的处理方法
如上图所示,在task_struct结构体(PCB)中有两张位图表和一个函数指针数组,每个位图的低32位中的每一位都代表一种信号;相应的,函数指针数组中的每一个函数指针都对应着一种信号。在图示中,1号信号SIGHUP没有产生也没有被阻塞,该信号响应默认的处理动作;2号信号SIGINT产生了但是被阻塞了,该信号响应的处理动作为忽略;3号信号SIGOUT产生了,没有被阻塞,该信号响应的处理动作为用户自定义的处理动作。
当进程从内核态切换为用户态时检查是否有信号(查看pending表)
如果信号的处理函数为自定义的,在信号递达时就会调用这个函数,这称为捕捉信号。
如上图所示,用户态的进程在运行过程中遇到异常(收到信号),进入内核态保存上下文,由于定义了信号捕捉函数,此时再转去用户态执行自定义的函数,自定义函数执行完后,返回到内核态去,再调用内核中的函数来返回到原来的位置。
函数被不同的控制流程调用,发生在一个调用还没返回时就再次进入该函数的现象。
可重入的条件:
函数只访问自己的局部变量或参数
符合下列条件之一就是不可重入:
1. 调用了malloc或free,因为它们是用全局链表来管理的,有可能因为重入造成错乱。
2. 调用了标准I/O库。因为标准I/O库的很多实现都是以不可重入的方式在使用全局数据结构。
程序在运行过程中由于时序问题而导致的错误,称为竞态条件