Unix/Linux系统编程自学笔记-第六章:信号和信号处理
目录
概述
本章介绍了信号、信号的产生、信号的内容和信号处理;介绍了信号和中断的统一管理,帮助建立对于信号的正确看待方式;信号在Unix/Linux是发挥怎样的作用,如何产生以及处理,PROC中的信号和信号作为进程通信(IPC)机制的适用性;验证信号处理的系列编程实践。
信号和中断
-
中断的定义
中断一个发送给进程的事件,它会把进程从正常的活动转移到其他的活动中去,即中断处理中。进程在完成中断后才会恢复到正常状态。
-
进程中断
一类发送给进程的中断,在Unix/Linux系统内中断被称为信号,每一个信号都有唯一的ID号,编号从1到31不等。PROC会根据不同的信号ID给出不同的操作。
-
硬件中断
发给处理器或CPU断点信号,可以来自硬件、其他处理器或自身。
-
进程的陷阱错误
由误判而产生的给自身的中断。这类异常中断在用户模式下发生时,进程在默认情况下会终止,并用一个可选的内存转储进行调试。在内核模式下发生陷阱的原因必是发生了硬件错误或是内核代码存在漏洞,此时Unix/Linux系统会打印一条PANIC错误信息,然后停止。
Unix/Linux中的信号
-
一些Unix/Linux信号实例
-
Ctrl & C
这个组合会发出一个键盘硬件中断信号,产生SIGINT(2)信号。
-
nohup a.out &
使进程在用户退出后仍然继续运行的命令,效果是使得生成的子进程忽略掉SIGHUP(1)信号,当用户退出后,还会像所有与终端相关的进程发送一个SIGHUP信号继续运行,后台程序将会中断与I/O设备的连接。
-
kill pid
发送一个SIGTERM(15)信号,杀死pid所标识的进程,如果进程拒绝死亡,可以使用
kill -s 9 pid
命令来杀死进程。kill -s signal_number pid
可以按不同参数杀死不同的进程。
-
-
Unix/Linux中的信号类型
如上文所述,在Unix/LInux系统中有31中不同的信号,每种信号都在signal.h中定义:
#define SIGHUP 1 #define SIGINT 2 #define SIGQUIT 3 #define SIGILL 4 #define SIGTRAP 5 #define SIGABRT 6 #define SIGIOT 6 #define SIGBUS 7 #define SIGFPE 8 #define SIGKILL 9 #define SIGUSR1 10 #define SIGSEGV 11 #define SIGUSR2 12 #define SIGPIPE 13 #define SIGALRM 14 #define SIGTERM 15 #define SIGSTKFLT 16 #define SIGCHLD 17 #define SIGCONT 18 #define SIGSTOP 19 #define SIGTSTP 20 #dpfine STGTTTN 21 #define SIGTTOU 22 #define SIGURG 23 #define SIGXCPU 24 #define SIGXFSZ 25 #define SIGVTALRM 26 #define SIGPROF 27 #define SIGWINCH 28 #define SIGPOLL 29 #define SIGPWR 30 #define SIGSYS 31
-
信号来源
Unix/Linux系统中的信号来源也是硬件、异常和其他进程,其中硬件产生的中断可以是来自中断组合键、间隔计时器和其他硬件错误。
-
PROC中的信号
PROC中有一个32位的向量来记录发送给进程的信号,每一位都代表了一个信号编号(0信号除外)。另有一个MASK位向量,用来屏蔽响应的信号。
-
信号处理函数
-
PROC中有一个信号处理数组
int sig[32]
,sig[32]指向对应的信号处理方式。0表示默认DEFault,1表示忽略IGNore,其他值则对应其他用户模式下预先安装的信号处理(捕捉)函数。 -
安装信号捕捉函数
int r = signal(int signal_number, void *handler);
是信号捕捉的系统调用,可以用来修改选定的信号编号的处理函数(SIGKILL(9)和SIGSTOP(19)除外),已安装的信号处理函数将会进入捕捉函数入口:void catcher(int signal_number){···}
sigaction()系统调用是更好的信号捕捉函数,它的结果如下:
int sigaction (int signum, const struct sigaction *act, struct sigaction *oldact);
sigaction结构体如下:
struct sigaction{ void (*sa_handler)(int);//指向处理函数 void (*sa_sigaction)(int ,siginfo_t * ,void *);//接受更多的信号 sigset_t sa_mask;//要阻塞的信号 int sa_flags;//修改信号处理进程的行为,此时本参数的值应为SA_SIGINFO void (*sa_restorer)(void); }
-
-
信号与异常处理与IPC
-
信号处理
-
在内核模式下,会检查信号并处理未完成的信号。清除信号,迂回执行捕捉函数,恢复正常执行。
-
重置用户安装的信号捕捉函数。
-
信号与唤醒,唤醒陷入休眠的进程。
-
异常处理
-
当进程遇到异常时会陷入内核模式,将异常原因转为信号编号发给自己。在内核模式下内核就只会打印一条PANIC错误信后然后终止进程。
-
进程通过预先安装的信号捕捉函数处理用户模式下的程序错误。
-
在特殊情况下,它会让某个进程发出杀死另一个进程的信号。
-
-
IPC
在许多操作系统的书籍中,信号被归类为进程间的通信机制。基本原理是一个进程可以向另一个进程发送信号,使它执行预先安装的信号处理函数。
- 该机制并不可靠,因为可能会丢失信号。每个信号由位向量中的一个位表示,只能记 录一个信号的一次岀现.如果某个进程向另一个进程发送两个或多个相同的信号,它 们可能只在接收PROC中出现一次。实时信号被放入队列,并保证按接收顺序发送, 但操作系统内核可能不支持实时信号。
- 竞态条件:在处理信号之前,进程通常会将信号处理函数重置为DEFault。要想捕捉同一信号的再次出现,进程必须在该信号再次到来之前重新安装捕捉函数。否则,下 一个信号可能会导致该进程终止。在执行信号捕捉函数时,虽然可以通过阻塞同一信 号来防止竞态条件,但是无法防止丢失信号。
- 大多数信号都有预定义的含义。不加区别地任意使用信号不仅不能达到通信的目的,反而会造成混乱。例如,向循环进程发送SIGSEGV(11)段错误信号,出现错误的信息。
因此,试图将信号用作进程间通信手段实际上是对信号预期用途的过度延伸.应避免出现这种情况。
实践
-
实践验证了书中t7.c段错误捕捉函数。
-
代码:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <signal.h> #include <setjmp.h> //for a long jump jmp_buf env; //for saving lonjmp enviroment int count = 0; void handler(int sig , siginfo_t *siginfo , void *context) { printf("handler: sig=%d from PID=%d UID=%d count=%d\n", sig , siginfo->si_pid , siginfo->si_uid , ++count); if (count >= 4) // let it occur up to 4 times longjmp(env , 1234); } int BAD() { int *ip = 0; printf("in BAD(): try to dereference NULL pointer\n"); *ip = 123; // dereference a NULL pointer printf("should not see this line\n"); } int main(int argc , char *argv[]) { int r; struct sigaction act; memset(&act , 0 , sizeof(act)); act.sa_sigaction = &handler; act.sa_flags = SA_SIGINFO; sigaction(SIGSEGV , &act , NULL); //install SIGSEGV catcher if ((r = setjmp(env)) == 0) //call set jmp(env) BAD(); //call BAD() else printf("proc %d survived SEGMENTATION FAULT: r=%d\n", getpid() , r); printf("proc %d looping\n" , getpid()); while (1); }
-
运行截图
-
代码
-
编译运行
-