【摘自《Linux/Unix系统编程手册》】
较之于标准信号,实时信号的优势如下:
- 实时信号的信号范围有所扩大,可应用于应用程序自定义的目的。而标准信号中可供应用随意使用的信号仅有两个:SIGUSR1 和 SIGUSR2。
- 对实时信号所采取的是队列化管理。如果将某一实时信号的多个实例发送给一进程,那么将会多次传递信号。相反,如果某一标准信号已经在等待某一进程,而此时即使再次向该进程发送信号的实例,信号也只会传递一次。
- 当发送一个实时信号时,可为信号指定伴随数据(一整型数或者指针值),供接收进程的信号处理器获取。
- 不同实时信号的传递顺序得到保障。如果有多个不同的实时信号处于等待状态,那么将率先传递具有最小编号的信号。换言之,信号的编号越小,其优先级越高。如果是同一类型的多个信号在排队,那么信号(以及伴随数据)的传递顺序与信号发送来时的顺序保持一致。
使用实时信号
- 发送进程使用 sigqueue() 系统调用来发送信号及其伴随数据。
- 要为该信号建立一个处理器函数,接收进程应以 SA_SIGINFO 标志发起对 sigaction() 的调用。因此,调用信号处理器时就会附带额外参数,其中之一是实时信号的伴随数据。
在 Linux 中,即使接收进程在建立信号处理器时并未指定 SA_SIGINFO 标志,也能对实时信号进行队列化管理(但在这种情况下,将不可能获得信号的伴随数据)。
发送实时信号
系统调用 sigqueue() 将由 sig 指定的实时信号发送给有 pid 指定的进程
#define _POSIX_C_SOURCE 199309 #include <signal.h> int sigqueue(pid_t pid, int sig, const union sigval value); Returns 0 on success, or -1 on error
使用 sigqueue() 发送信号所需要的权限与 kill() 的要求一致。也可以发送空信号(即信号 0),其语义与 kill() 中的含义相同。(不同于 kill(),sigqueue() 不能通过将 pid 指定为负值而向整个进程组发送信号)
参数 value 指定了信号的伴随数据,具有以下形式:
union sigval { int sival_int; /* Integer value for accompanying data */ void* sival_ptr; /* Pointer value for accompanying data */ };
对该参数的解释则取决于应用程序,由其选择对联合体(union)中的 sival_int 属性还是 sival_ptr 属性进行设置。sigqueue() 中很少使用 sival_ptr,因为指针的作用范围在进程内部,对于另一进程几乎没意义。
一旦触及对排队信号的数量限制,sigqueue() 调用将会失败,同时将 errno 置为 EAGAIN,以示需要再次发送该信号。
1 /* t_sigqueue.c */ 2 #define _POSIX_C_SOURCE 199309 3 #include <signal.h> 4 #include "tlpi_hdr.h" 5 6 int main(int argc, char* argv[]) 7 { 8 int sig, numSigs, j, sigData; 9 union sigval sv; 10 11 if (argc < 4 || strcmp(argv[1], "--help") == 0) 12 usageErr("%s pid sig-num data [num-sigs]\n", argv[0]); 13 14 /* Display our PID and UID, so that they can be compared with the 15 corresponding fields of the siginfo_t argument supplied to the 16 handler in the receiving process */ 17 printf("%s: PID is %ld, UID is %ld\n", argv[0], (long)getpid(), (long)getuid()); 18 19 sig = getInt(argv[2], 0, "sig-num"); 20 sigData = getInt(argv[3], GN_ANY_BASE, "data"); 21 numSigs = (argc > 4) ? getInt(argv[4], GN_GT_O, "num-sigs") : 1; 22 23 for (j = 0; j < numSigs; j++) { 24 sv.sival_int = sigData + j; 25 if (sigqueue(getLong(argv[1], 0, "pid"), sig, sv) == -1) 26 errExit("sigqueue %d", j); 27 } 28 29 exit(EXIT_SUCCESS); 30 }
处理实时信号
可以向标准信号一样,使用常规(单参数)信号处理器来处理实时信号。也可以用带有 3 个参数的信号处理器函数来处理实时信号,其建立则会用到 SA_SIGINFO 标志。一旦采用了 SA_SIGINFO 标志,传递给信号处理器函数的第二个参数将是一个 siginfo_t 结构,内含实时信号的附加信息,会设置如下字段:
- si_signo 字段,其值与传递给信号处理器函数的第一个参数相同
- si_code 字段表示信号来源。对于通过 sigqueue() 发送的实时信号来说,该字段值总是为 SI_QUEUE
- si_value 字段所含数据,由进程于使用 sigqueue() 发送信号时在 value 参数(sigval union)中指定。
- si_pid 和 si_uid 字段分别包含信号发送进程的进程 ID 和实际用户 ID
1 /* catch_rtsigs.c */ 2 #define _GNU_SOURCE 3 #include <string.h> 4 #include <signal.h> 5 #include "tlpi_hdr.h" 6 7 static volatile int handlerSleepTime; 8 static volatile int sigCnt = 0; /* Number of signals received */ 9 static volatile int allDone = 0; 10 11 static void siginfoHandler(int sig, siginfo_t* si, void* ucontext) 12 { 13 /* UNSAFE: This handler uses non-async-signal-safe functions (printf())*/ 14 /* SIGINT or SIGTERM can be used to terminate program */ 15 if (sig == SIGINT || sig == SIGTERM) { 16 allDone = 1; 17 return; 18 } 19 20 sigCnt++; 21 printf("caugth signal %d\n", sig); 22 printf(" si_signo = %d, si_code = %d (%s), ", si->si_signo, si->si_code, 23 (si->si_code == SI_USER) ? "SI_USER" : 24 (si->si_code == SI_QUEUE) ? "SI_QUEUE" : "other"); 25 printf("si_value = %d\n", si->si_value.sival_int); 26 printf(" si_pid = %ld, si_uid = %ld\n", (long)si->si_pid, (long)si->si_uid); 27 28 sleep(handlerSleepTime); 29 } 30 31 int main(int argc, char* argv[]) 32 { 33 struct sigaction sa; 34 int sig; 35 sigset_t prevMask, blockMask; 36 37 if (argc > 1 && strcmp(argv[1], "--help") == 0) 38 usageErr("%s [block-time [handler-sleep-time]]\n", argv[0]); 39 40 printf("%s: PID is %ld\n", argv[0], (long)getpid()); 41 42 handlerSleepTime = (argc > 2) ? getInt(argv[2], GN_NONNEG, "handler-sleep-time") : 1; 43 44 /* Establish handler for most signals, During execution of the handler, 45 mask all other signals to prevent handlers recursively interrupting 46 each other (which would make the output hard to read). */ 47 sa.sa_sigaction = siginfoHandler; 48 sa.sa_flags = SA_SIGINFO; 49 sigfillset(&sa.sa_mask); 50 51 for (sig = 1; sig < NSIG; sig++) 52 if (sig != SIGTSTP && sig != SIGQUIT) 53 sigaction(sig, &sa, NULL); 54 55 /* Optionally block signals and sleep, allowing signals to be sent to us 56 before they are unblocked and handled */ 57 if (argc > 1) { 58 sigfillset(&blockMask); 59 sigdelset(&blockMask, SIGINT); 60 sigdelset(&blockMask, SIGTERM); 61 62 if (sigprocmask(SIG_SETMASK, &blockMask, &prevMask) == -1) 63 errExit("sigprocmask"); 64 65 printf("%s: signals blocked - sleeping %s seconds\n", argv[0], argv[1]); 66 sleep(getInt(argv[1], GN_GT_O, "block-time")); 67 printf("%s: sleep complete\n", argv[0]); 68 69 if (sigprocmask(SIG_SETMASK, &prevMask, NULL) == -1) 70 errExit("sigprocmask"); 71 } 72 73 while (!allDone) 74 pause(); 75 }
sigsuspend()
#include <signal.h> int sigsuspend(const sigset_t* mask); (Normally) returns -1 with errno set to EINTR
sigsuspend() 将解除信号阻塞和挂起进程这两个动作封装成一个原子操作。将以 mask 所指向的信号集来替换进程的信号掩码,然后挂起进程的执行,直到其捕获到信号,并从信号处理器中返回。一旦处理器返回,sigsuspend()会将进程信号掩码恢复为调用前的值。
调用 sigsuspend(),相当于以不可中断方式执行如下操作:
sigprocmask(SIG_SETMASK, &mask, &prevMask); /* Assign new mask */ pause(); sigprocmask(SIG_SETMASK, &prevMask, NULL); /* Restore old mask */
若 sigsuspend() 因信号的传递而中断,则将返回 -1,并将 errno 置为 EINTR。如果 mask 指向的地址无效,则 sigsuspend() 调用失败,并将 errno 置为 EFAULT。
以同步方式等待信号
使用 sigsuspend() 需要编写信号处理器函数,还需要应对信号异步传递所带来的复杂性。对于某些应用而言,这种方法过于复杂。作为替代方案,可以利用 sigwaitinfo() 系统调用来同步接收信号。
#define _POSIX_C_SOURCE 199309 #include <signal.h> int sigwaitinfo(const sigset_t* set, siginfo_t* info); Returns number of delivered signal on success, or -1 on error
sigwaitinfo() 系统调用挂起进程的执行,直至 set 指向信号集中的某一信号抵达。如果调用 sigwaitinfo() 时,set 中的某一信号已经处于等待状态,那么 sigwaitinfo() 将立即返回。传递来的信号就此从进程的等待信号队列中移除,并且将返回信号编号作为函数结果。info 参数如果不为空,则会指向经过初始化处理的 siginfo_t 结构,其中所含信息与提供给信号处理器函数的 siginfo_t 参数相同。
sigwaitinfo() 所接受信号的传递顺序和排队特性与信号处理器所捕获的信号相同,就是说,不对标准信号进行排队处理,对实时信号进行排队处理,并且对实时信号的传递遵循低编号优先的原则。
除了卸去编写信号处理器的负担之外,使用 sigwaitinfo() 来等待信号也要比信号处理器外加 sigsuspend() 的组合稍快一些。
1 /* t_sigwaitinfo.c */ 2 #define _GNU_SOURCE 3 #include <string.h> 4 #include <signal.h> 5 #include <time.h> 6 #include "tlpi_hdr.h" 7 8 int main(int argc, char* argv[]) 9 { 10 int sig; 11 siginfo_t si; 12 sigset_t allSigs; 13 14 if (argc > 1 && strcmp(argv[1], "--help") == 0) 15 usageErr("%s [delay-secs]\n", argv[0]); 16 17 printf("%s: PID is %ld\n", argv[0], (long)getpid()); 18 19 /* Block all signals (except SIGKILL and SIGSTOP) */ 20 21 sigfillset(&allSigs); 22 if (sigprocmask(SIG_SETMASK, &allSigs, NULL) == -1) 23 errExit("sigprocmask"); 24 25 printf("%s: signals blocked\n", argv[0]); 26 27 if (argc > 1) { /* Delay so that signals can be sent to us */ 28 printf("%s: about to delay %s seconds\n", argv[0], argv[1]); 29 sleep(getInt(argv[1], GN_GT_O, "delay-secs")); 30 printf("%s: finished delay\n", argv[0]); 31 } 32 33 for (;;) { /* Fetch signals until SIGINT (^c) or SIGTERM */ 34 sig = sigwaitinfo(&allSigs, &si); 35 if (sig == -1) 36 errExit("sigwaitinfo"); 37 38 if (sig == SIGINT || sig == SIGTERM) 39 exit(EXIT_SUCCESS); 40 41 printf("got signal: %d (%s)\n", sig, strsignal(sig)); 42 printf(" si_signo = %d, si_code = %d (%s), ", si.si_signo, si.si_code, 43 (si.si_code == SI_USER) ? "SI_USER" : 44 (si.si_code == SI_QUEUE) ? "SI_QUEUE" : "other"); 45 printf("si_value = %d\n", si.si_value.sival_int); 46 printf(" si_pid = %ld, si_uid = %ld\n", (long)si.si_pid, (long)si.si_uid); 47 } 48 }