rt_sigqueueinfo系统调用及示例

我们来深入学习 rt_sigqueueinfo 系统调用,在 Linux 中,进程间通信(IPC)有多种方式,信号(Signal)是其中一种轻量级的通知机制。通常,我们使用 kill() 来发送一个简单的信号,或者使用 sigqueue() 来发送一个信号并附带一小段数据(一个整数或指针)。

1. 函数介绍

在 Linux 中,进程间通信(IPC)有多种方式,信号(Signal)是其中一种轻量级的通知机制。通常,我们使用 kill() 来发送一个简单的信号,或者使用 sigqueue() 来发送一个信号并附带一小段数据(一个整数或指针)。

rt_sigqueueinfo 是一个更底层、更强大但也更危险的系统调用。它允许你向另一个进程发送一个信号,并且可以完全自定义随信号一起传递的 siginfo_t 结构体中的所有信息。这包括发送者的 PID、信号产生的原因代码(si_code)、以及附带的数据等。

为什么危险?
因为它强大的自定义能力意味着发送者可以伪造信号的来源和原因。例如,一个普通用户进程可以伪造自己是内核(si_code 为 SI_KERNEL)或其他进程发送的信号。因此,这个系统调用通常受到严格的权限检查,普通应用程序一般不应该直接使用它。

什么时候会用到?
主要是在实现更高级别的信号发送函数(如 sigqueue)时,由 C 库(glibc)内部调用,或者在一些非常特殊的、需要精确控制信号信息的系统级编程中。

对于 Linux 编程小白:你更可能使用 sigqueue() 函数,它更安全、更易用,并且能满足绝大多数需要发送带数据信号的场景。

2. 函数原型

// 这是底层系统调用,直接在用户空间调用比较复杂且需要特殊权限
#include <sys/syscall.h>   // 包含系统调用号
#include <unistd.h>        // 包含 syscall 函数
#include <signal.h>        // 包含 siginfo_t 定义

long syscall(SYS_rt_sigqueueinfo, pid_t pid, int sig, siginfo_t *uinfo);
// 注意:用户空间标准 C 库通常不直接提供 rt_sigqueueinfo 的包装函数

用户空间更常用、更安全的替代函数:

#include <signal.h>

int sigqueue(pid_t pid, int sig, const union sigval value);

3. 功能

向指定进程 ID (pid) 发送指定信号 (sig),并允许发送者完全指定 siginfo_t 结构体 (uinfo) 中包含的详细信息。

4. 参数

  • pid:
    • pid_t 类型。
    • 目标进程的进程 ID (PID)。信号将被发送给这个进程。
  • sig:
    • int 类型。
    • 要发送的信号编号,例如 SIGUSR1SIGRTMIN 等。注意,不能发送 SIGKILL 和 SIGSTOP
  • uinfo:
    • siginfo_t * 类型。
    • 一个指向 siginfo_t 结构体的指针。这个结构体包含了你想随信号一起传递给目标进程的所有详细信息。调用者需要自己填充这个结构体的各个字段。

5. 返回值

  • 成功: 返回 0。
  • 失败: 返回 -1,并设置全局变量 errno 来指示具体的错误原因:
    • EAGAIN: (对于实时信号) 已达到接收者排队信号的最大数量限制 (RLIMIT_SIGPENDING)。
    • EPERM: 调用者没有权限发送信号给目标进程(例如,普通用户不能向 root 进程发送任意伪造的信号)。
    • EINVALsig 是无效的信号号,或者 uinfo 中的 si_code 是无效的或不允许由用户设置的。
    • ESRCH: 找不到 pid 指定的进程或进程组。

6. 相似函数或关联函数

  • sigqueue: 用户空间更安全、更常用的发送信号和数据的方法。它只允许设置 siginfo_t 中的 si_value 字段,其他字段由内核自动填充。
  • kill: 发送一个不带附加数据的信号。
  • siginfo_t: 包含信号详细信息的结构体。主要字段包括:
    • si_signo: 信号编号(内核会设置)。
    • si_errno: 如果非零,表示伴随信号的错误代码(内核会设置)。
    • si_code: 信号产生的原因代码(例如 SI_USERSI_QUEUESI_TIMER 等)。这是 rt_sigqueueinfo 允许用户自定义的关键字段,但也因此危险。
    • si_pid: 发送信号的进程 ID(通常由内核设置,但 rt_sigqueueinfo 可能允许伪造)。
    • si_uid: 发送信号的用户 ID(通常由内核设置)。
    • si_value: 伴随信号的用户数据(union sigval,包含 sival_int 或 sival_ptr)。
    • … 还有其他针对特定信号类型的字段。

7. 示例代码

由于直接使用 rt_sigqueueinfo 需要特殊权限且容易误用,下面的示例将演示如何通过 syscall 调用它,并说明其风险。同时,也会提供一个使用标准 sigqueue 的对比示例。

警告:直接使用 rt_sigqueueinfo 可能会因为权限问题而失败,尤其是在没有 root 权限的情况下。

#define _GNU_SOURCE // 启用 GNU 扩展
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <sys/syscall.h> // 包含 syscall 和 SYS_rt_sigqueueinfo
#include <errno.h>
#include <sys/types.h> // 包含 pid_t

// 用于接收信号的处理函数 (使用 SA_SIGINFO 获取详细信息)
void signal_handler(int sig, siginfo_t *info, void *context) {
    printf("Received signal %d\n", sig);
    printf("  si_signo: %d\n", info->si_signo);
    printf("  si_code:  %d", info->si_code);
    switch(info->si_code) {
        case SI_USER: printf(" (SI_USER: kill, sigsend or raise)\n"); break;
        case SI_QUEUE: printf(" (SI_QUEUE: sigqueue)\n"); break;
        case SI_KERNEL: printf(" (SI_KERNEL: sent by kernel)\n"); break;
        case SI_TKILL: printf(" (SI_TKILL: tkill or tgkill)\n"); break;
        default: printf(" (Other code)\n"); break;
    }
    printf("  si_pid:   %d\n", info->si_pid);
    printf("  si_uid:   %d\n", info->si_uid);
    if (info->si_code == SI_QUEUE) {
        printf("  si_value.sival_int: %d\n", info->si_value.sival_int);
        printf("  si_value.sival_ptr: %p\n", info->si_value.sival_ptr);
    }
    // 注意:这里为了演示打印了信息,但实际信号处理函数应只调用异步信号安全函数
}

int main() {
    pid_t my_pid = getpid();
    struct sigaction sa;
    siginfo_t si_to_send;
    union sigval data_to_send = {.sival_int = 999};

    printf("My PID is: %d\n", my_pid);

    // 1. 设置 SIGUSR1 的处理函数,使用 SA_SIGINFO 获取详细信息
    memset(&sa, 0, sizeof(sa));
    sa.sa_sigaction = signal_handler; // 注意是 sa_sigaction
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_SIGINFO; // 必须设置此标志
    if (sigaction(SIGUSR1, &sa, NULL) == -1) {
        perror("sigaction");
        exit(EXIT_FAILURE);
    }


    // --- 方法 1: 使用标准的 sigqueue (推荐) ---
    printf("\n--- Using sigqueue (Recommended) ---\n");
    printf("Sending SIGUSR1 with data %d using sigqueue()...\n", data_to_send.sival_int);
    if (sigqueue(my_pid, SIGUSR1, data_to_send) == -1) {
        perror("sigqueue");
        // sigqueue 失败通常是因为资源限制或信号无效
    }
    sleep(1); // 给点时间处理信号


    // --- 方法 2: 使用 rt_sigqueueinfo (不推荐,仅供演示) ---
    printf("\n--- Using rt_sigqueueinfo (Not Recommended) ---\n");
    // 2. 准备 siginfo_t 结构体
    memset(&si_to_send, 0, sizeof(si_to_send));
    si_to_send.si_signo = SIGUSR1;  // 信号号
    si_to_send.si_code = SI_QUEUE;  // 伪造为 sigqueue 发送的
    si_to_send.si_pid = my_pid;     // 伪造 PID
    si_to_send.si_uid = getuid();   // 伪造 UID
    si_to_send.si_value = data_to_send; // 附带数据

    printf("Attempting to send SIGUSR1 with forged info using rt_sigqueueinfo()...\n");
    printf("(This will likely fail with EPERM unless run with special privileges)\n");
    // 3. 调用底层系统调用
    long result = syscall(SYS_rt_sigqueueinfo, my_pid, SIGUSR1, &si_to_send);

    if (result == -1) {
        perror("rt_sigqueueinfo");
        printf("Error: rt_sigqueueinfo failed. This is expected for unprivileged processes.\n");
        printf("Errno: %d\n", errno);
        if (errno == EPERM) {
            printf("Reason: EPERM - Operation not permitted (insufficient privileges to forge signal info).\n");
        }
    } else {
        printf("rt_sigqueueinfo succeeded (unexpected for unprivileged user).\n");
    }

    sleep(1); // 给点时间处理可能的信号

    // --- 方法 3: 使用 rt_sigqueueinfo 伪造为内核发送 (非常不推荐) ---
    printf("\n--- Forging signal as from Kernel (Highly Not Recommended) ---\n");
    si_to_send.si_code = SI_KERNEL; // 尝试伪造为内核发送
    printf("Attempting to forge signal as sent by the KERNEL...\n");
    result = syscall(SYS_rt_sigqueueinfo, my_pid, SIGUSR1, &si_to_send);

    if (result == -1) {
        perror("rt_sigqueueinfo (forged as kernel)");
        printf("Error: Forging kernel signal failed (as expected).\n");
    } else {
        printf("rt_sigqueueinfo (forged as kernel) succeeded (highly unexpected!).\n");
    }

    sleep(1);

    printf("\nProgram finished.\n");
    return 0;
}

使用 sigqueue 的简单对比示例 (推荐方式):

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>

void signal_handler(int sig, siginfo_t *info, void *context) {
    if (info->si_code == SI_QUEUE) {
        printf("Received signal %d with value: %d\n", sig, info->si_value.sival_int);
    } else {
        printf("Received signal %d (not from sigqueue)\n", sig);
    }
}

int main() {
    pid_t pid;
    struct sigaction sa;
    union sigval value1 = {.sival_int = 100};
    union sigval value2 = {.sival_int = 200};

    // 设置信号处理函数
    memset(&sa, 0, sizeof(sa));
    sa.sa_sigaction = signal_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_SIGINFO;
    if (sigaction(SIGUSR1, &sa, NULL) == -1) {
        perror("sigaction");
        exit(EXIT_FAILURE);
    }

    pid = fork();
    if (pid == -1) {
        perror("fork");
        exit(EXIT_FAILURE);
    }

    if (pid == 0) {
        // Child: 发送信号给父进程
        sleep(1);
        printf("Child: Sending SIGUSR1 with value %d\n", value1.sival_int);
        if (sigqueue(getppid(), SIGUSR1, value1) == -1) {
            perror("sigqueue 1");
        }

        sleep(1);
        printf("Child: Sending SIGUSR1 with value %d\n", value2.sival_int);
        if (sigqueue(getppid(), SIGUSR1, value2) == -1) {
            perror("sigqueue 2");
        }

        exit(EXIT_SUCCESS);
    } else {
        // Parent: 等待信号
        printf("Parent: Waiting for signals...\n");
        sleep(5); // 等待子进程发送信号并处理
        wait(NULL); // 等待子进程结束
        printf("Parent: Finished.\n");
    }

    return 0;
}

编译和运行:

# 假设代码保存在 rt_sigqueueinfo_example.c 和 sigqueue_example.c 中
gcc -o rt_sigqueueinfo_example rt_sigqueueinfo_example.c
gcc -o sigqueue_example sigqueue_example.c

# 运行第一个示例 (会展示 rt_sigqueueinfo 的权限限制)
./rt_sigqueueinfo_example

# 运行第二个示例 (推荐的 sigqueue 用法)
./sigqueue_example

总结:
对于 Linux 编程新手,请优先学习和使用 sigqueue()rt_sigqueueinfo 是一个底层工具,功能强大但使用不当有安全风险,通常由系统库内部使用。

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

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

发表回复

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