rt_tgsigqueueinfo系统调用及示例

我们来深入学习 rt_tgsigqueueinfo 系统调用,在 Linux 系统中,信号(Signal)是进程间通信的一种方式。kill() 系统调用可以向一个进程发送信号,而 sigqueue() 则可以发送信号并附带一些数据(union sigval)。rt_tgsigqueueinfo 是一个更底层、更具体、但也更复杂的系统调用。

1. 函数介绍

在 Linux 系统中,信号(Signal)是进程间通信的一种方式。kill() 系统调用可以向一个进程发送信号,而 sigqueue() 则可以发送信号并附带一些数据(union sigval)。

data-ad-format="fluid" data-ad-layout-key="-7k+ex-4a-9w+4a">

rt_tgsigqueueinfo 是一个更底层、更具体、但也更复杂的系统调用。它的名字可以拆解为:

  • rt_: 表示这是一个与实时信号相关的系统调用。

  • tg: 表示 Thread Group(线程组)。在 Linux 中,一个进程(Process)可以包含多个线程(Threads),这些线程共享同一个 PID,但每个线程有自己的唯一 Thread ID (TID)。所有共享同一个 PID 的线程构成了一个线程组。

  • sigqueueinfo: 表示它像 sigqueue 一样发送信号和数据,但允许发送者提供更详细的信号信息(通过 siginfo_t 结构体)。

所以,rt_tgsigqueueinfo 的作用是:向指定进程(线程组)中的指定线程(由 TID 指定)发送一个信号,并允许发送者完全指定该信号的 siginfo_t 信息。

与 sigqueue 的区别:

  • sigqueue(pid_t pid, …): 向进程 pid 发送信号。内核会选择该进程(线程组)中的某个线程来接收信号。

  • rt_tgsigqueueinfo(pid_t tgid, pid_t tid, …): tgid 是线程组 ID(通常就是进程的 PID),tid 是线程组内具体线程的 ID(TID)。它允许你指定哪个线程应该接收这个信号。

为什么复杂/危险?与 rt_sigqueueinfo 类似,它允许发送者完全伪造 siginfo_t 中的信息(如发送者 PID、si_code 等),这可能带来安全风险。因此,它的使用受到严格的权限检查。

对于 Linux 编程小白:你通常不需要直接使用 rt_tgsigqueueinfo。在需要向特定线程发送信号时,更常见的做法是在线程内部使用 pthread_kill()(POSIX 线程库函数)。rt_tgsigqueueinfo 主要供系统级程序或 C 库实现使用。

2. 函数原型

1
2
3
4
5
6
7
// 这是底层系统调用,用户空间标准 C 库通常不直接提供包装函数
#include <sys/syscall.h> // 包含系统调用号
#include <unistd.h> // 包含 syscall 函数
#include <signal.h> // 包含 siginfo_t 定义

long syscall(SYS_rt_tgsigqueueinfo, pid_t tgid, pid_t tid, int sig, siginfo_t *uinfo);

用户空间更常用的相关函数:

1
2
3
4
5
6
#include <signal.h>
int sigqueue(pid_t pid, int sig, const union sigval value); // 向进程发送信号和数据

#include <pthread.h> // (需要链接 -lpthread)
int pthread_kill(pthread_t thread, int sig); // 向特定 POSIX 线程发送信号

3. 功能

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

4. 参数

tgid:

  • pid_t 类型。

  • 目标线程所属的线程组 ID (Thread Group ID)。对于单线程进程,这通常就是它的 PID。对于多线程程序,所有线程共享同一个 tgid(即主线程的 PID)。

tid:

  • pid_t 类型。

  • 目标线程在内核中的唯一 ID (Thread ID)。在用户空间的 pthread_create 返回的 pthread_t 和内核的 tid 之间需要转换(可以使用 /proc/self/task/ 目录或特定的系统调用来获取)。

sig:

  • int 类型。

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

uinfo:

  • siginfo_t * 类型。

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

5. 返回值

  • 成功: 返回 0。

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

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

  • EPERM: 调用者没有权限发送信号给目标线程(例如,权限不足,或试图伪造某些 si_code)。

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

  • ESRCH: 找不到 tgid 指定的线程组或 tid 指定的线程。

6. 相似函数或关联函数

  • sigqueue: 向进程(线程组)发送信号和数据,由内核选择线程接收。

  • pthread_kill: POSIX 标准函数,用于向指定的 pthread_t 线程发送信号,更易于使用。

  • tgkill: 一个更安全的系统调用,用于向指定线程组和线程发送信号(但不带 siginfo_t 数据),通常由 pthread_kill 内部调用。

  • kill: 向进程发送信号。

  • siginfo_t: 包含信号详细信息的结构体。

  • getpid: 获取当前进程的 PID (也是线程组 ID tgid)。

  • gettid: 获取当前线程的内核 TID。注意:这不是标准 C 库函数,需要通过 syscall(SYS_gettid) 调用。

  • pthread_self: 获取当前线程的 POSIX 线程 ID (pthread_t)。

7. 示例代码

由于直接使用 rt_tgsigqueueinfo 需要获取内核线程 ID (tid) 并且容易因权限或错误的 siginfo_t 而失败,下面的示例将演示如何获取 tid 并尝试调用 rt_tgsigqueueinfo,同时提供一个使用推荐的 pthread_kill 的对比示例。

警告:直接使用 rt_tgsigqueueinfo 可能会因为权限问题或 siginfo_t 构造不当而失败。

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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
#define _GNU_SOURCE // 启用 GNU 扩展,获取 gettid
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <sys/syscall.h> // 包含 syscall 和 SYS_rt_tgsigqueueinfo, SYS_gettid
#include <errno.h>
#include <sys/types.h> // 包含 pid_t
#include <pthread.h> // 包含 pthread_t, pthread_create 等
#include <stdatomic.h> // 用于线程间安全通信

// 全局变量用于线程间通信
_Atomic int signal_count = 0;

// 信号处理函数 (使用 SA_SIGINFO 获取详细信息)
void signal_handler(int sig, siginfo_t *info, void *context) {
atomic_fetch_add(&signal_count, 1); // 原子增加计数
printf("Thread %ld: Received signal %d\n", syscall(SYS_gettid), 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_TKILL: printf(" (SI_TKILL: tkill or tgkill)\n"); break;
default: printf(" (Other code)\n"); break;
}
printf(" si_pid: %d\n", info->si_pid);
if (info->si_code == SI_QUEUE) {
printf(" si_value.sival_int: %d\n", info->si_value.sival_int);
}
}

// 线程函数
void* worker_thread(void *arg) {
sigset_t set;
int sig;
siginfo_t info;

printf("Worker thread started. TID: %ld, PID (TGID): %d\n", syscall(SYS_gettid), getpid());

// 为这个线程设置信号掩码,只等待 SIGUSR1
sigemptyset(&set);
sigaddset(&set, SIGUSR1);

// 阻塞 SIGUSR1,准备用 sigwaitinfo 接收
if (sigprocmask(SIG_BLOCK, &set, NULL) == -1) {
perror("sigprocmask (worker)");
return NULL;
}

while (atomic_load(&signal_count) < 3) { // 等待接收3次信号
printf("Worker thread (TID %ld) waiting for SIGUSR1...\n", syscall(SYS_gettid));
sig = sigwaitinfo(&set, &info); // 同步等待 SIGUSR1
if (sig == -1) {
perror("sigwaitinfo (worker)");
break;
}
// sigwaitinfo 成功返回后,信号处理函数 signal_handler 会被调用
// 因为我们阻塞了信号,sigwaitinfo 会接收它,但处理函数仍然会执行
// 这里我们主要依靠 signal_handler 中的计数
}
printf("Worker thread (TID %ld) finished.\n", syscall(SYS_gettid));
return NULL;
}

int main() {
pthread_t thread;
pid_t my_tgid, my_tid, worker_tid;
struct sigaction sa;
siginfo_t si_to_send;
union sigval data_to_send = {.sival_int = 54321};

my_tgid = getpid();
my_tid = syscall(SYS_gettid); // 获取主线程的内核 TID
printf("Main thread: PID (TGID): %d, TID: %ld\n", my_tgid, my_tid);

// 1. 设置 SIGUSR1 的处理函数
memset(&sa, 0, sizeof(sa));
sa.sa_sigaction = signal_handler; // 使用 sa_sigaction 获取 info
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_SIGINFO; // 必须设置
if (sigaction(SIGUSR1, &sa, NULL) == -1) {
perror("sigaction");
exit(EXIT_FAILURE);
}

// 2. 创建一个工作线程
if (pthread_create(&thread, NULL, worker_thread, NULL) != 0) {
perror("pthread_create");
exit(EXIT_FAILURE);
}

// 简单等待一下,让子线程启动并打印其 TID
sleep(1);
// 在真实的程序中,获取另一个线程的 TID 比较复杂,
// 通常需要线程自己报告。这里我们简化处理,
// 假设我们 somehow 知道了 worker 线程的 TID。
// 为了演示,我们还是向主线程自己发送。
worker_tid = my_tid; // 简化:发送给自己
printf("\n--- Attempting to use rt_tgsigqueueinfo ---\n");
printf("Targeting TGID: %d, TID: %ld\n", my_tgid, worker_tid);

// 3. 准备 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_tgid; // 伪造 PID
si_to_send.si_uid = getuid(); // 伪造 UID
si_to_send.si_value = data_to_send; // 附带数据

printf("Sending SIGUSR1 with data %d using rt_tgsigqueueinfo()...\n", data_to_send.sival_int);
printf("(This may fail with EPERM unless run with special privileges or on some systems)\n");

// 4. 调用底层系统调用
long result = syscall(SYS_rt_tgsigqueueinfo, my_tgid, worker_tid, SIGUSR1, &si_to_send);

if (result == -1) {
perror("rt_tgsigqueueinfo");
printf("Error: rt_tgsigqueueinfo failed.\n");
printf("Errno: %d\n", errno);
if (errno == EPERM) {
printf("Reason: EPERM - Operation not permitted (insufficient privileges or invalid si_code).\n");
}
} else {
printf("rt_tgsigqueueinfo succeeded.\n");
}

sleep(2); // 等待信号处理

// 5. 再发送一次,使用标准的 sigqueue (推荐方式)
printf("\n--- Using sigqueue (Recommended) ---\n");
printf("Sending SIGUSR1 with data %d using sigqueue()...\n", data_to_send.sival_int);
if (sigqueue(my_tgid, SIGUSR1, data_to_send) == -1) {
perror("sigqueue");
}
sleep(2);

// 6. 等待线程结束
if (pthread_join(thread, NULL) != 0) {
perror("pthread_join");
}

printf("\nFinal signal count: %d\n", atomic_load(&signal_count));
printf("Main program finished.\n");
return 0;
}

使用 pthread_kill 的推荐示例:

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
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <pthread.h>
#include <stdatomic.h>

_Atomic int signal_received = 0;

void thread_signal_handler(int sig) {
atomic_store(&signal_received, 1);
printf("Signal %d received by thread %lu\n", sig, pthread_self());
}

void* target_thread(void *arg) {
// 设置本线程的信号处理掩码,解除对 SIGUSR1 的阻塞
// (假设主线程已经阻塞了它)
sigset_t set;
sigemptyset(&set);
// sigaddset(&set, SIGUSR1); // 如果需要在这里处理
pthread_sigmask(SIG_UNBLOCK, &set, NULL);

printf("Target thread (pthread_t: %lu) running...\n", pthread_self());
// 简单循环等待信号
while(!atomic_load(&signal_received)) {
sleep(1);
}
printf("Target thread received signal and is exiting.\n");
return NULL;
}

int main() {
pthread_t thread;
struct sigaction sa;

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

// 阻塞 SIGUSR1 在主线程
sigset_t block_set;
sigemptyset(&block_set);
sigaddset(&block_set, SIGUSR1);
sigprocmask(SIG_BLOCK, &block_set, NULL);

if (pthread_create(&thread, NULL, target_thread, NULL) != 0) {
perror("pthread_create");
exit(EXIT_FAILURE);
}

sleep(1); // 让子线程启动

printf("Main thread sending SIGUSR1 to target thread using pthread_kill()...\n");
if (pthread_kill(thread, SIGUSR1) != 0) { // 推荐方式
perror("pthread_kill");
}

if (pthread_join(thread, NULL) != 0) {
perror("pthread_join");
}

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

编译和运行:

1
2
3
4
5
6
7
8
9
10
11
# 假设代码保存在 rt_tgsigqueueinfo_example.c 和 pthread_kill_example.c 中
# 需要链接 pthread 库
gcc -o rt_tgsigqueueinfo_example rt_tgsigqueueinfo_example.c -lpthread
gcc -o pthread_kill_example pthread_kill_example.c -lpthread

# 运行示例 (rt_tgsigqueueinfo 可能会因权限失败)
./rt_tgsigqueueinfo_example

# 运行推荐示例 (pthread_kill)
./pthread_kill_example

总结:对于 Linux 编程新手,处理多线程信号时,请优先学习和使用 pthread_kill()。rt_tgsigqueueinfo 是一个底层、强大的工具,但使用不当会有安全风险且复杂,通常由系统库内部或高级系统编程使用。

https://www.calcguide.tech/2025/08/24/rt-tgsigqueueinfo系统调用及示例/

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

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