rt_sigtimedwait系统调用及示例

我们来深入学习 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. 相似函数或关联函数

  • sigwaitinfosigtimedwait 的一个特例,等价于 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.

关键点总结:

  1. 预先阻塞信号:在调用 sigtimedwait/sigwaitinfo 之前,必须使用 sigprocmask(SIG_BLOCK, ...) 阻塞你打算等待的信号。这是确保信号能被这些函数捕获的关键步骤。
  2. 原子性等待:这两个函数原子性地执行“解除对指定信号的阻塞”和“挂起等待”操作,避免了在设置掩码和挂起之间可能发生的竞态条件。
  3. 精确等待:通过 set 参数,你可以精确指定等待哪一组信号。
  4. 超时控制sigtimedwait 的 timeout 参数让你可以避免程序无限期地等待。
  5. 信息获取:通过 info 参数,你可以获得信号的来源、发送方式(kill vs sigqueue)以及附带的数据(使用 sigqueue 发送时),这使得信号成为一种强大的进程间通信机制。

rt_sigtimedwait系统调用及示例-CSDN博客

此条目发表在linux文章分类目录。将固定链接加入收藏夹。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注