【摘自《Linux/Unix系统编程手册》】
较之于标准信号,实时信号的优势如下:
- 实时信号的信号范围有所扩大,可应用于应用程序自定义的目的。而标准信号中可供应用随意使用的信号仅有两个:SIGUSR1 和 SIGUSR2。
- 对实时信号所采取的是队列化管理。如果将某一实时信号的多个实例发送给一进程,那么将会多次传递信号。相反,如果某一标准信号已经在等待某一进程,而此时即使再次向该进程发送信号的实例,信号也只会传递一次。
- 当发送一个实时信号时,可为信号指定伴随数据(一整型数或者指针值),供接收进程的信号处理器获取。
- 不同实时信号的传递顺序得到保障。如果有多个不同的实时信号处于等待状态,那么将率先传递具有最小编号的信号。换言之,信号的编号越小,其优先级越高。如果是同一类型的多个信号在排队,那么信号(以及伴随数据)的传递顺序与信号发送来时的顺序保持一致。
使用实时信号
- 发送进程使用 sigqueue() 系统调用来发送信号及其伴随数据。
- 要为该信号建立一个处理器函数,接收进程应以 SA_SIGINFO 标志发起对 sigaction() 的调用。因此,调用信号处理器时就会附带额外参数,其中之一是实时信号的伴随数据。
在 Linux 中,即使接收进程在建立信号处理器时并未指定 SA_SIGINFO 标志,也能对实时信号进行队列化管理(但在这种情况下,将不可能获得信号的伴随数据)。
发送实时信号
系统调用 sigqueue() 将由 sig 指定的实时信号发送给有 pid 指定的进程
#define _POSIX_C_SOURCE 199309#includeint 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 #include4 #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 the15 corresponding fields of the siginfo_t argument supplied to the16 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 #include4 #include 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 interrupting46 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 }
#includeint 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#includeint 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 #include4 #include 5 #include 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 }