我们来深入学习 set_tid_address 系统调用
set_tid_address系统调用及示例-CSDN博客
在 Linux 系统中,进程和线程是程序执行的基本单位。每个线程都有一个唯一的标识符,叫做 Thread ID (TID)。对于主线程(也就是进程本身),它的 TID 通常和 Process ID (PID) 是相同的。但对于通过 clone() 或 pthread_create() 创建的子线程,它们会有自己独立的 TID。
有时候,一个线程(或进程)需要知道另一个线程何时退出。例如,在一个多线程服务器中,主线程可能需要清理已退出的工作线程的资源。
set_tid_address 系统调用提供了一种机制来实现这一点。当一个线程调用 set_tid_address 并传入一个内存地址(我们称之为“TID 地址”或 tidptr)后,内核会记住这个地址。当这个线程最终退出时,内核会执行一个非常重要的操作:将这个地址处的内存值(通常是一个 int 或 pid_t 变量)清零(设置为 0)。
这样,程序的其他部分(比如父线程)就可以通过检查这个 tidptr 指向的内存位置的值来判断线程是否已经退出:如果值是 0,说明线程退出了;如果值非 0,说明线程还在运行(或者刚好是它的 TID)。
简单来说,set_tid_address 就是告诉内核:“当我死(退出)的时候,请帮我把这个地方的数字清零。”这样别人(通常是创建我的线程)就能通过看这个数字知道我是不是挂了。
重要提示:用户空间程序通常不会直接调用 set_tid_address。当你使用 pthread_create() 创建线程时,底层的 C 库(如 glibc NPTL)会自动为你调用 set_tid_address,并管理好这个 tidptr。这个系统调用主要是供 C 库实现线程功能时使用的。
对于 Linux 编程小白:你只需要知道,当你使用标准的 POSIX 线程库(pthread)时,线程退出通知机制(例如 pthread_join 能知道线程何时结束)在底层可能就是通过 set_tid_address 实现的。直接调用它比较少见,除非你在编写自己的线程库或者进行非常底层的系统编程。
2. 函数原型 1 2 3 4 5 6 7 #include <sys/syscall.h> #include <unistd.h> long syscall (SYS_set_tid_address, int *tidptr) ;
3. 功能 设置当前线程在退出时用于清除的用户空间地址。内核会记录这个地址,当线程终止时,内核会将该地址指向的内存单元(通常是一个 int)的值设置为 0。
4. 参数 tidptr:
5. 返回值
6. 相似函数或关联函数
clone: 用于创建进程或线程的底层系统调用。clone 可以接受 CLONE_CHILD_CLEARTID 标志,该标志会使得新创建的子进程/线程在退出时自动调用类似 set_tid_address 的操作。
pthread_create / pthread_join: POSIX 线程库函数。pthread_create 会创建线程并可能在底层使用 set_tid_address,pthread_join 会等待线程结束,其底层实现可能依赖于 set_tid_address 提供的机制(例如通过 futex 等待 tidptr 变为 0)。
gettid: 获取当前线程的 TID。可以通过 syscall(SYS_gettid) 调用。
futex: 快速用户空间互斥锁系统调用。pthread_join 等函数可能使用 futex 来高效地等待由 set_tid_address 清零的变量。
wait / waitpid: 用于等待子进程结束。这是针对进程的,而 set_tid_address 是针对线程的。
7. 示例代码 由于 set_tid_address 通常由 C 库内部使用,直接调用它需要手动管理线程的创建和同步,这比较复杂。下面的示例将演示如何结合 clone 系统调用和 set_tid_address 来手动创建一个线程,并利用 set_tid_address 的机制来等待它退出。
警告:这是一个非常底层的示例,展示了 set_tid_address 和 clone 的用法。在实际编程中,强烈建议使用 pthread 库。
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 #define _GNU_SOURCE #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/syscall.h> #include <sys/wait.h> #include <sched.h> #include <errno.h> #include <stdatomic.h> #include <linux/futex.h> #include <sys/time.h> #include <limits.h> #define STACK_SIZE (1024 * 1024) int thread_function (void *arg) { long thread_num = (long )arg; pid_t my_tid = syscall (SYS_gettid); printf ("Child thread %ld (TID: %d) started.\n" , thread_num, my_tid); for (int i = 0 ; i < 5 ; ++i) { printf ("Child thread %ld working... %d\n" , thread_num, i); sleep (1 ); } printf ("Child thread %ld (TID: %d) finishing.\n" , thread_num, my_tid); return 0 ; }void wait_for_thread_exit (int *tidptr) { while (__atomic_load_n(tidptr, __ATOMIC_ACQUIRE) != 0 ) { printf ("Main thread: Waiting for thread with TID %d to exit (tidptr=%d)...\n" , *tidptr, *tidptr); sleep (1 ); } printf ("Main thread: Detected thread has exited (tidptr is now 0).\n" ); }int main () { char *stack; char *stack_top; pid_t child_tid; int tid_location = 0 ; printf ("--- Demonstrating set_tid_address with clone ---\n" ); printf ("Main thread PID/TID: %d\n" , getpid ()); stack = malloc (STACK_SIZE); if (stack == NULL ) { perror ("malloc" ); exit (EXIT_FAILURE); } stack_top = stack + STACK_SIZE; printf ("Allocated stack for child thread at %p (top at %p)\n" , stack, stack_top); child_tid = syscall (SYS_clone, CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_THREAD | CLONE_SYSVSEM | CLONE_PARENT_SETTID | CLONE_CHILD_CLEARTID, stack_top, NULL , &tid_location); if (child_tid == -1 ) { perror ("clone" ); free (stack); exit (EXIT_FAILURE); } if (child_tid == 0 ) { pid_t my_tid = syscall (SYS_gettid); long returned_tid = syscall (SYS_set_tid_address, &tid_location); printf ("In child thread: My TID is %d, set_tid_address returned %ld\n" , my_tid, returned_tid); __atomic_store_n(&tid_location, my_tid, __ATOMIC_RELEASE); printf ("In child thread: Set tid_location to %d\n" , tid_location); thread_function ((void *)1 ); free (stack); exit (EXIT_SUCCESS); } printf ("Main thread: clone returned child TID: %d\n" , child_tid); printf ("Main thread: tid_location variable address: %p\n" , &tid_location); printf ("Main thread: Initial value of tid_location: %d\n" , tid_location); sleep (1 ); printf ("Main thread: Value of tid_location after child setup: %d\n" , tid_location); printf ("Main thread: Waiting for child thread to exit...\n" ); wait_for_thread_exit (&tid_location); printf ("Main thread: Child thread has exited.\n" ); printf ("Main thread: Final value of tid_location: %d\n" , tid_location); printf ("Main thread: Program finished.\n" ); return 0 ; }
使用标准 pthread 的对比示例 (推荐方式):
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 #define _GNU_SOURCE #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <pthread.h> void * worker_thread (void *arg) { long thread_num = (long )arg; printf ("Worker thread %ld started.\n" , thread_num); for (int i = 0 ; i < 5 ; ++i) { printf ("Worker thread %ld working... %d\n" , thread_num, i); sleep (1 ); } printf ("Worker thread %ld finishing.\n" , thread_num); return NULL ; }int main () { pthread_t thread1, thread2; printf ("--- Using standard pthread (Recommended) ---\n" ); printf ("Main thread PID/TID: %d\n" , getpid ()); 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); } printf ("Main thread: Created worker threads.\n" ); printf ("Main thread: Waiting for worker threads to join...\n" ); pthread_join (thread1, NULL ); pthread_join (thread2, NULL ); printf ("Main thread: Both worker threads have finished.\n" ); printf ("Main thread: Program finished using standard pthread library.\n" ); return 0 ; }
编译和运行:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 gcc -o pthread_example pthread_example.c -lpthread gcc -o set _tid_address_example set _tid_address_example.c./pthread_example ./set_tid_address_example
总结:对于 Linux 编程新手,请优先学习和使用标准的 pthread 库来创建和管理线程。set_tid_address 是一个底层系统调用,主要用于 C 库实现线程功能,它提供了一种高效的线程退出通知机制。直接使用它需要深入了解系统调用、内存管理和线程同步,通常只在编写系统级代码时才会涉及。
https://www.calcguide.tech/2025/08/23/set-tid-address系统调用及示例/
相关阅读