【APUE | 10】函数signal
函数signal
函数signal介绍
typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler); signal函数 作用1:站在应用程序的角度,注册一个信号处理函数 作用2:忽略信号,设置信号默认处理 信号的安装和回复 参数 --signal是一个带signum和handler两个参数的函数,准备捕捉或屏蔽的信号由参数signum给出,接收到指定信号时将要调用的函数有handler给出 --handler这个函数必须有一个int类型的参数(即接收到的信号代码),它本身的类型是void --handler也可以是下面两个特殊值:① SIG_IGN 屏蔽该信号 ② SIG_DFL 恢复默认行为
SIGCHLD信号
通过 1 signal(SIGCHLD, SIG_IGN) 通知内核对子进程的结束不关心,由内核回收。
- 如果不想让父进程挂起,可以在父进程中加入一条语句: signal(SIGCHLD, SIG_IGN); 表示父进程忽略SIGCHLD信号。
- SIGCHLD信号:该信号是子进程退出的时候向父进程发送的。 子进程结束时, 父进程会收到这个信号。
如果父进程没有处理这个信号,也没有等待(wait)子进程,子进程虽然终止,但是还会在内核进程表中占有表项,这时的子进程称为僵尸进程。这种情 况我们应该避免(父进程或者忽略SIGCHILD信号,或者捕捉它,或者wait它派生的子进程,或者父进程先终止,这时子进程的终止自动由init进程 来接管)。
1. 测试代码
1 //忽略,屏蔽信号 2 #include <stdio.h> 3 #include <stdlib.h> 4 #include <string.h> 5 #include <unistd.h> 6 #include <errno.h> 7 #include <sys/types.h> 8 #include <signal.h> 9 10 int main(int arg, char *args[]) 11 { 12 pid_t pid=fork(); 13 if(pid == -1) 14 { 15 printf("fork() failed! error message:%s\n",strerror(errno)); 16 return -1; 17 } 18 //注册信号,屏蔽SIGCHLD信号,子进程退出,将不会给父进程发送信号,因此也不会出现僵尸进程 19 signal(SIGCHLD,SIG_IGN); 20 if(pid > 0) 21 { 22 printf("father is runing !\n"); 23 sleep(10); 24 } 25 if(pid == 0) 26 { 27 printf("i am child!\n"); 28 exit(0); 29 } 30 printf("game over!\n"); 31 return 0; 32 }
2. 测试代码
1 //恢复信号 2 #include <stdio.h> 3 #include <stdlib.h> 4 #include <string.h> 5 #include <unistd.h> 6 #include <errno.h> 7 #include <sys/types.h> 8 #include <signal.h> 9 10 void catch_signal(int sign) 11 { 12 switch (sign) 13 { 14 case SIGINT: 15 printf("ctrl + C 被执行了!\n"); 16 //exit(0); 17 break; 18 } 19 } 20 21 int main(int arg, char *args[]) 22 { 23 //注册终端中断信号 24 signal(SIGINT, catch_signal); 25 char tempc = 0; 26 while ((tempc = getchar()) != 'a') 27 { 28 printf("tempc = %d\n", tempc); 29 //sleep() 30 } 31 //恢复信号 32 signal(SIGINT, SIG_DFL); 33 while (1) 34 { 35 pause(); 36 } 37 printf("game over!\n"); 38 return 0; 39 }
输出结果:
不可靠信号
什么是不可靠信号:不可靠的意思是信号可能丢失或者被错误处理。
在早起系统中,信号存在两大缺陷,导致了信号不可靠。
一、缺陷一:
信号发生后,信号处理方式被重置为系统默认动作。依旧是说,signal函数知识把信号和我们的信号处理函数关联一次,在发生一次信号后,信号的处理方式就被重置为系统默认了。
这就导致了信号处理函数必须使用如下代码:
1 int sig_int(); 2 ... 3 signal(SIGINT, sig_int); /*①1establish handler*/ 4 ... 5 sig_int() 6 { 7 signal(SIGINT, sig_int); //②为了捕捉下一个信号 8 ... 9 ./*process the signal ... */ 10 ... 11 }
我们不得不在信号处理函数中再次使用signal()。
但是,这样的处理并不能保证程序完全正确,因为在发生一次信号时,在我们开始调用sig_int函数,到执行sig_int函数中的signal函数(也就是我们②代码)之间是有时间间隔的.如果在这段时间间隔里发生了再次发生了信号,那么针对这个信号的处理方式就是系统默认的方法了。
所以早期的信号时不可靠的,因为他不能保证信号都使用正确的(我们期望的)处理方式进行处理。
二、缺陷二:
信号对进程的控制能力差: 早期系统实现中,当我们不希望信号发生时,进程无法关闭一个 信号,并在记录它的发生。
很多时候我们有这样的需求,我们不希望信号打断某项的工作,但是当工作执行完后,又希望系统告诉我们这段时间内发生了什么信号。比如我们运行一段程序,要求运行完之前不能中断它(比如我们的Ctl+C),这是就需要暂时关闭这个信号。 首先我们明确需求,我们需要的是,信号暂时不起作用,并在之后能够提醒我们信号发生过。
为了实现这一点,我们使用下面代码
1 int sig_int(); /* my signal handling function */ 2 int sig_int_flag; /* set nonzero when signal occurs */ 3 main() 4 { 5 signal(SIGINT, sig_int); /* establish handler */ 6 ... 7 while (sig_int_flag == 0) 8 pause(); /* go to sleep, waiting for signal */ 9 ... 10 } 11 sig_int() 12 { 13 signal(SIGINT, sig_int); /* reestablish handler for next time */ 14 sig_int_flag = 1; /* set flag for main loop to examine */ 15 }
sig_int只有两行代码,它的作用就是忽略信号,并且用sig_int_flag标志信号发生过。
可重入函数
1.测试代码
1 #include<stdio.h> 2 #include<signal.h> 3 4 int value = 0; 5 6 void fun() { 7 int i = 0; 8 while (i++<5) { 9 value++; 10 printf("value is %d\n", value); 11 sleep(1); 12 } 13 } 14 int main() 15 { 16 signal(2, fun); 17 fun(); 18 printf("the value is %d\n", value); 19 return 0; 20 }
输出结果: