1.信号基础
1.1 信号函数
#include <signal.h>
void (*signal(int signo, void (*func)(int)))(int); 这是可靠写法
void (* signal( int signo, void (*func)(int) ) )(int);
函数名 :signal
函数参数 :int signo, void (*func)(int)
返回值类型:void (*)(int);
信号的忽略: 信号忽略的核心是将mask中设置0,导致后续与操作始终是0,SIGKILL与SIGSTOP不能被忽略!另外,如果忽略某些由硬件异常产生的信号(例如除以0),则进程的运行行为是未定义的。
信号的默认动作:大多数的信号处理函数是终止进程!
信号捕捉:为了做到这一点,要通知内核在某种信号发生时调用一个用户函数。在用户函数中,可执行用户希望对这种事件进行的处理。注意,不能捕捉SIGKILL和SIGSTOP信号。
信号与中断:信号依赖于中断!
信号与系统调用:信号是可以打断系统调用的,所以在执行调用函数的时候,需要增加判断,errno是否是EINTR,如果是则continue,否则perror(“error”)
typedef void sigfun(int);
sigfun* signal(int signo, sigfun* func) //信号函数换一种写法
signo为信号no,func为信号处理函数
#define SIG_ERR ( void (*) () )-1
#define SIG_DFL ( void (*) () )0
#define SIG_IGN ( void (*) () )1
#include "apue.h"
static void sig_usr(int); /* one handler for both signals */
int
main(void)
{
if(signal(SIGUSR1, sig_usr) == SIG_ERR)
err_sys("can't catch SIGUSR1");
if(signal(SIGUSR2, sig_usr) == SIG_ERR)
err_sys("can't catch SIGUSR2");
for(;;)
pause();
}
static void sig_usr(int signo) /* argument is signal number */
{
if(signo == SIGUSR1)
printf("received SIGUSR1\n");
else if (signo == SIGUSR2)
printf("received SIGUSR2\n");
else
err_dump("received signal %d\n", signo);
}
可以用signal(1), signal -SIGUSR1 pidno来查看结果
1.2 信号产生
产生信号的条件:
- 当用户按某些终端键时,引发终端产生的信号。
- 硬件异常产生信号。
- 进程调用kill(2)函数可将信号发送给另一个进程或进程组。(自然,对此有所限制:接收信号进程和发送信号进程的所有者必须相同,或者发送信号进程的所有者必须是超级用户。)
- 用户可用kill(1)命令将信号发送给其他进程。
- 当检测到某种软件条件已经发生,并应将其通知有关进程时也产生信号。(这里指的不是硬件产生的条件,而是软件条件。)
在信号产生(generation)和递送(delivery)之间的时间间隔,称信号是未决的(pending)
1.3可重入函数
信号的函数执行是可以被打断的,所以会涉及到函数的可重入,系统调用函数都是可重入的,后续在sigaction里可以设置信号处理函数会自动重启被打断的系统调用,一部分库函数也是可重入的
可冲重入的函数列表如下:
(a)已知它们使用静态数据结构,(b)它们调用malloc或free,或(c)它们是标准I/O函数。
1.4 实时信号与非实时信号
其中1-31 是非实时信号,是不可靠信号, 34-64是实时信号,是可靠的
非实时信号是通过pending位图来实现,同时出现多个信号会覆盖
实时信号是有队列的,可以ulimit -a来查看pending最大个数,同时出现多个信号会排队
2. 信号响应策略
信号响应的过程
- 信号从接收到响应是有一个延迟的,因为需要等待中断
- 标准信号是没有严格的响应顺序的
- 因为pending位图的关系,导致如果连续多个标准信号到来,pending会被覆盖,所以会出现丢失,没有队列!
注意点:
- 进程中的信号位图的初始时刻pending均为0,mask均为1,后续可以通过sigprocmask,sigaction等来修改,阻塞信号mask置0。应结合SA_NODEFER分析。
- 信号在用户态切换到内核态前要进行位与操作,判断是否有信号
- 只有当有中断到来后,当前进程进入内核态,信号本身到来并不能立刻响应
- 进程在内核态发现信号后,将mask与pending均置0,当信号处理函数执行完后,会将mask置1,pending置0!