信号(signal)是一种软中断,他提供了一种处理异步事件的方法,也是进程间唯一的异步通信方式。在Linux系统中,根据POSIX标准扩展以后的信号机制,不仅可以用来通知某进程发生了什么事件,还可以给进程传递数据。
1.1信号的来源
(1)硬件方式
·用户在终端上按下某些键时,将产生信号。如死循环时经常按的Ctrl+c
·硬件异常产生信号:除数为0、无效的存储访问等。
(2)软件方式
·用户在终端下调用kill命令向进程发送任意信号。
·进程调用kill或sigqueue函数发送信号。
·当检测到某种软件条件已经具备时发出信号,如alarm或settimer设置的定时器超时时将生成SIGALRM信号。
1.2信号的种类
在Shell下输入 kill -l 可显示Linux系统支持的全部信号。
信号的值在signal.h中定义
前30个信号的意义
https://blog.51cto.com/peacefulmind/1948591
从31号信号到第64信号是Linux的实时信号,他们没有固定的含义(可由用户自由使用)。Linux线程机制使用了前3个实时信号。所有实时信号的默认动作都是终止进程。
①可靠信号与不可靠信号
第1到第31号信号都是继承自UNIX系统,是不可靠信号。第33到第64号信号都是可靠信号,也称为实时信号。
信号的可靠性是指信号是否会丢失,或者说该信号是否支持排队。当导致产生信号的事件发生时,内核就产生一个信号。信号产生以后,内核通常会在进程表中设置某种形式的标志。当内核设置了这个标志以后,我们就称内核向进程传递了一个信号。信号产生和信号传递之间的时间间隔,称之为信号未决。
进程可以调用sigpending将信号设置为阻塞,若为进程产生了一个阻塞的信号,而对该信号的动作是捕捉该信号,则内核将为该进程的此信号保持为为决状态,直到该进程对此信号解除阻塞或者将此信号的响应更改为忽略。如果在进程解除对某个信号的阻塞之前,这种信号发生了多次,那么如果信号被递送多次(即信号在为决信号队列里排队),则称之为可靠信号;只被递送一次的信号被称之为不可靠信号。
②信号的优先级
由于信号的实质是软中断,中断有优先级,那信号同样有优先级。如果一个进程有多个为决信号,则对于同一个未决的实时信号,内核将按照发送的顺序来递送信号。若存在多个未决的实时信号,则值越小的越先被递送。如果既存在不可靠信号,有存在可靠信号,则Linux系统优先递送不可靠信号。
1.3进程对信号的响应
当信号发生时,用户可以要求进程以下列3种方式之一对信号做出响应。
①捕捉信号。对于要捕捉的信号,可以为其指定信号处理函数,信号发生时该函数自动被调用,在该函数内部实现对该信号的处理。
②忽略信号。大多数信号都可以使用这种方式进行处理,但是SIGKILL和SIGSTOP这俩信号不可以被忽略,这两个信号也不能被捕获或者阻塞。此外,如果忽略某些由硬件异常产生的信号,则进程的行为是不可预测的。
③按照系统默认方式处理。大部分信号的默认操作是终止进程,且所有的实时信号的默认动作都是终止进程
2.信号处理
2.1信号的捕捉和处理
Linux系统中对信号的处理主要由signal和sigaction函数来完成。
①signal函数
signal函数用来设置进程在接收到信号时的动作
#include<signal.h> typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler);
signal会根据参数signum指定的信号编辑来设置该信号的处理函数。当指定的信号到达时就会跳转到参数handler指定的函数执行。如果参数handler不是函数指针,则必须是常数SIG_IGN(忽略该信号)或SIG_DFI(对该信号执行默认操作)。handler是一个函数指针,它所指向的函数的类型时sighandler_t,即它所指向的函数有一个int型参数,且返回值的类型为void。
signal函数执行成功时返回以前的信号处理函数指针,当有错误发生时则返回SIG_ERR。
例程看一下signal怎么用
#include<stdio.h> #include<signal.h> void handler_sigint(int signo) { printf("recv SIGINT\n"); } int main() { signal(SIGINT, handler_sigint); while(1); return 0; }
运行程序以后按 Ctrl+c
程序先使用signal()安装信号SIGINT的处理函数handler_sigint,然后进入死循环。当程序接收到SIGINT信号以后(Ctrl+c就是在给程序发送SIGINT信号),程序自动跳转到信号处理函数处执行。
②sigaction函数
sigaction函数可用来检查或设置进程在接收到信号时的动作。
#include<signal.h> int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
sigaction会根据参数signum指定的信号来设置该信号的处理函数。参数signum可以是SIGKILL和SIGSTOP以外的任何信号。如果参数act不是空指针,则为signum设置新的信号处理函数;如果oldact不是空指针,则旧的信号处理函数将被存储在oldact中。struct sigaction定义如下
struct sigaction { void (*sa_handler)(int); void (*sa_sigaction)(int, signfo_t *, void *); sigset_t sa_mask; void (*sa_restorer)(void); }
sa_handler和sa_sigaction在某些体系结构上被定义为共用体,即这两个值在某一时刻只有一个有效。
数据成员sa_restorer已经作废,不再使用。
sa_handler可以是常数SIG_DFL或SIG_IGN,或者是一个信号处理函数的函数名。信号处理函数只有一个参数即信号编号。该参数和参数sa_sigaction实际上都是函数指针。
sa_sigaction也是用来指定函数signum的处理函数,它有三个参数,第一个参数是信号编号;第二个参数是指向siginfo_t结构的指针;第三个参数是一个指向任何类型的指针,一般不使用。
sa_mask成员声明了一个信号集,在调用信号捕捉函数前,该信号集会增加到进程的信号屏蔽码中,新的信号屏蔽码会自动包括正在处理的函数信号。当从信号捕捉函数返回时,进程的信号屏蔽码会恢复为原来的值。因此,当处理一个给定的信号时,如果这个信号再次发生,那么他会被阻塞到本次信号处理结束为止。若这个信号发生了多次,则对于不可靠信号,他只会被阻塞一次,即本次信号处理结束以后只会再处理一次(相当于丢失了信号);对于可靠信号(实时信号),则会被阻塞多次,即信号不会丢失,信号发生了多少次就会调用信号处理函数多少次。
sa_flags成员用来说明信号处理的一些其他相关操作。他可以取以下值或他们的组合
注:Linux下signal函数是由sigaction函数实现的
例程
#include<stdio.h> #include<unistd.h> #include<signal.h> int temp = 0; void handler_sigint(int signo) { printf("recv SIGINT\n"); sleep(5); temp += 1; printf("the value of temp is: %d\n", temp); printf("in handler_sigint, after sleep\n"); } int main() { struct sigaction act; act.sa_handler = handler_sigint; act.sa_flags = SA_NOMASK; //act.sa_flags = NULL; sigaction(SIGINT, &act, NULL); while(1); return 0; }
程序定义了一个act结构,并设置了act的sa_handler微信号处理函数handler_sigint,并且将sa_flags赋值为SA_NOMASK,即支持信号的嵌套处理。
快速按下 Ctrl+c 组合键两次,执行结果如下(实际上我按了4次)
程序启动后,接收SIGINT信号(我们按得Ctrl+c就是给程序发信号),程序打印出“recv SIGINT”表示信号处理函数处理了信号SIGINT,然后sleep(5)。在函数挂起期间,我们又按了三次 Ctrl+c ,由于我们设定了sa_flags的值为SA_NOMASK,因此程序又响应一次信号SIGINT,程序·从sleep()处嵌套调用信号处理函数handler_sigint,再一次打印出“recv SIGINT”。睡眠5秒后,将temp的值打印出来并返回到本次信号处理程序的跳入点sleep处,然后再打印出temp的值并返回到主函数。
从程序执行顺序可以看出来,temp的值随着被调用的次数而自增,而由于实际应用中信号总是随机发生的,这样temp的值也会随机变化。若main函数或其他地方还用到了这个全局变量,则程序将产生不可预料的结果。我们将这种函数称之为不可重入函数。编写信号处理函数不能使用不可重入函数。一般来说,满足一下条件的函数为不可重入函数:
(1)使用了静态的数据结构,如getgrgid(),全局变量等
(2)函数实现时,调用了malloc()函数或free()函数
(3)函数实现时,使用了标准I/O函数
将程序中的“act.sa_flags = SA_NOMASK”这行代码注释,将下一行注释取消,然后重新编译并执行,快速按3次及以上Ctrl+c,结果如下
可以看到,sigaction按照默认方式阻塞当前正在处理的信号,但为什么结果只有两个呢?这是因为SIGINT是不可靠信号,不可靠信号不支持排队,从而有可能丢失信号,从程序可以看出,第三个信号确实被丢失了。
③pause
pause函数使调用进程挂起直至捕捉到一个信号。
#include<unistd.h> int pause(void);
pause函数会令目前的进程暂停(睡眠),直至被信号(signal)所中断。该函数只返回-1并将errno设置为EINTR.