我们来深入学习 tkill 系统调用
1. 函数介绍 在 Linux 系统中,信号(Signal)是一种进程间通信(IPC)机制,允许一个进程(或内核)向另一个进程发送简短的异步通知。例如,当你在终端按下 Ctrl+C 时,系统会向当前前台进程发送 SIGINT 信号。
发送信号最常用的方法是 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 #include <sys/syscall.h> #include <unistd.h> #include <signal.h> long syscall (SYS_tkill, int tid, int sig) ;
注意:用户空间标准 C 库通常不直接提供 tkill() 函数。你需要使用 syscall() 函数来直接调用系统调用号 SYS_tkill。
3. 功能 向指定线程 ID (tid) 的线程发送指定的信号 (sig)。
4. 参数 tid:
sig:
5. 返回值
6. 错误码 (errno)
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 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <pthread.h> #include <signal.h> #include <sys/syscall.h> #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); printf ("Worker thread %ld started. Kernel TID: %d\n" , thread_num, my_tid); 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); struct sigaction sa; printf ("--- Demonstrating tkill vs pthread_kill ---\n" ); printf ("Main thread PID: %d, Kernel TID: %d\n" , getpid (), main_tid); 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); } sigset_t block_set; sigemptyset (&block_set); sigaddset (&block_set, SIGUSR1); sigprocmask (SIG_BLOCK, &block_set, NULL ); 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 ); printf ("\n--- Using tkill (NOT RECOMMENDED) ---\n" ); pid_t worker1_tid = syscall (SYS_gettid); 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" ); } 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> _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); 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); } pid_t target_tid; while ((target_tid = atomic_load (&worker_tid)) == 0 ) { usleep (100000 ); } printf ("Main thread got worker TID: %d\n" , target_tid); sleep (1 ); 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 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 段落
保存草稿 发布文章
相关阅读