tkill系统调用及示例

我们来深入学习 tkill 系统调用

1. 函数介绍

在 Linux 系统中,信号(Signal)是一种进程间通信(IPC)机制,允许一个进程(或内核)向另一个进程发送简短的异步通知。例如,当你在终端按下 Ctrl+C 时,系统会向当前前台进程发送 SIGINT 信号。

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

发送信号最常用的方法是 kill() 系统调用。kill(pid_t pid, int sig) 可以向指定的进程 ID (PID) 发送信号。

然而,在 Linux(特别是引入了线程之后),情况变得稍微复杂了一些。一个进程 (Process) 可以包含多个线程 (Threads)。这些线程共享同一个 PID,但每个线程都有自己的唯一线程 ID (TID)。在 Linux 内核的实现中,每个线程实际上都被视为一个独立的“任务”(task)。

这就引出了一个问题:如果我想向一个特定的线程发送信号,而不是整个进程,该怎么办?使用 kill() 只能指定 PID,内核会选择该进程中的某个线程来接收信号,但我们无法精确控制是哪一个。

tkill (Thread Kill) 系统调用就是为了解决这个问题而设计的。它的作用是向指定的线程 ID (TID) 发送信号。

https://www.calcguide.tech/2025/08/08/tkill系统调用及示例/

简单来说,tkill 就是 kill 的“精确打击”版本,它让你可以指定向哪个“小兵”(线程)开炮,而不是向整个“军队”(进程)开炮。

重要提示:

  • tkill 是一个非常底层的系统调用。

  • 对于大多数应用程序开发,不推荐直接使用 tkill。

  • POSIX 标准提供了更安全、更可移植的线程信号发送方法:pthread_kill()。这是你应该优先考虑使用的函数。

  • tkill 主要由线程库(如 NPTL)或高级调试/管理工具在内部使用。

2. 函数原型

1
2
3
4
5
6
7
8
// 标准 C 库通常不提供直接包装,需要通过 syscall 调用
#include <sys/syscall.h> // 包含系统调用号 SYS_tkill
#include <unistd.h> // 包含 syscall 函数
#include <signal.h> // 包含信号常量

long syscall(SYS_tkill, int tid, int sig);
// 注意:现代 glibc (2.30+) 提供了 tgkill,tkill 可能被弃用或不直接暴露

注意:用户空间标准 C 库通常不直接提供 tkill() 函数。你需要使用 syscall() 函数来直接调用系统调用号 SYS_tkill。

3. 功能

向指定线程 ID (tid) 的线程发送指定的信号 (sig)。

4. 参数

tid:

  • int 类型。

  • 目标线程在内核中的唯一 ID (Thread ID)。这不是 POSIX 线程库 (pthread_t) 的 ID,而是内核级别的 TID。可以通过 syscall(SYS_gettid) 获取当前线程的 TID。

sig:

  • int 类型。

  • 要发送的信号编号,例如 SIGTERM, SIGUSR1, SIGSTOP 等。注意,SIGKILL 和 SIGSTOP 不能被捕获或忽略,但可以发送。

5. 返回值

  • 成功: 返回 0。

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

6. 错误码 (errno)

  • EINVAL: sig 参数无效(不是一个有效的信号号)。

  • EPERM: 调用者权限不足,无法向目标线程发送信号(例如,普通用户试图向 root 用户的线程发送信号)。

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

7. 相似函数或关联函数

  • kill: 向指定进程 ID (PID) 发送信号。内核会选择进程中的一个线程来接收信号。

  • pthread_kill: POSIX 标准函数,用于向指定的 pthread_t 线程发送信号。这是用户空间多线程程序推荐使用的方法。

  • tgkill: 一个更安全的系统调用,用于向指定线程组 ID 和线程 ID 的线程发送信号 (tgkill(tgid, tid, sig))。它提供了额外的检查以避免向错误的线程发送信号。pthread_kill 在底层通常会调用 tgkill。

  • gettid: 获取当前线程的内核 TID。需要通过 syscall(SYS_gettid) 调用。

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

8. 为什么 tkill 不推荐直接使用?

易错性:直接使用内核 TID (tid) 容易出错。如果 tid 恰好与系统中另一个不相关的进程的 PID 相同,tkill 可能会错误地向那个进程发送信号。

竞态条件:线程 ID 是可复用的。一个线程退出后,它的 TID 可能会被分配给一个新创建的、完全不相关的线程。如果在 ID 复用之间调用 tkill,可能会发送给错误的线程。

可移植性:tkill 是 Linux 特有的系统调用。使用 POSIX 标准的 pthread_kill 可以让你的代码更容易移植到其他支持 POSIX 线程的系统上。

更好的抽象:pthread_kill 工作在 POSIX 线程模型层面,更符合应用程序员的思维。

9. 示例代码

由于直接使用 tkill 比较危险且不推荐,下面的示例将演示:

如何获取线程的内核 TID。

如何(不推荐地)使用 tkill。

如何(推荐地)使用 pthread_kill。

警告:此示例用于教学目的,展示 tkill 的用法。在实际编程中,请使用 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
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
#define _GNU_SOURCE // 启用 GNU 扩展以使用 syscall(SYS_gettid)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h> // POSIX 线程
#include <signal.h> // 信号
#include <sys/syscall.h> // syscall, SYS_tkill, SYS_gettid
#include <errno.h>
#include <string.h>

// 全局变量用于线程间通信
volatile sig_atomic_t signal_received = 0;

// 信号处理函数
void signal_handler(int sig) {
printf("Thread (TID %ld) received signal %d\n", syscall(SYS_gettid), sig);
signal_received = 1;
}

// 线程函数
void* worker_thread(void *arg) {
long thread_num = (long)arg;
pid_t my_tid = syscall(SYS_gettid); // 获取内核 TID

printf("Worker thread %ld started. Kernel TID: %d\n", thread_num, my_tid);

// 设置信号处理掩码,解除对 SIGUSR1 的阻塞
// (假设主线程已经阻塞了它)
sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGUSR1);
pthread_sigmask(SIG_UNBLOCK, &set, NULL);

// 等待信号
while (!signal_received) {
printf("Worker thread %ld (TID %d) waiting for signal...\n", thread_num, my_tid);
sleep(1);
}

printf("Worker thread %ld (TID %d) finishing.\n", thread_num, my_tid);
return NULL;
}

int main() {
pthread_t thread1, thread2;
pid_t main_tid = syscall(SYS_gettid); // 获取主线程的 TID
struct sigaction sa;

printf("--- Demonstrating tkill vs pthread_kill ---\n");
printf("Main thread PID: %d, Kernel TID: %d\n", getpid(), main_tid);

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

// 2. 阻塞 SIGUSR1 在主线程,稍后在线程中解除阻塞
sigset_t block_set;
sigemptyset(&block_set);
sigaddset(&block_set, SIGUSR1);
sigprocmask(SIG_BLOCK, &block_set, NULL);

// 3. 创建工作线程
if (pthread_create(&thread1, NULL, worker_thread, (void*)1) != 0 ||
pthread_create(&thread2, NULL, worker_thread, (void*)2) != 0) {
perror("pthread_create");
exit(EXIT_FAILURE);
}

sleep(2); // 等待线程启动并打印 TID

// --- 方法 1: 使用不推荐的 tkill ---
printf("\n--- Using tkill (NOT RECOMMENDED) ---\n");
// 假设我们 somehow 知道了 thread1 的内核 TID (这在现实中很难安全地做到)
// 这里我们简化处理,直接使用
pid_t worker1_tid = syscall(SYS_gettid); // 这是错误的!这是主线程的 TID
// 为了演示,我们假设我们有一个正确的方法获取了 worker1 的 TID
// 比如通过全局变量或线程自己报告
// 让我们让 worker_thread 报告它的 TID
// (在实际代码中,你需要一个安全的机制在线程间传递 TID)
// 这里我们伪造一个值来演示,实际使用非常危险
// pid_t fake_worker_tid = 99999; // 假设的 TID
// printf("Attempting to send SIGUSR1 to worker thread using tkill(TID=%d)...\n", fake_worker_tid);
// long result = syscall(SYS_tkill, fake_worker_tid, SIGUSR1);
// if (result == -1) {
// perror("tkill");
// } else {
// printf("tkill succeeded.\n");
// }

// 由于获取其他线程 TID 的安全方法复杂,我们跳过直接 tkill 演示
// 而是重点演示推荐的方法

// --- 方法 2: 使用推荐的 pthread_kill ---
printf("\n--- Using pthread_kill (RECOMMENDED) ---\n");
printf("Sending SIGUSR1 to worker thread 1 using pthread_kill()...\n");
if (pthread_kill(thread1, SIGUSR1) != 0) {
perror("pthread_kill thread1");
} else {
printf("pthread_kill(thread1, SIGUSR1) succeeded.\n");
}

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

printf("Sending SIGUSR1 to worker thread 2 using pthread_kill()...\n");
if (pthread_kill(thread2, SIGUSR1) != 0) {
perror("pthread_kill thread2");
} else {
printf("pthread_kill(thread2, SIGUSR1) succeeded.\n");
}

// 5. 等待线程结束
if (pthread_join(thread1, NULL) != 0 ||
pthread_join(thread2, NULL) != 0) {
perror("pthread_join");
}

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

改进版示例:安全地在线程间传递 TID 并演示 tkill(仅供学习)

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

// 使用原子变量安全地在线程间共享 TID
_Atomic pid_t worker_tid = 0;

volatile sig_atomic_t worker_signal_received = 0;

void worker_signal_handler(int sig) {
pid_t tid = syscall(SYS_gettid);
printf("Worker (TID %d) received signal %d\n", tid, sig);
worker_signal_received = 1;
}

void* worker_thread_v2(void *arg) {
pid_t my_tid = syscall(SYS_gettid);
printf("Worker thread started. Kernel TID: %d\n", my_tid);

// 安全地将 TID 报告给主线程
atomic_store(&worker_tid, my_tid);

// 设置信号处理
struct sigaction sa;
sa.sa_handler = worker_signal_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
if (sigaction(SIGUSR2, &sa, NULL) == -1) { // 使用不同的信号以区分
perror("sigaction worker");
return NULL;
}

while (!worker_signal_received) {
sleep(1);
}
printf("Worker (TID %d) exiting.\n", my_tid);
return NULL;
}

int main() {
pthread_t thread;
pid_t main_tid = syscall(SYS_gettid);

printf("--- Demonstrating tkill (Learning Purpose Only) ---\n");
printf("Main thread PID: %d, Kernel TID: %d\n", getpid(), main_tid);

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

// 等待 worker 线程报告其 TID
pid_t target_tid;
while ((target_tid = atomic_load(&worker_tid)) == 0) {
usleep(100000); // 0.1 秒
}
printf("Main thread got worker TID: %d\n", target_tid);

sleep(1); // 确保 worker 设置了信号处理函数

// --- 现在演示 tkill (仅供学习,实际不推荐) ---
printf("\n--- Using tkill (FOR LEARNING ONLY) ---\n");
printf("Sending SIGUSR2 to worker thread (TID %d) using tkill()...\n", target_tid);
long result = syscall(SYS_tkill, target_tid, SIGUSR2);
if (result == -1) {
perror("tkill");
printf("This might fail due to permissions or the TID being invalid/reused.\n");
} else {
printf("tkill() returned 0 (success).\n");
}

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

10. 编译和运行

1
2
3
4
5
6
7
# 假设代码保存在 tkill_example.c 中
# 需要链接 pthread 库
gcc -o tkill_example tkill_example.c -lpthread

# 运行程序
./tkill_example

11. 预期输出 (第二个示例)

1
2
3
4
5
6
7
8
9
10
11
12
--- Demonstrating tkill (Learning Purpose Only) ---
Main thread PID: 12345, Kernel TID: 12345
Worker thread started. Kernel TID: 12346
Main thread got worker TID: 12346

--- Using tkill (FOR LEARNING ONLY) ---
Sending SIGUSR2 to worker thread (TID 12346) using tkill()...
Worker (TID 12346) received signal 12
tkill() returned 0 (success).
Worker (TID 12346) exiting.
Main thread finished.

12. 总结

tkill 是一个底层的 Linux 系统调用,用于向指定的内核线程 ID (TID) 发送信号。

  • 优点:提供了向特定线程发送信号的能力。

缺点/风险:

  • 易于误用,可能导致向错误的进程/线程发送信号。

  • 存在竞态条件(TID 复用)。

  • 不是 POSIX 标准,可移植性差。

推荐做法:在用户空间多线程程序中,应始终优先使用标准的 pthread_kill() 函数来向特定线程发送信号。它更安全、更可靠、更具可移植性。

tkill 的用途:主要供系统级程序、线程库实现或调试工具内部使用。

对于 Linux 编程新手,请记住:能用 pthread_kill 就别用 tkill。

https://app-blog.csdn.net/csdn/aiChatNew

1. 函数介绍

2. 函数原型

3. 功能

4. 参数

5. 返回值

6. 错误码 (errno)

7. 相似函数或关联函数

8. 为什么 tkill 不推荐直接使用?

9. 示例代码

10. 编译和运行

11. 预期输出 (第二个示例)

12. 总结

Markdown 7782 字数 338 行数 当前行 1, 当前列 20 文章已保存22:13:08

HTML 7390 字数 253 段落

保存草稿 发布文章

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