int gscope_flag;
#ifndef __ASSUME_PRIVATE_FUTEX
int private_futex;
#else
int __glibc_reserved1;
#endif
void *__private_tm[4];
void *__private_ss;
} tcbhead_t;
如何去获得上述代码保存的tcbhead_t,以pthread_self的实现为例:
# define THREAD_SELF
({ struct pthread *__self;
asm ("movl %%gs:%c1,%0" : "=r" (__self)
: "i" (offsetof (struct pthread, header.self)));
__self;})
PS:x86时,pthread结构体的第一个元素就是tcbhead_t,所以他们的地址相同:
struct pthread
{
union
{
#if !TLS_DTV_AT_TP
tcbhead_t header;
#else
glibc中的TLS,可以分为两类,三种。
第一类通过dtv_t *dtv实现,这是一个数组,数组里面每一项都是dtv_t联合体。
typedef union dtv
{
size_t counter;
struct
{
void *val;
bool is_static;
} pointer;
} dtv_t;
dtv[-1]为申请的数组的大小,dtv[0]是max generation number,不知道表示什么。这两个都是counter类型的,之后的都是pointer类型的。
每个pointer类型的dtv_t联合体,都和一个被打开的有__thread变量的.so相关(dtv[1]除外,表示程序本身)。其val指向一个数组,也就是该.so中的保存所有__thread变量的一段连续空间。dtv数组的下标是l_tls_modid,表示被打开的有__thread变量的.so的序号。
保存__thread变量的连续空间的大小在编译时就确定好了,已初始化的__thread保存在.tdata段,未初始化的__thread保存在.tbss段,类似于.data和.bss的概念。
可以readelf -S 看看.tdata和.tbss的信息。
pointer类型的dtv_t联合体有静态和动态两种。
在线程创建之前被打开的.so对应的dtv_t是静态的,具体的位置在tcbhead_t前面的内存中。
在线程创建后被dlopen打开的.so对应的dtv_t是动态的,动态申请内存,具体位置在线程栈中。
gdb调试验证可以看:http://codemacro.com/2014/10/07/pthread-tls-bug/
第二类的实现在struct pthread中:
struct pthread
{
tcbhead_t header;
struct pthread_key_data
{
uintptr_t seq;
void *data;
} specific_1stblock[PTHREAD_KEY_2NDLEVEL_SIZE];
struct pthread_key_data *specific[PTHREAD_KEY_1STLEVEL_SIZE];
}
specific是一个二维数组,specific_1stblock是第一个一维数组,用于加快访问速度的。
通过pthread_key_create, pthread_setspecific和pthread_getspecific三个函数来折腾。
以pthread_getspecific来看怎么找到specific(gs—>gdt6—>tcbhead_t—>self—>THREAD_SELF—>specific)和使用二维数组的,比较简单:
void *
__pthread_getspecific (key)
pthread_key_t key;
{
struct pthread_key_data *data;
if (__glibc_likely (key < PTHREAD_KEY_2NDLEVEL_SIZE))
data = &THREAD_SELF->specific_1stblock[key];
else
{
if (key >= PTHREAD_KEYS_MAX)
return NULL;
unsigned int idx1st = key / PTHREAD_KEY_2NDLEVEL_SIZE;
unsigned int idx2nd = key % PTHREAD_KEY_2NDLEVEL_SIZE;
struct pthread_key_data *level2 = T HREAD_GETMEM_NC (THREAD_SELF,
specific, idx1st);
if (level2 == NULL)
return NULL;
data = &level2[idx2nd];
}
void *result = data->data;
if (result != NULL)
{
uintptr_t seq = data->seq;
if (__glibc_unlikely (seq != __pthread_keys[key].seq))
result = data->data = NULL;
}
return result;
}
三、bionic中的TLS
===========================================================================
设置gs的值,以及gdt[6]中的地址,是在如下代码中进行设置的:
void __libc_init_tls(KernelArgumentBlock& args) {
__libc_auxv = args.auxv;
static void* tls[BIONIC_TLS_SLOTS];
static pthread_internal_t main_thread;
main_thread.tls = tls;
__set_tls(main_thread.tls);
tls[TLS_SLOT_BIONIC_PREINIT] = &args;
__init_alternate_signal_stack(&main_thread);
}
__LIBC_HIDDEN__ int __set_tls(void* ptr) {
struct user_desc tls_descriptor;
__init_user_desc(&tls_descriptor, true, ptr);
int rc = __set_thread_area(&tls_descriptor);
if (rc != -1) {
// Change %gs to be new GDT entry.
uint16_t table_indicator = 0; // GDT
uint16_t rpl = 3; // Requested privilege level
uint16_t selector = (tls_descriptor.entry_number << 3) | table_indicator | rpl;
__asm__ __volatile__("movw %w0, %%gs" : : "q"(selector) : );
}
return rc;
}
__init_user_desc第二个参数为true,表示动态申请gdt中的位置,一般为6,所以gs=0x33。
后续在__set_thread_area函数里将指向tls[BIONIC_TLS_SLOTS]的地址写到了gdt[6]中。
数组前几项是固定的:
enum {
TLS_SLOT_SELF = 0, // The kernel requires this specific slot for x86.
TLS_SLOT_THREAD_ID,
TLS_SLOT_ERRNO,
// These two aren't used by bionic itself, but allow the graphics code to
// access TLS directly rather than using the pthread API.
TLS_SLOT_OPENGL_API = 3,
TLS_SLOT_OPENGL = 4,
TLS_SLOT_BIONIC_PREINIT = TLS_SLOT_OPENGL_API,
TLS_SLOT_STACK_GUARD = 5, // GCC requires this specific slot for x86.
TLS_SLOT_DLERROR,
TLS_SLOT_FIRST_USER_SLOT // Must come last!
};
bionic中的TLS表相当于一个一维数组。
bionic中不支持__thread语法,pthread_key_create, pthread_setspecific和pthread_getspecific三个函数直接折腾TLS_SLOT_FIRST_USER_SLOT之后的位置,目前TLS个数限制为64个。
以pthread_getspecific为例,看看bionic中的实现,比glibc简单多了:
void* pthread_getspecific(pthread_key_t key) {
if (!IsValidUserKey(key)) {
return NULL;
}
// For performance reasons, we do not lock/unlock the global TLS map
// to check that the key is properly allocated. If the key was not
// allocated, the value read from the TLS should always be NULL
// due to pthread_key_delete() clearing the values for all threads.
return __get_tls()[key];
}
四、什么是libhybris
============================================================================
libhybris简而言之,就是glibc想使用bionic中的.so。但是pthread,ipc等很多东西又不兼容,所以就整了这么一套东西。
libhybris实现了类似于android bionic的linker, 加一些glue code和wrap,hook之类的东西,去处理不兼容的部分。
demo程序,android端提供一个libfoo.c,里面有foo和bar两个函数:
Android.mk:
LOCAL_PATH:=$(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE:= libfoo
LOCAL_SRC_FILES:= foo.cpp
include $(BUILD_SHARED_LIBRARY)
foo.cpp:
#include
#include
void foo(void)
{
printf("foon");
printf("%sn", getenv("PATH"));
}
void bar(void)
{
foo();
printf("barn");
}
其他系统端,通过libhybris,调用bar函数:
#include
#include
#include
#include
#include
int main(void)
{
void *handle;
void (*bar)(void);
handle = android_dlopen("libfoo.so", RTLD_NOW);
if (NULL == handle)
{
fprintf(stderr, "android_dlopen failed: %sn", strerror(errno));
return -1;
}
bar = (void (*)(void))android_dlsym(handle, "_Z3barv");
if (NULL == bar)
{
fprintf(stderr, "fail to dlsym: %sn", strerror(errno));
return -1;



