捕捉信号:
使用 signal 函数
signal 函数是 Linux 系统上传统的信号处理接口:
#include <signal.h>
sighandler_t signal(int signum, sighandler_t handler);
其中 sighandler_t 类型是一个函数指针类型,定义如下:
typedef void (*sighandler_t)(int);
这个类型表示一个信号处理函数。signal 函数的作用就是讲 handler 参数所指向的函数注册成为参数
signum 所代表的信号的处理函数,它的返回值是这个信号原来的处理函数,如果返回 SIG_ERR,则说明有错误发生,
注册失败。
注册成功后,所注册的函数就会在信号被处理时调用,代替了默认的行为,或者成为信号被捕捉。
使用 signal 函数时应注意以下两点:
◆ handler 参数的值可以是 SIG_IGN 或者 SIG_DFL,SIG_IGN 表示忽略这个信号,SIG_DFL 表示对信号的处
理重设为系统的默认方式。
◆ 有些信号是不可以忽略或捕获的,如 SIGKILL 和 SIGSTOP。
下面给出一个例程来说明信号的产生、忽略与捕获的编程,例程代码如下:
1 /* 文件名:sigtest.c */ 2 /* 说明:信号处理例程 */ 3 4 #include <stdio.h> 5 #include <stdlib.h> 6 #include <signal.h> 7 #include <unistd.h> 8 #include <sys/wait.h> 9 10 static pid_t pid; 11 12 /* 子进程 1 SIGALRM 信号处理函数 */ 13 static void wakeup(int dummy) 14 { 15 printf("I (pid = %d) am up now\n",pid); 16 } 17 18 /* 子进程 1 SIGINT 信号处理函数 */ 19 static void handler(int dummy) 20 { 21 printf("I (pid = %d) got an interrupt, will exit\n",pid); 22 exit(0); 23 } 24 25 /* 子进程 2 信号处理函数 */ 26 static void trapper(int i) 27 { 28 if(i == SIGUSR1) 29 { 30 printf("I (pid = %d) got SIGUSR1,will exit\n", pid); 31 exit(0); 32 } 33 else 34 { 35 printf("I (pid = %d) got signal %d, will continue\n", pid, i); 36 } 37 } 38 39 40 /* 父进程信号处理函数 */ 41 void parent(int sig) 42 { 43 printf("Signal (%d) received by parent (%d)\n", sig, pid); 44 } 45 46 int main(int argc, char *argv[]) 47 { 48 int i, cpid1, cpid2; 49 50 printf("Number of signal is %d\n", NSIG); //输出系统中信号的个数 51 52 if(!(cpid1 = fork())) //创建第一个子进程 53 { 54 pid = cpid1 = getpid(); //获得子进程的进程号 55 printf("CPID1 = %d\n", cpid1); 56 57 for(i=1; i<NSIG; i++) 58 { 59 signal(i, SIG_IGN); //忽略所以的信号 60 } 61 62 signal(SIGINT, handler); //捕获信号 SIGINT 63 signal(SIGALRM, wakeup); //捕获超时信号 64 alarm(2); //启动定时器,设置 2 秒后超时 65 66 for(; ;) 67 { 68 pause(); //等待信号 69 } 70 71 printf(" -- CPID1 (%d) terminates\n", cpid1); 72 73 exit(0); 74 } 75 else if(!(cpid2 = fork())) //创建第二个子进程 76 { 77 pid = cpid2 = getpid(); 78 printf("CPID2 = %d\n", cpid2); 79 80 for(i=1; i<NSIG; i++) 81 { 82 signal(i, trapper); //捕获所有的信号 83 } 84 85 for(; ;) 86 { 87 pause(); //等待信号 88 } 89 90 printf(" -- CPID2 (%d) terminates\n", cpid2); 91 92 exit(0); 93 } 94 95 /* 下面是父进程执行的代码 */ 96 pid = getpid(); //取得 PID 97 sleep(3); //睡眠,让子进程先运行 98 printf("This is parent process (pid = %d)\n", pid); 99 100 for(i=1; i<NSIG; i++) 101 { 102 signal(i, parent); //捕获所以信号 103 } 104 105 printf("Send SIGUSR1(%d) to CPID1 (%d)\n", SIGUSR1, cpid1); 106 kill(cpid1, SIGUSR1); 107 printf("Send SIGINT(%d) to CPID1 (%d)\n", SIGINT, cpid1); 108 kill(cpid1, SIGINT); 109 printf("Send SIGINT(%d) to CPID2 (%d)\n", SIGBUS, cpid2); 110 kill(cpid2, SIGINT); 111 printf("Send SIGUSR1(%d) to CPID2 (%d)\n", SIGUSR1, cpid2); 112 kill(cpid2, SIGUSR1); 113 114 for(; wait((int *)0) > 0; ); //等待子进程结束 115 116 return 0; 117 }
在这个例程中,父进程又创建了两个子进程,这三个进程分别注册或忽略了相关的信号,然后通过 alarm 函数设置超
时信号或通过 kill 函数发送信号。还用到了 pause 和 sleep 函数,如下:
#include <unistd.h>
int pause(void);
unsigned int sleep(unsigned int seconds);
pause 函数将使当前进程进入睡眠态,直到有信号发送。它的返回值永远是 -1,同时变量 errno 的值被设为
EINTR 以表示有信号发生。
sleep 函数将使当前进程进入睡眠态,并在 seconds 参数指定的秒数后被唤醒继续执行。注意当有未忽略的
信号发生时 sleep 函数会提前返回,返回值是剩余的秒数。如果返回 0 则说明是正常被唤醒的,而不是因信号的发生
提前返回的。