zoukankan      html  css  js  c++  java
  • 从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 "          
         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 ptlsysdepspthreadpt-initfini.c中有对这个函数的调用,而且这个文件的名字给出的提示是这个文件是PThread的初始化和结束化相关操作。

    由于其内容少儿重要,所以这里进行摘抄:

    /* The beginning of _init:  */
    asm (" /*@_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 (" /*@_init_PROLOG_ENDS*/");注意,这个将是下面Makefile脚本中sed处理定界符的来源

    也就是在_init函数中调用了这个__pthread_initialize_minimal函数,从而完成了主线程的初始化。但是如果搜索对这个_init的调用,那可能会发现很多莫名其妙的调用地方,莫名其妙到无法对应。而且明显地,glibc-2.7sysdepsgenericinitfini.c中定义的_init函数更有竞争力,因为编译过glibc的同学都知道,pthread库是glibc的一个addons选项,也就是一个“添头”,所以如果libc和pthread库一起链接,一定使用的是generic中的_init函数。说了这么多,可能让问题更加扑朔迷离了,以为我自己也没有说清楚(但是是想清楚的),所以直接正向的描述这个问题吧。

    在 glibc-2.7 ptlMakefile中有下面的代码

    (objpfx)pt-initfini.spt-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.7sysdepsi386elfstart.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.7csuelf-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 (" /*@_init_PROLOG_ENDS*/");
      asm (" /*@_init_EPILOG_BEGINS*/");
      SECTION(".init");
    }
    asm ("END_INIT");

  • 相关阅读:
    一意孤行
    叶子书签
    漫舞
    男朋友
    ubuntu自动关机命令,ubuntu 无法关机解决方法
    情人节
    生死由天
    春暖花开
    android开发学习中的问题:error: device not found解决办法
    暗恋
  • 原文地址:https://www.cnblogs.com/tsecer/p/10485849.html
Copyright © 2011-2022 走看看