我们来深入学习 rt_sigqueueinfo 系统调用,在 Linux 中,进程间通信(IPC)有多种方式,信号(Signal)是其中一种轻量级的通知机制。通常,我们使用 kill() 来发送一个简单的信号,或者使用 sigqueue() 来发送一个信号并附带一小段数据(一个整数或指针)。
1. 函数介绍 在 Linux 中,进程间通信(IPC)有多种方式,信号(Signal)是其中一种轻量级的通知机制。通常,我们使用 kill() 来发送一个简单的信号,或者使用 sigqueue() 来发送一个信号并附带一小段数据(一个整数或指针)。
data-ad-format="fluid"
data-ad-layout-key="-7k+ex-4a-9w+4a">
rt_sigqueueinfo 是一个更底层、更强大但也更危险的系统调用。它允许你向另一个进程发送一个信号,并且可以完全自定义随信号一起传递的 siginfo_t 结构体中的所有信息。这包括发送者的 PID、信号产生的原因代码(si_code)、以及附带的数据等。
为什么危险?因为它强大的自定义能力意味着发送者可以伪造信号的来源和原因。例如,一个普通用户进程可以伪造自己是内核(si_code 为 SI_KERNEL)或其他进程发送的信号。因此,这个系统调用通常受到严格的权限检查,普通应用程序一般不应该直接使用它。
什么时候会用到?主要是在实现更高级别的信号发送函数(如 sigqueue)时,由 C 库(glibc)内部调用,或者在一些非常特殊的、需要精确控制信号信息的系统级编程中。
对于 Linux 编程小白:你更可能使用 sigqueue() 函数,它更安全、更易用,并且能满足绝大多数需要发送带数据信号的场景。
2. 函数原型 1 2 3 4 5 6 7 8 // 这是底层系统调用,直接在用户空间调用比较复杂且需要特殊权限 #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 的包装函数
用户空间更常用、更安全的替代函数:
1 2 3 4 #include <signal.h> int sigqueue(pid_t pid, int sig, const union sigval value);
3. 功能 向指定进程 ID (pid) 发送指定信号 (sig),并允许发送者完全指定 siginfo_t 结构体 (uinfo) 中包含的详细信息。
4. 参数 pid:
sig:
uinfo:
5. 返回值
失败: 返回 -1,并设置全局变量 errno 来指示具体的错误原因:
EAGAIN: (对于实时信号) 已达到接收者排队信号的最大数量限制 (RLIMIT_SIGPENDING)。
EPERM: 调用者没有权限发送信号给目标进程(例如,普通用户不能向 root 进程发送任意伪造的信号)。
EINVAL: sig 是无效的信号号,或者 uinfo 中的 si_code 是无效的或不允许由用户设置的。
ESRCH: 找不到 pid 指定的进程或进程组。
6. 相似函数或关联函数
siginfo_t: 包含信号详细信息的结构体。主要字段包括:
si_signo: 信号编号(内核会设置)。
si_errno: 如果非零,表示伴随信号的错误代码(内核会设置)。
si_code: 信号产生的原因代码(例如 SI_USER, SI_QUEUE, SI_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 权限的情况下。
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 #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 的简单对比示例 (推荐方式):
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 #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; }
编译和运行:
1 2 3 4 5 6 7 8 9 10 # 假设代码保存在 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 是一个底层工具,功能强大但使用不当有安全风险,通常由系统库内部使用。
https://www.calcguide.tech/2025/08/24/rt-sigqueueinfo系统调用及示例/
rt_sigqueueinfo系统调用及示例-CSDN博客