从pthread_self看GNU ld链接器
一、问题引出
对于主线程(也就是main函数对应的线程),它并不是通过pthread_create创建的线程,所以我们没有这个主线程对应的pthread_t结构,这个结构也就是pthread_create的第一个参数。这当然只是最为直观的一个结论,事实上系统不会这么羸弱,在main函数中通过pthread_self函数就可以获得主线程的线程描述符。为什么呢?因为对于一个函数来说,它并不知道也不应该知道自己是否是被主线程调用,不可能每个函数都要判断自己是被主线程调用还是其它线程调用。
二、pthread库实现
1、__pthread_initialize_minimal_internal
从C库是先看,pthread库中很多基本的操作都是在这个函数中实现的,而这个函数就是坐落在这个init.c文件中,但是单单从这个文件来看,并不能看出这个文件有什么特殊之处,至少看不出来它会在系统启动的时候被执行以至于pthread_self可以有点意义。现在我们先分析一下这个函数是如何让pthread_self有意义的,然后再说明它是如何被调用的。初始化的调用连:
__pthread_initialize_minimal_internal--->>> __libc_setup_tls (TLS_TCB_SIZE, TLS_TCB_ALIGN)--->>> TLS_INIT_TP (tlsblock, 0):
/* Install the TLS. */ \
asm volatile (TLS_LOAD_EBX \
"int $0x80\n\t" \
TLS_LOAD_EBX \
: "=a" (_result), "=m" (_segdescr.desc.entry_number) \
: "0" (__NR_set_thread_area), \
TLS_EBX_ARG (&_segdescr.desc), "m" (_segdescr.desc)); \
如果你很好奇这个线程描述符是在哪里分配的,也就是它的逻辑位置在哪里?那么从__libc_setup_tls函数可以找到答案,在这个函数中__sbrk扩充了主线程的堆栈大小,也就是申请了一个pthread_t结构和对应的tls数据区,从而让主线程的“堆栈+TLS"也和其它pthread线程一致,从而保证pthread库中接口的一致性。
2、__pthread_initialize_minimal被调用的时机
简单搜索这个函数被调用的地方,可以发现glibc-2.7\nptl\sysdeps\pthread\pt-initfini.c中有对这个函数的调用,而且这个文件的名字给出的提示是这个文件是PThread的初始化和结束化相关操作。
由于其内容少儿重要,所以这里进行摘抄:
/* The beginning of _init: */
asm ("\n/*@_init_PROLOG_BEGINS*/");注意,这个将是下面Makefile脚本中sed处理定界符的来源。
static void
call_initialize_minimal (void)
{
extern void __pthread_initialize_minimal_internal (void)
__attribute ((visibility ("hidden")));
__pthread_initialize_minimal_internal ();
}
SECTION (".init");
extern void __attribute__ ((section (".init"))) _init (void);
void
_init (void)
{
/* The very first thing we must do is to set up the registers. */
call_initialize_minimal ();
asm ("ALIGN");
asm("END_INIT");
/* Now the epilog. */
asm ("\n/*@_init_PROLOG_ENDS*/");注意,这个将是下面Makefile脚本中sed处理定界符的来源。
也就是在_init函数中调用了这个__pthread_initialize_minimal函数,从而完成了主线程的初始化。但是如果搜索对这个_init的调用,那可能会发现很多莫名其妙的调用地方,莫名其妙到无法对应。而且明显地,glibc-2.7\sysdeps\generic\initfini.c中定义的_init函数更有竞争力,因为编译过glibc的同学都知道,pthread库是glibc的一个addons选项,也就是一个“添头”,所以如果libc和pthread库一起链接,一定使用的是generic中的_init函数。说了这么多,可能让问题更加扑朔迷离了,以为我自己也没有说清楚(但是是想清楚的),所以直接正向的描述这个问题吧。
在 glibc-2.7\nptl\Makefile中有下面的代码
(objpfx)pt-initfini.s: pt-initfini.c
$(compile.c) -S $(CFLAGS-pt-initfini.s) -finhibit-size-directive \
$(patsubst -f%,-fno-%,$(exceptions)) -o $@
………………
# We only have one kind of startup code files. Static binaries and
# shared libraries are build using the PIC version.
$(objpfx)crti.S: $(objpfx)pt-initfini.s
sed -n -e '1,/@HEADER_ENDS/p' \
-e '/@_.*_PROLOG_BEGINS/,/@_.*_PROLOG_ENDS/p' \这个sed表达式的意思就是将@_.*_PROLOG_BEGINS到@_.*_PROLOG_ENDS之间的内容输出到标准输出中,由于接下来将这个标准输出重定向到了$@(也就是crti.s中),加上前面看到pt-initfini.s是由pt-initfini.c生成的,所以,结合pt-initfini.c中的_init函数将会被输出到这个crti.s文件中,加上这个函数通过extern void __attribute__ ((section (".init"))) _init (void);声明要求将自己安葬在.init节中,所以在crti.s中,这个函数依然是在.init节中。
-e '/@TRAILER_BEGINS/,$$p' $< > $@
$(objpfx)crtn.S: $(objpfx)pt-initfini.s
sed -n -e '1,/@HEADER_ENDS/p' \
-e '/@_.*_EPILOG_BEGINS/,/@_.*_EPILOG_ENDS/p' \
-e '/@TRAILER_BEGINS/,$$p' $< > $@
$(objpfx)defs.h: $(objpfx)pt-initfini.s
sed -n -e '/@TESTS_BEGIN/,/@TESTS_END/p' $< | \
$(AWK) -f ../csu/defs.awk > $@
3、_init函数被调用的时机
\glibc-2.7\sysdeps\i386\elf\start.S
/* Push address of our own entry points to .fini and .init. */
pushl $__libc_csu_fini
pushl $__libc_csu_init
pushl %ecx /* Push second argument: argv. */
pushl %esi /* Push first argument: argc. */
pushl $BP_SYM (main)
/* Call the user's main function, and exit with its value.
But let the libc call main. */
call BP_SYM (__libc_start_main)
\glibc-2.7\csu\elf-init.c
void
__libc_csu_init (int argc, char **argv, char **envp)
{
/* For dynamically linked executables the preinit array is executed by
the dynamic linker (before initializing any shared object. */
#ifndef LIBC_NONSHARED
/* For static executables, preinit happens rights before init. */
{
const size_t size = __preinit_array_end - __preinit_array_start;
size_t i;
for (i = 0; i < size; i++)
(*__preinit_array_start [i]) (argc, argv, envp);
}
#endif
_init ();
const size_t size = __init_array_end - __init_array_start;
for (size_t i = 0; i < size; i++)
(*__init_array_start [i]) (argc, argv, envp);
}
SECTION (".init");
extern void __attribute__ ((section (".init"))) _init (void);
void
_init (void)
{
/* We cannot use the normal constructor mechanism in gcrt1.o because it
appears before crtbegin.o in the link, so the header elt of .ctors
would come after the elt for __gmon_start__. One approach is for
gcrt1.o to reference a symbol which would be defined by some library
module which has a constructor; but then user code's constructors
would come first, and not be profiled. */
call_gmon_start ();
asm ("ALIGN");
asm("END_INIT");
/* Now the epilog. */
asm ("\n/*@_init_PROLOG_ENDS*/");
asm ("\n/*@_init_EPILOG_BEGINS*/");
SECTION(".init");
}
asm ("END_INIT");