我们来深入学习 rt_sigtimedwait 系统调用
1. 函数介绍 在 Linux 系统中,信号是一种重要的进程间通信和通知机制。通常,我们会为信号设置一个处理函数(使用 sigaction),当信号到来时,内核会中断程序的正常执行流程,转而去执行我们的处理函数。这是一种异步的处理方式。
data-ad-format="fluid"
data-ad-layout-key="-7k+ex-4a-9w+4a">
但有时候,我们希望程序能够主动地、同步地等待某个信号的到来。也就是说,程序执行到某一点,就停下来,专门等着某个信号发生,信号来了,程序再接着往下走。这有点像在火车站等车,车来了(信号到了),你再上车(继续执行)。
rt_sigtimedwait(用户空间通常通过 sigtimedwait 或 sigwaitinfo 函数调用)就是这样一个工具。它允许你的程序明确地说:“我现在要等着信号 SIGUSR1 或 SIGRTMIN 中的任意一个到来”。它比 pause()(等待任意信号)或 sigsuspend()(等待信号但不指定具体哪个)更加精确。
更棒的是,它还可以:
简单来说,sigtimedwait 就像是一个功能强大的“信号接收器”,你可以指定接收哪些信号,可以得到信号的“包裹单”(详细信息),还可以设置一个“闹钟”(超时时间)。
2. 函数原型 1 2 3 4 5 6 7 8 9 #include <signal.h> // 带超时时间的版本 int sigtimedwait(const sigset_t *set, siginfo_t *info, const struct timespec *timeout); // 不带超时时间的版本 (无限期等待) int sigwaitinfo(const sigset_t *set, siginfo_t *info); // sigwaitinfo(set, info) 实际上等价于 sigtimedwait(set, info, NULL);
3. 功能 原子地将调用进程的信号掩码临时设置为与 set 中指定信号互补的掩码(即,阻塞所有除了 set 中信号之外的信号),然后挂起进程,等待 set 中的任何一个信号到来,或者等待 timeout 指定的时间超时。
关键点:它临时解除阻塞 set 中的信号,而阻塞其他所有信号。
4. 参数 set:
info:
timeout:
const struct timespec * 类型。
一个指向 timespec 结构体的指针,用于指定最长等待时间。timespec 结构包含 tv_sec(秒)和 tv_nsec(纳秒)两个成员。
如果传入 NULL(就像 sigwaitinfo 那样),则函数会无限期等待,直到 set 中的某个信号到达。
5. 返回值
成功(等到了 set 中的信号):返回接收到的那个信号的编号。
超时(对于 sigtimedwait,在指定时间内未收到信号):返回 -1,并设置 errno 为 EAGAIN。
被其他信号中断(例如,收到一个不在 set 中且未被阻塞的信号):返回 -1,并设置 errno 为 EINTR 或其他相关错误码。
其他错误(例如参数无效):返回 -1,并设置相应的 errno。
6. 相似函数或关联函数
sigwaitinfo: sigtimedwait 的一个特例,等价于 sigtimedwait(set, info, NULL),即无限期等待。
sigsuspend: 临时改变信号掩码并挂起等待,但不指定等待哪个信号,也不获取信号信息或设置超时。
pause: 简单地挂起进程直到收到任何信号。
sigprocmask: 用于检查或修改当前进程的信号屏蔽字。在使用 sigtimedwait 之前,通常需要先阻塞要等待的信号。
siginfo_t: 包含信号详细信息的结构体。
sigqueue: 用于向另一个进程发送信号和数据,常与 sigtimedwait/sigwaitinfo 配对使用,实现进程间数据传递。
7. 示例代码 下面是一个综合示例,演示如何使用 sigwaitinfo(无限等待)和 sigtimedwait(带超时)来等待信号,并获取信号附带的信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 #define _GNU_SOURCE // 启用 GNU 扩展以使用 sigqueue 等 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <signal.h> #include <string.h> #include <sys/types.h> // 包含 pid_t #include <sys/wait.h> // 包含 wait #include <errno.h> // 包含 errno, EAGAIN #include <time.h> // 包含 timespec int main() { pid_t pid; sigset_t wait_set; // 定义要等待的信号集 siginfo_t info; // 用于接收信号信息 struct timespec timeout; // 超时时间 int sig_received; // 存储接收到的信号号 union sigval value; // 用于发送数据 printf("Main (Parent) process PID: %d\n", getpid()); // 1. 创建要等待的信号集:SIGUSR1 和 SIGRTMIN (一个实时信号) sigemptyset(&wait_set); // 初始化为空集 sigaddset(&wait_set, SIGUSR1); // 添加 SIGUSR1 sigaddset(&wait_set, SIGRTMIN); // 添加 SIGRTMIN printf("Configured to wait for SIGUSR1 (%d) and SIGRTMIN (%d)\n", SIGUSR1, SIGRTMIN); // 2. 非常重要:在调用 sigtimedwait/sigwaitinfo 之前, // 必须先阻塞掉你打算等待的信号。 // 这样可以确保这些信号在发送和等待之间不会被意外地异步处理掉。 if (sigprocmask(SIG_BLOCK, &wait_set, NULL) == -1) { perror("sigprocmask BLOCK"); exit(EXIT_FAILURE); } printf("Blocked SIGUSR1 and SIGRTMIN to queue them for synchronous waiting.\n"); // 3. Fork 一个子进程来发送信号 pid = fork(); if (pid == -1) { perror("fork"); exit(EXIT_FAILURE); } if (pid == 0) { // --- Child Process --- printf("\nChild process (PID: %d) started.\n", getpid()); sleep(2); // 等待 2 秒,让父进程先进入等待状态 printf("Child: Sending SIGUSR1 to parent (PID: %d) using kill()...\n", getppid()); if (kill(getppid(), SIGUSR1) == -1) { // 使用 kill 发送简单信号 perror("kill SIGUSR1"); exit(EXIT_FAILURE); } sleep(3); // 再等待 3 秒 // 使用 sigqueue 发送实时信号和数据 value.sival_int = 12345; printf("Child: Sending SIGRTMIN with value %d to parent using sigqueue()...\n", value.sival_int); if (sigqueue(getppid(), SIGRTMIN, value) == -1) { perror("sigqueue SIGRTMIN"); exit(EXIT_FAILURE); } sleep(3); // 再等待 3 秒 printf("Child: Sending SIGUSR1 again to parent using kill()...\n"); if (kill(getppid(), SIGUSR1) == -1) { perror("kill SIGUSR1 (again)"); exit(EXIT_FAILURE); } printf("Child process finished.\n"); exit(EXIT_SUCCESS); } else { // --- Parent Process --- printf("\nParent process now waiting for signals synchronously...\n"); // 4. 使用 sigwaitinfo 无限期等待第一个信号 printf("\n--- Parent: Calling sigwaitinfo (will wait indefinitely) ---\n"); printf("Parent: Waiting for either SIGUSR1 or SIGRTMIN...\n"); // sigwaitinfo 会解除对 wait_set 中信号的阻塞,并等待其中一个到来 sig_received = sigwaitinfo(&wait_set, &info); // 等待 set 中任意一个信号 if (sig_received == -1) { perror("sigwaitinfo"); // 通常不会发生,除非被其他未阻塞的信号中断 } else { printf("Parent: sigwaitinfo successfully returned.\n"); printf(" Parent: Received signal number: %d\n", sig_received); if (sig_received == SIGUSR1) { printf(" Parent: It was SIGUSR1.\n"); printf(" Parent: Sender PID: %d\n", info.si_pid); // SIGUSR1 通过 kill 发送,通常 si_code 是 SI_USER printf(" Parent: si_code: %d (SI_USER=%d)\n", info.si_code, SI_USER); } } // 5. 使用 sigtimedwait 等待下一个信号,设置 5 秒超时 printf("\n--- Parent: Calling sigtimedwait (with 5s timeout) ---\n"); timeout.tv_sec = 5; // 5 秒 timeout.tv_nsec = 0; // 0 纳秒 printf("Parent: Waiting for next signal (timeout set to 5 seconds)...\n"); sig_received = sigtimedwait(&wait_set, &info, &timeout); if (sig_received == -1) { if (errno == EAGAIN) { printf("Parent: sigtimedwait timed out. No signal arrived within 5 seconds.\n"); } else { perror("Parent: sigtimedwait"); // 其他错误,如被其他信号中断 (EINTR) } } else { printf("Parent: sigtimedwait successfully returned before timeout.\n"); printf(" Parent: Received signal number: %d\n", sig_received); if (sig_received == SIGRTMIN) { // SIGRTMIN 通过 sigqueue 发送,si_code 是 SI_QUEUE if (info.si_code == SI_QUEUE) { printf(" Parent: It was SIGRTMIN sent via sigqueue().\n"); printf(" Parent: Sender PID: %d\n", info.si_pid); printf(" Parent: Attached integer value: %d\n", info.si_value.sival_int); } } } // 6. 再次使用 sigwaitinfo 等待信号 (应该很快收到之前发送的 SIGUSR1) printf("\n--- Parent: Calling sigwaitinfo again ---\n"); printf("Parent: Waiting for any of {SIGUSR1, SIGRTMIN} again...\n"); sig_received = sigwaitinfo(&wait_set, &info); if (sig_received != -1) { printf("Parent: sigwaitinfo successfully returned.\n"); printf(" Parent: Received signal number: %d\n", sig_received); if (sig_received == SIGUSR1) { printf(" Parent: It was SIGUSR1.\n"); printf(" Parent: Sender PID: %d\n", info.si_pid); } } else { perror("Parent: sigwaitinfo (second call)"); } // 7. 等待子进程结束 if (wait(NULL) == -1) { perror("wait"); } printf("\nParent: Confirmed child process (PID %d) has finished. Parent exiting.\n", pid); } return 0; }
编译和运行:
1 2 3 4 5 6 # 假设代码保存在 sigtimedwait_example.c 中 gcc -o sigtimedwait_example sigtimedwait_example.c # 运行程序 ./sigtimedwait_example
预期输出 (时间点可能略有差异):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 Main (Parent) process PID: 12345 Configured to wait for SIGUSR1 (10) and SIGRTMIN (34) Blocked SIGUSR1 and SIGRTMIN to queue them for synchronous waiting. Parent process now waiting for signals synchronously... --- Parent: Calling sigwaitinfo (will wait indefinitely) --- Parent: Waiting for either SIGUSR1 or SIGRTMIN... Child process (PID: 12346) started. Child: Sending SIGUSR1 to parent (PID: 12345) using kill()... Parent: sigwaitinfo successfully returned. Parent: Received signal number: 10 Parent: It was SIGUSR1. Parent: Sender PID: 12346 Parent: si_code: 0 (SI_USER=0) # SI_USER 的值在不同系统上可能不同,通常是 0 --- Parent: Calling sigtimedwait (with 5s timeout) --- Parent: Waiting for next signal (timeout set to 5 seconds)... Child: Sending SIGRTMIN with value 12345 to parent using sigqueue()... Parent: sigtimedwait successfully returned before timeout. Parent: Received signal number: 34 Parent: It was SIGRTMIN sent via sigqueue(). Parent: Sender PID: 12346 Parent: Attached integer value: 12345 --- Parent: Calling sigwaitinfo again --- Parent: Waiting for any of {SIGUSR1, SIGRTMIN} again... Child: Sending SIGUSR1 again to parent using kill()... Child process finished. Parent: sigwaitinfo successfully returned. Parent: Received signal number: 10 Parent: It was SIGUSR1. Parent: Sender PID: 12346 Parent: Confirmed child process (PID 12346) has finished. Parent exiting.
关键点总结:
预先阻塞信号:在调用 sigtimedwait/sigwaitinfo 之前,必须使用 sigprocmask(SIG_BLOCK, …) 阻塞你打算等待的信号。这是确保信号能被这些函数捕获的关键步骤。
原子性等待:这两个函数原子性地执行“解除对指定信号的阻塞”和“挂起等待”操作,避免了在设置掩码和挂起之间可能发生的竞态条件。
精确等待:通过 set 参数,你可以精确指定等待哪一组信号。
超时控制:sigtimedwait 的 timeout 参数让你可以避免程序无限期地等待。
信息获取:通过 info 参数,你可以获得信号的来源、发送方式(kill vs sigqueue)以及附带的数据(使用 sigqueue 发送时),这使得信号成为一种强大的进程间通信机制。
https://www.calcguide.tech/2025/08/24/rt-sigtimedwait系统调用及示例/
rt_sigtimedwait系统调用及示例-CSDN博客