信号的产生:
当引发信号的事件发生时,为进程产生一个信号(或向一个进程发送一个信号)。信号产生时,内核会在进程表中设置一位标识。
信号的递送(delivery):
当进程对信号采取动作(执行信号处理函数或忽略)时称为递送。
信号产生和递送之间的时间间隔内称信号是未决的(pending)。
信号递送阻塞(block):
进程可指定对某个信号采用递送阻塞,如果此时信号的处理是系统默认动作或者捕捉该信号,则该信号就会处于未决的状态,直到进程解除对该信号的递送阻塞或者处理方式改为忽略。
来了一个信号,信号产生标记变为1,此时检测信号屏蔽字是否为0;为0可以递送,递送走之后,信号产生标记变为0,信号屏蔽字变为1;当捕捉函数接收递送的信号并结束,信号屏蔽字变为0。(类似于接电话,接通第一个电话的时候,接不了第二个电话) 如果信号的处理方式是忽略该信号,那么该信号永远不会处于递送或者递送未决状态
非实时信号: 1~31(不支持排队,信号同一时间太多,会丢失,不可靠信号)
信号名称编号信号说明默认处理SIGHUP1终端关闭时产生这个信号进程终止SIGINT2终端输入了中断字符ctrl+c进程终止SIGKILL9用来立即结束程序的运行。本信号不能被阻塞、处理和忽略。如果管理员发现某个进程终止不了,可尝试发送这个信号。进程终止SIGSEGV11试图访问未分配给自己的内存, 或试图往没有写权限的内存地址写数据,默认打印出segment fault。进程终止并且产生core文件SIGPIPE13往管道写时,读者已经不在了,或者往一个已断开数据流socket写数据。发送的进程会收到进程终止SIGALRM14时钟定时信号, 计算的是实际的时间或时钟时间。alarm 函数使用该信号。进程终止SIGTERM15有kill函数调用产生。进程终止SIGCHLD17子进程停止或者终止时,父进程会收到该信号。忽略该信号SIGSTOP19用来停止一个进程。本信号不能忽略,也不能被捕捉进程暂停执行实时信号:34~64(支持排队,可靠信号)(克服了信号丢失的问题)
进程可以从三个方面使用信号:
指定当收到信号时进程的处理函数(信号处理)。
阻塞一个信号(也就是推迟它的递送),比如处于一段临界代码,执行完临界代码后在启用这个信号。
向另外一个进程发送信号(进程间通讯)。
进程在接收到一个信号时,通常可以进行三种处理:
忽略此信号:大多数信号都可使用这种方式进行处理.
但有两种信号不能被忽略:SIGKILL和SIGSTOP。(它们向超级用户提供一种进程终止或或停止的可靠方法)
signal(SIGINT, SIG_IGN)//忽略SIGINT信号 捕捉信号:为了做到这一点要通知内核在某种信号发生时,调用一个用户函数。在用户函数中,可执行用户希望对这种事件进行的处理。 #include <stdio.h> #include <signal.h> void fun(int sig) { printf("sig:%", sig);//输出SIGINT的编号2 } int main() { signal(SIGINT, fun); while(1); } 执行系统默认动作:对大多数信号的系统默认动作是终止该进程。 signal(SIGINT, SIG_DFL)//系统默认方式处理SIGINT子进程调用exec函数后,exec将父进程中设置为捕捉的信号变为默认处理方式,其余不变(SIG_IGN)。例如在父进程中把SIGTERM设置为捕捉,SIGINT设置为忽略。子进程执行exec和不执行exec的区别。
int main() { signal(SIGINT, handler1); pid_t pid = fork(); if (pid == 0) { //收到SIGINT信号子进程结束(变为默认处理方式) execl("b.out"); } //收到SIGINT父进程输出hello world getchar(); return 0; } int main() { signal(SIGINT, SIG_IGN); pid_t pid = fork(); if (pid == 0) { //收到SIGINT信号子进程忽略信号 //因为b.out里面没有handler自定义函数 execl("b.out"); } //收到SIGINT父进程忽略信号 getchar(); return 0; }目前Linux系统定义了64个信号,以后也可能进一步扩展。所以用类型sigset_t来表示所有的信号。
一般情况下,sigset_t中的一个比特位表示一种信号。
对信号集的操作需要专用的操作函数。
信号0是系统保留,不被使用的。
信号集操作函数有以下5个
#include <signal.h> //把所有信号集的比特位置为0 int sigemptyset(sigset_t *set); //把所有信号集的比特位设为1 int sigfillset(sigset_t *set); //把信号signum加入信号集set中 int sigaddset(sigset_t *set, int signum);sigaddset(&set, SIGINT); //把信号signum从信号集set中清除 int sigdelset(sigset_t *set, int signum); //判断某个信号是否在信号集set中 int sigismember(const sigset_t *set, int signum); 使用例子: #include <stdio.h> #include <signal.h> #include <unistd.h> int main() { sigset_t sigset; sigemptyset(&sigset); sigaddset(&sigset, 2); sigaddset(&sigset, 5); sigaddset(&sigset, 17); sigaddset(&sigset, 22); int i; for (i = 1; i <= 64; i++) { if (i == 32 || i == 33) continue; if (sigismember(&sigset, i)) { printf("i:%d\n", i);//打印出信号集里的所有信号 } } }Linux中signal注册函数最终也是调用sigaction来实现,保留signal主要是为了兼容。
signum:指定需要处理的信号(除SIGKILL和SIGSTOP)。
act:设定信号的处理方式,act可以为NULL。
oldact:之前设定的信号处理方式会保存到第三个参数oldact,oldact为NULL时不保存。
返回0成功,返回-1失败。
由于信号处理函数可被中断,所以在处理函数中调用的函数都必须是可重入的
可重入函数:指一个可以被多个任务调用的过程(函数),任务在调用时不必担心数据是否会出错。因为进程在收到信号后,就将跳转到信号处理函数去接着执行。如果信号处理函数中使用了不可重入函数,那么信号处理函数可能会修改原来进程中不应该被修改的数据,这样进程从信号处理函数中返回接着执行时,可能会出现不可预料的后果。不可再入函数在信号处理函数中被视为不安全函数
满足下列条件的函数多数是不可重入的:
(1)使用静态的数据结构或者变量或者使用了全局的对象或变量的函数,如strtok(),gmtime(),getgrgid(),gethostbyname(),localtime()以及getpwnam()等等;
(2)实现时使用了标准I/O函数的。标准I / O库的很多实现都以不可再入方式使用全局数据结构。
tips:fun_r()可重入函数
每个进程都会有一个信号屏蔽字,它规定了当前那些信号可以递送,哪些信号需要阻塞。
当程序执行敏感任务时(比如更新数据库),不希望外界信号中断程序的运行。在这个阶段并不是简单地忽略信号,而是阻塞这些信号,当进程处理完关键任务后,还会处理这些信号。
sigprocmask可以检测和修改进程的信号屏蔽字。oldset非空时,当前信号屏蔽字会保存到oldset中。如果set非空,那么参数how指示如何修改当前屏蔽字。
#include <signal.h> int sigprocmask(int how, const sigset_t *set, sigset_t *oldset); /*how有三个值: SIG_BLOCK 被阻塞的信号是当前屏蔽字加上第二个参数包含的信号 SIG_UNBLOCK 第二个参数包含的信号从当前屏蔽字中移除 SIG_SETMASK 当前屏蔽字被完全设置为第二个参数的值。 */sigpending返回当前处于阻塞递送的信号,即信号已经产生,但还没有递送给进程处理。
成功返回0,失败返回-1
在sigpending中,如果我们在解除某个信号调用sleep函数,那么未被递送的信号会首先被处理。导致pause无法被唤醒。
需要把解除信号和等待信号递送做成一个原子操作,这样就不会产生上述问题。
#include <signal.h> int sigprocmask(int how, const sigset_t *set, sigset_t *oldset); /*how有三个值: SIG_BLOCK 被阻塞的信号是当前屏蔽字加上第二个参数包含的信号 SIG_UNBLOCK 第二个参数包含的信号从当前屏蔽字中移除 SIG_SETMASK 当前屏蔽字被完全设置为第二个参数的值。 */ eg: sigset_t set sigset_t set2 //所有信号加到信号集set sigfillset(&set); //设置屏障 sigprocmask(SIG_SETMASK, &set, NULL); //敏感任务 meeting(); //返回挡在门外的信号集set2 sigpending(&set2); //找出挡在门外的信号 for 1 to 64 sigismember(); //解除屏障 sigemptyset(set); //信号集清空 sigprocmask(SIG_SETMASK, &set, NULL);