了解实时信号

本文介绍了实时信号在Linux/Unix系统编程中的特点,如信号范围扩大、队列化管理和优先级排序。详细讲解了如何使用sigqueue()发送信号及伴随数据,以及如何使用sigwaitinfo()同步接收信号,包括信号处理器的设置和信号的传递顺序。
摘要由CSDN通过智能技术生成

【摘自《Linux/Unix系统编程手册》】

较之于标准信号,实时信号的优势如下:

  • 实时信号的信号范围有所扩大,可应用于应用程序自定义的目的。而标准信号中可供应用随意使用的信号仅有两个:SIGUSR1 和 SIGUSR2。
  • 对实时信号所采取的是队列化管理。如果将某一实时信号的多个实例发送给一进程,那么将会多次传递信号。相反,如果某一标准信号已经在等待某一进程,而此时即使再次向该进程发送信号的实例,信号也只会传递一次。
  • 当发送一个实时信号时,可为信号指定伴随数据(一整型数或者指针值),供接收进程的信号处理器获取。
  • 不同实时信号的传递顺序得到保障。如果有多个不同的实时信号处于等待状态,那么将率先传递具有最小编号的信号。换言之,信号的编号越小,其优先级越高。如果是同一类型的多个信号在排队,那么信号(以及伴随数据)的传递顺序与信号发送来时的顺序保持一致。

【推荐阅读】

一文看懂linux内核详解

linux内核内存管理-写时复制

深入了解使用linux查看磁盘io使用情况

使用实时信号

  • 发送进程使用 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 }

原文作者:Kjing

原文地址:实时信号 - Kjing - 博客园(版权归原文作者所有,侵权留言联系删除)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值