《APUE》读书笔记—第十章信号(中)
1、signal函数
Unix系统的信号机制最简单的接口是signal函数,函数原型如下:
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
signum表示信号名称,handler取值常量SIG_IGN(忽略此信号)、常量SIG_DFL(执行默认动作)或者接到此信号后要调用的函数的地址(调用信号处理程序)。
写个程序练习一下signal函数的使用,程序的功能是捕获子进程退出。程序如下:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <sys/wait.h> 4 #include <unistd.h> 5 #include <sys/types.h> 6 #include <errno.h> 7 #include <signal.h> 8 //信号处理程序 9 void sig_child(int signo) 10 { 11 int status; 12 if(waitpid(-1,&status,0) != -1) 13 printf("child process exit.status= %d\n",status); 14 else 15 { 16 perror("waitpid() error"); 17 exit(-1); 18 } 19 } 20 21 int main() 22 { 23 pid_t pid; 24 //创建子进程退出信号 25 signal(SIGCHLD,sig_child); 26 if((pid=fork()) == -1) 27 perror("fork() error"); 28 else if(pid == 0) 29 { 30 printf("I am child process.\n"); 31 exit(0); 32 } 33 else 34 { 35 sleep(3); //让子进程先执行 36 printf("I am parent process.\n"); 37 } 38 exit(0); 39 }
程序执行结果如下:
2、中断的系统调用
如果进程在执行一个低速系统调用而阻塞期间捕获到一个信号,则该系统调用就被中断不再继续执行。系统调用返回出错,将errno的值设置为EINTR。当一个信号发生时,进程捕捉到,意味着已经发生了某种事情,可以唤醒阻塞系统调用进行相关操作。低速系统调用是可能使进程永远阻塞的一类系统调用。与被中断的系统调用相关的问题是必须显示的处理出错返回。例如在进行多操作时候,多的过程被中断了,中断结束后希望重新启动读操作。代码如下:
1 again: 2 if((n=read(fd,buf,BUFSIZE))<0) 3 { 4 if(errno == EINTR) 5 goto again; 6 /*handle other errnors*/ 7 }
自动重新启动的系统调用包括:ioctl、read、readv、write、writew、wait和waitpid。
3、可重入函数
进程捕捉到信号并对其进行处理,进程正在执行的指令序列就被信号处理程序临时中断,首先执行该信号处理程序中的指令,如果从信号处理程序返回,则继续在捕捉到信号时进程正在执行的正常指令中返回。在信号处理程序中,不能判断捕捉到信号时进程在何处执行,这样不能保证在中断处理结束后能够正确返回到进程的执行指令中。为了保证进程在处理完中断后能够正确返回,需要保证调用的是可重入的函数。不可重入函数包括:(1)使用静态数据结构,(2)调用malloc或free,(3)标准I/O函数。信号处理程序中调用一个不可重入的函数,则结果是不可预测的。例如getpwnam函数是个不可重入的,因为其结果存放在静态存储单元中,信号处理程序调用后,返回给正常调用者的信息可能是被返回给信号处理程序的信息覆盖。程序如下:
#include <stdio.h> #include <stdlib.h> #include <sys/wait.h> #include <unistd.h> #include <sys/types.h> #include <errno.h> #include <signal.h> #include <pwd.h> #include <string.h> static void my_alarm(int signo) { struct passwd *rootptr; printf("in signal handler.\n"); if ((rootptr = getpwnam("root")) == NULL) { perror("getpwnam() error"); exit(-1); } printf("return value:,pw_name = %s\n",rootptr->pw_name); alarm(1); } int main() { struct passwd *ptr; signal(SIGALRM,my_alarm); alarm(1); while(1) { if((ptr = getpwnam("anker")) == NULL) { perror("getpwnam() error"); exit(-1); } if(strcmp(ptr->pw_name,"anker") != 0) printf("return value corrupted!,pw_name = %s\n",ptr->pw_name); else printf("return value not corrupted!,pw_name = %s\n",ptr->pw_name); } }
编译执行程序,没有发现出现什么异常,此处有些不明白,日后过来想想。
4、kill和raise函数
kill函数将信号发送给进程或者进程组,raise函数则允许进程向自身发送信号。进程将信号发送给其他进程需要权限,超级用户可以将信号发送给任一进程。调用kill产生的信号时不被阻塞的。函数原型如下:
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
int raise(int sig); //等价于kill(getpid(),signo)
5、alarm和pause函数
使用alarm函数设置计时器,在将来某个指定时间该计时器会超时,产生SIGALRM信号,默认动作时终止调用alarm函数的进程,每个进程只能有一个闹钟时钟,如果在调用alarm之前已设置过闹钟时间,则任何以前的闹钟时间都被新值所代替。当在调用alarm()前已经设置了一个闹钟,那么我们可以调用alarm(0)来取消此闹钟,并返回剩余时间。puase函数使调用进程挂起直到捕捉到一个信号。只有执行了一个信号处理程序并从其返回时,pause才返回-1,并将errno设置为EINTR。函数原型如下:
#include <unistd.h>
unsigned int alarm(unsigned int seconds); //返回0或者以前设置的闹钟时间的余留秒数
int pause(void);
写个程序练习以上函数,程序如下:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <sys/wait.h> 4 #include <unistd.h> 5 #include <sys/types.h> 6 #include <errno.h> 7 #include <signal.h> 8 static void sig_kill(int signo) 9 { 10 printf("Received from kill().\n"); 11 } 12 static void sig_alarm(int signo) 13 { 14 printf("Receiver form aralm().\n"); 15 } 16 int main() 17 { 18 19 signal(SIGHUP,sig_kill); 20 signal(SIGALRM,sig_alarm); 21 22 printf("kill() is called.\n"); 23 kill(getpid(),SIGHUP); 24 printf("alarm() is called.\n"); 25 alarm(3); 26 printf("pause() is called.\n"); 27 pause(); 28 printf("raise() is called.\n"); 29 raise(SIGHUP); 30 exit(0); 31 }
程序执行结果如下:
alarm只设定一个闹钟,时间到达并执行其注册函数之后,闹钟便失效。如果想循环设置闹钟,需在其注册函数中在调用alarm函数。写个程序可以设置一个循环闹钟,使得程序每个多少秒执行以下,执行完成之后开始计时,时间都接着执行,循环指定主程序结束。程序如下:
1 #include <unistd.h> 2 #include <signal.h> 3 #include <stdio.h> 4 void sig_alarm(int signo) 5 { 6 printf("Alarming.\n"); 7 signal(SIGALRM, sig_alarm); //让内核做好准备,一旦接受到SIGALARM信号,就执行sig_alarm 8 alarm(5); 9 } 10 void main() 11 { 12 int i; 13 signal(SIGALRM, sig_alarm);//让内核做好准备,一旦接受到SIGALARM信号,就执行sig_alarm 14 alarm(5); 15 for(i=1;i<21;i++) 16 { 17 printf("sleep %d ...\n",i); 18 sleep(1); 19 } 20 }
程序执行结果如下:
6、信号集
通过信号集(signal set)表示多个信号,这样方便操作多个信号。信号集的数据类型为sigset_t,信号集操作函数如下:
#include <signal.h>
int sigemptyset(sigset_t *set); //初始化由set指向的信号集,清除其中所有信号
int sigfillset(sigset_t *set); //初始化由set指向的信号集,使其包括所有信号
int sigaddset(sigset_t *set, int signum); //添加一个指定的信号
int sigdelset(sigset_t *set, int signum); //删除一个指定信号
int sigismember(const sigset_t *set, int signum); //判断signum是否在set所指向的信号集中
一个进程的信号屏蔽字规定了当前阻塞而不能递送给该进程的信号集,调用sigprocmask函数可以检测或更改其信号屏蔽字。函数原型如下:
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
how的取值为:SIG_BLOCK、SIG_UNBLOCK、SIG_SETMASK
sigpending函数返回信号集,其中的各个信号对于调用进程是阻塞的而不能传递。函数原型为:int sigpending(sigset_t *set)。
写个程序进行信号屏蔽测试,程序如下:
1 #include <sys/wait.h> 2 #include <unistd.h> 3 #include <sys/types.h> 4 #include <errno.h> 5 #include <signal.h> 6 #include <stdio.h> 7 #include <stdlib.h> 8 9 static void sig_quit(int signo); 10 11 int main() 12 { 13 sigset_t newmask,oldmask,pendmask; 14 if(signal(SIGQUIT,sig_quit) == SIG_ERR) 15 { 16 perror("signal() error"); 17 exit(-1); 18 } 19 sigemptyset(&newmask); 20 //添加一个退出信号 21 sigaddset(&newmask,SIGQUIT); 22 //将newmask信号机设置为阻塞,原信号集保存在oldmask中 23 if(sigprocmask(SIG_BLOCK,&newmask,&oldmask) == -1) 24 { 25 perror("sigprocmask() error"); 26 exit(-1); 27 } 28 sleep(5); 29 //获取阻塞的信号集 30 if(sigpending(&pendmask) == -1) 31 { 32 perror("sigpending() error"); 33 exit(-1); 34 } 35 //判断SIGQUIT是否是阻塞的 36 if(sigismember(&pendmask,SIGQUIT)) 37 printf("\nSIGQUIT is pending.\n"); 38 //回复原理的信号集 39 if(sigprocmask(SIG_SETMASK,&oldmask,NULL) == -1) 40 { 41 perror("sigprocmask() error"); 42 exit(-1); 43 } 44 printf("SITQUIT unblocked\n"); 45 sleep(5); 46 exit(0); 47 } 48 49 static void sig_quit(int signo) 50 { 51 printf("caught SIGQUIT.\n"); 52 if(signal(SIGQUIT,SIG_DFL) == SIG_ERR) 53 { 54 perror("signal() error"); 55 exit(-1); 56 } 57 }
程序执行结果如下:在中断上键入Ctlr+\退出字符。
第二次运行时候,在进程休眠的时候产生多次SIGQUIT信号,但是解除了该信号的阻塞后,只会向进程发送一个SIGQUIT,系统没有对信号进行排队。