在linux上獲得線程id的方法
在linux2.4版本后,linux使用了NPTL作為自己的線程庫,為了兼容POSIX標準,所以在內核task中有兩個域tgid和tid,前者是進程id,后者是線程id。在linux上獲得線程id的方法,目前我所知的有三種,當然這里的三種是指在用戶態的程序中,否則除非自己寫的kernel module, 都是調用編號224的系統調用實現的(2.6版本)。第一種: gettid(), man gettid 可以看到gettid的使用方式。
使用時要先定義:_syscall0(pid_t, gettid)
其中_syscall0是一個宏(由于參數的不同還有_syscall1,_syscall2...),定義如下:
#define _syscall0(type,name) \
type name(void) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \ //int 80, 軟中斷
: "=a" (__res) \ //輸入輸出都用的eax
: "0" (__NR_##name)); \ //#define __NR_gettid 224
__syscall_return(type,__res); \ //返回tid
}
編譯時,宏展開之后,相當于定義了一個pid_t gettid(void)函數,用內嵌匯編實現,在程序中就可以使用gettid()獲得線程id了。
第二種:syscall(), 名字叫syscall(),卻是glibc中的庫函數。
使用方式:syscall(__NR_gettid), 其中__NR_gettid就是224,同上。
syscall的實現要到glibc中去找,不同的硬件平臺有不同的實現版本,在i386上的實現在syscall.S中:
#include <sysdep.h>
.text
ENTRY (syscall)
PUSHARGS_6 /* Save register contents. */
_DOARGS_6(44) /* Load arguments. */
movl 20(%esp), %eax /* Load syscall number into %eax. */
ENTER_KERNEL /* Do the system call. */
POPARGS_6 /* Restore register contents. */
cmpl $-4095, %eax /* Check %eax for error. */
jae SYSCALL_ERROR_LABEL /* Jump to error handler if error. */
L(pseudo_end):
ret /* Return to caller. */
PSEUDO_END (syscall)
其中ENTRY也是一個宏,展開了相當的長,主要用于在鏈接的時候讓gcc能夠"看見"并調用這段用匯編寫成的syscall()函數。
第三種:pthread_self()
同樣是一個glibc提供的函數,在linux的manual中說返回的是當前線程的thread ID.但是實際你看到的是一個很長的,似乎沒有規律的值。什么原因得看看它的實現:
在glibc中,pthread_self()返回的是THREAD_SELF,這又是一個宏
定義如下
# define THREAD_SELF \
({ struct pthread *__self; \
asm ("movl %%gs:%c1,%0" : "=r" (__self) \
: "i" (offsetof (struct pthread, header.self))); \
__self;})
這段代碼返回了當前線程的descriptor,pthread_self()得到的就是這個descriptor的地址, 也就是unsigned long int類型的pthread_t。知道了這一點就好辦了,找到thread descriptor的定義:
struct pthread
{
...
pid_t tid;
...
}
接下來知道怎么做了嗎?算好長度n,構造一個假的pthread結構。
struct pthread_fake
{
void *nothing[n];
pid_t tid;
};
用(struct pthread_fake *) pthread_self()->tid得到線程id了
相比前兩種做法,這種無疑是最繁瑣的,但是同理,可以獲取很多glibc中維護了,但是沒有提供訪問方法的數據。