我们来深入学习 rt_sigtimedwait
系统调用
1. 函数介绍
在 Linux 系统中,信号是一种重要的进程间通信和通知机制。通常,我们会为信号设置一个处理函数(使用 sigaction
),当信号到来时,内核会中断程序的正常执行流程,转而去执行我们的处理函数。这是一种异步的处理方式。
但有时候,我们希望程序能够主动地、同步地等待某个信号的到来。也就是说,程序执行到某一点,就停下来,专门等着某个信号发生,信号来了,程序再接着往下走。这有点像在火车站等车,车来了(信号到了),你再上车(继续执行)。
rt_sigtimedwait
(用户空间通常通过 sigtimedwait
或 sigwaitinfo
函数调用)就是这样一个工具。它允许你的程序明确地说:“我现在要等着信号 SIGUSR1
或 SIGRTMIN
中的任意一个到来”。它比 pause()
(等待任意信号)或 sigsuspend()
(等待信号但不指定具体哪个)更加精确。
更棒的是,它还可以:
- 获取信号的详细信息:比如信号是谁发的?附带了什么数据?(通过
siginfo_t
结构体) - 设置等待超时:我不想一直等下去,最多等 5 秒钟,如果 5 秒内信号没来,就继续干别的事。这避免了程序无限期地挂起。
简单来说,sigtimedwait
就像是一个功能强大的“信号接收器”,你可以指定接收哪些信号,可以得到信号的“包裹单”(详细信息),还可以设置一个“闹钟”(超时时间)。
2. 函数原型
#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
:const sigset_t *
类型。- 一个指向信号集(
sigset_t
)的指针。这个集合定义了调用者愿意等待的信号。在sigtimedwait
执行期间,只有这个集合中的信号才不会被阻塞。
info
:siginfo_t *
类型。- 一个指向
siginfo_t
结构体的指针。如果函数成功等到一个信号,该结构体会被填充为这个信号的详细信息(例如发送者 PID、附带的整数值等)。如果你不关心这些信息,可以传NULL
。
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
(带超时)来等待信号,并获取信号附带的信息。
#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;
}
编译和运行:
# 假设代码保存在 sigtimedwait_example.c 中
gcc -o sigtimedwait_example sigtimedwait_example.c
# 运行程序
./sigtimedwait_example
预期输出 (时间点可能略有差异):
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
vssigqueue
)以及附带的数据(使用sigqueue
发送时),这使得信号成为一种强大的进程间通信机制。