我们来深入学习 rt_tgsigqueueinfo
系统调用,在 Linux 系统中,信号(Signal)是进程间通信的一种方式。kill()
系统调用可以向一个进程发送信号,而 sigqueue()
则可以发送信号并附带一些数据(union sigval
)。rt_tgsigqueueinfo
是一个更底层、更具体、但也更复杂的系统调用。
1. 函数介绍
在 Linux 系统中,信号(Signal)是进程间通信的一种方式。kill()
系统调用可以向一个进程发送信号,而 sigqueue()
则可以发送信号并附带一些数据(union sigval
)。
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. 函数原型
// 这是底层系统调用,用户空间标准 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);
用户空间更常用的相关函数:
#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 (也是线程组 IDtgid
)。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
构造不当而失败。
#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
的推荐示例:
#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;
}
编译和运行:
# 假设代码保存在 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
是一个底层、强大的工具,但使用不当会有安全风险且复杂,通常由系统库内部或高级系统编程使用。