rt_sigqueueinfo系统调用及示例

我们来深入学习 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:

  • pid_t 类型。

  • 目标进程的进程 ID (PID)。信号将被发送给这个进程。

sig:

  • int 类型。

  • 要发送的信号编号,例如 SIGUSR1, SIGRTMIN 等。注意,不能发送 SIGKILL 和 SIGSTOP。

uinfo:

  • siginfo_t * 类型。

  • 一个指向 siginfo_t 结构体的指针。这个结构体包含了你想随信号一起传递给目标进程的所有详细信息。调用者需要自己填充这个结构体的各个字段。

5. 返回值

  • 成功: 返回 0。

失败: 返回 -1,并设置全局变量 errno 来指示具体的错误原因:

  • EAGAIN: (对于实时信号) 已达到接收者排队信号的最大数量限制 (RLIMIT_SIGPENDING)。

  • EPERM: 调用者没有权限发送信号给目标进程(例如,普通用户不能向 root 进程发送任意伪造的信号)。

  • EINVAL: sig 是无效的信号号,或者 uinfo 中的 si_code 是无效的或不允许由用户设置的。

  • ESRCH: 找不到 pid 指定的进程或进程组。

6. 相似函数或关联函数

  • sigqueue: 用户空间更安全、更常用的发送信号和数据的方法。它只允许设置 siginfo_t 中的 si_value 字段,其他字段由内核自动填充。

  • kill: 发送一个不带附加数据的信号。

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博客

data-ad-format="auto" data-full-width-responsive="true">