__pthread_initialize_minimal源码分析

来源:互联网 发布:java读取hdfs文件目录 编辑:程序博客网 时间:2024/06/03 20:31

__pthread_initialize_minimal源码分析

__pthread_initialize_minimal在__libc_start_main中被调用,下面来看
glibc nptl/nptl-init.c

void __pthread_initialize_minimal_internal (void){  __libc_setup_tls (TLS_TCB_SIZE, TLS_TCB_ALIGN);  struct pthread *pd = THREAD_SELF;  pd->pid = pd->tid = INTERNAL_SYSCALL (set_tid_address, err, 1, &pd->tid);  THREAD_SETMEM (pd, specific[0], &pd->specific_1stblock[0]);  THREAD_SETMEM (pd, user_stack, true);  pd->robust_head.list = &pd->robust_head;  set_robust_list_not_avail ();  THREAD_SETMEM (pd, stackblock_size, (size_t) __libc_stack_end);  INIT_LIST_HEAD (&__stack_user);  list_add (&pd->list, &__stack_user);  struct sigaction sa;  sa.sa_sigaction = sigcancel_handler;  sa.sa_flags = SA_SIGINFO;  __sigemptyset (&sa.sa_mask);  (void) __libc_sigaction (SIGCANCEL, &sa, NULL);  sa.sa_sigaction = sighandler_setxid;  sa.sa_flags = SA_SIGINFO | SA_RESTART;  (void) __libc_sigaction (SIGSETXID, &sa, NULL);  __sigaddset (&sa.sa_mask, SIGCANCEL);  __sigaddset (&sa.sa_mask, SIGSETXID);  (void) INTERNAL_SYSCALL (rt_sigprocmask, err, 4, SIG_UNBLOCK, &sa.sa_mask,               NULL, _NSIG / 8);  size_t static_tls_align;  _dl_get_tls_static_info (&__static_tls_size, &static_tls_align);  if (STACK_ALIGN > static_tls_align)    static_tls_align = STACK_ALIGN;  __static_tls_align_m1 = static_tls_align - 1;  __static_tls_size = roundup (__static_tls_size, static_tls_align);  struct rlimit limit;  if (getrlimit (RLIMIT_STACK, &limit) != 0      || limit.rlim_cur == RLIM_INFINITY)    limit.rlim_cur = ARCH_STACK_DEFAULT_SIZE;  else if (limit.rlim_cur < PTHREAD_STACK_MIN)    limit.rlim_cur = PTHREAD_STACK_MIN;  const uintptr_t pagesz = GLRO(dl_pagesize);  const size_t minstack = pagesz + __static_tls_size + MINIMAL_REST_STACK;  if (limit.rlim_cur < minstack)    limit.rlim_cur = minstack;  limit.rlim_cur = (limit.rlim_cur + pagesz - 1) & -pagesz;  __default_stacksize = limit.rlim_cur;  GL(dl_init_static_tls) = &__pthread_init_static_tls;  GL(dl_wait_lookup_done) = &__wait_lookup_done;    __libc_pthread_init (&__fork_generation, __reclaim_stacks,             ptr_pthread_functions);  __is_smp = is_smp_system ();}

首先通过__libc_setup_tls函数进行初始化工作,传入的参数分别为pthread结构的大小和对齐。

# define TLS_INIT_TCB_SIZE sizeof (struct pthread)# define TLS_INIT_TCB_ALIGN __alignof__ (struct pthread)

THREAD_SELF宏从fs寄存器中获取当前进程的pthread结构。
接下来的INTERNAL_SYSCALL宏通过set_tid_address系统调用获取进程的id,以及设置clear_child_tid变量,用于在进程退出时通知在futex机制中等待的进程,futex同步机制有空再分析。

THREAD_SETMEM宏用于将specific_1stblock指针地址赋值给specific,THREAD_SETMEM只是会根据不同cpu的地址大小作不同的操作。specific_1stblock和specific的指针类型同为pthread_key_data,用于存放tls数据,二者构成一个pthread_key_data类型的二维数组,specific为第一级指针,specific_1stblock为第二级中第一个数组的指针,当specific_1stblock空间不足时,就会在specific上开辟新的空间,具体可查看__pthread_setspecific的源码。

接下来将user_stack设置为true,表示该线程的堆栈是否是用户提供的。

接下来的两行和robust互斥锁有关,本章不关心。
接下来设置线程中用户堆栈的大小stackblock_size为__libc_stack_end,__libc_stack_end为__libc_start_main函数中传入的当前堆栈的最低地址,这里只是对stackblock_size的初始化,在调用pthread_create创建线程时会重新设置。

再往下初始化__stack_user链表,再将__stack_user链表添加到当前线程pthread结构的list中。如果有新的线程是通过用户指定分配堆栈的,就将该新线程的pthread链接到__stack_user链表中。

再往下注册了两个信号处理函数,sigcancel_handler用于处理SIGCANCEL信号,sighandler_setxid用于处理SIGSETXID信号,__libc_sigaction会通过rt_sigaction系统调用最终通过do_sigaction将信号处理函数注册到当前进程结构中的sighand中区。最后通过rt_sigprocmask系统调用对当前进程的阻塞信号进行修改,由于传入的参数是SIG_UNBLOCK,因此接触当前进程阻塞信号中的SIGCANCEL以及SIGSETXID信号。

再往下计算并设置__static_tls_size,static_tls_align分别代表线程堆栈大小和对齐。

再往下需要获得默认的线程堆栈大小,getrlimit是获得linux系统的资源限制中的栈限制,接下来对获得的值进行调整。最后将rlim_cur赋值给__default_stacksize。

is_smp_system返回是否是一个smp系统,默认值为1。

下面首先来看__libc_setup_tls初始化函数。

__pthread_initialize_minimal_internal->__libc_setup_tls
glibc csu/libc-tls.c

void__libc_setup_tls (size_t tcbsize, size_t tcbalign){  void *tlsblock;  size_t memsz = 0;  size_t filesz = 0;  void *initimage = NULL;  size_t align = 0;  size_t max_align = tcbalign;  size_t tcb_offset;  ElfW(Phdr) *phdr;  ...  tcb_offset = roundup (memsz + GL(dl_tls_static_size), tcbalign);  tlsblock = __sbrk (tcb_offset + tcbsize + max_align);  tlsblock = (void *) (((uintptr_t) tlsblock + max_align - 1)               & ~(max_align - 1));  static_dtv[0].counter = (sizeof (static_dtv) / sizeof (static_dtv[0])) - 2;  static_dtv[2].pointer.val = ((char *) tlsblock + tcb_offset                   - roundup (memsz, align ?: 1));  static_map.l_tls_offset = roundup (memsz, align ?: 1);  static_dtv[2].pointer.is_static = true;  memcpy (static_dtv[2].pointer.val, initimage, filesz);  INSTALL_DTV ((char *) tlsblock + tcb_offset, static_dtv);  TLS_INIT_TP ((char *) tlsblock + tcb_offset, 0);  static_map.l_tls_align = align;  static_map.l_tls_blocksize = memsz;  static_map.l_tls_initimage = initimage;  static_map.l_tls_initimage_size = filesz;  static_map.l_type = lt_executable;  static_map.l_tls_modid = 1;  init_slotinfo ();  static_slotinfo.si.slotinfo[1].map = &static_map;  memsz = roundup (memsz, align ?: 1);  init_static_tls (memsz, MAX (TLS_TCB_ALIGN, max_align));}

从上面的分析可知,传入的参数tcbsize为TLS_TCB_SIZE,值为pthread结构大小,参数tcbalign为TLS_TCB_ALIGN,值为pthread的对齐方式。

省略的部分为当前进程有tls段时,从该段中读取各个信息,这里不考虑。

接下来通过__sbrk为假设存在的tls段中标注的占用的内存p_memsz,dl_tls_static_size以及tcb即pthread分配内存空间并对齐,分配默认的dl_tls_static_size值为2048。

static_dtv代表静态的tls变量,接下来初始化static_dtv数组,计算static_dtv数组的容量,减去2是因为static_dtv数组的前两项留作他用,将数组长度存入static_dtv数组的第一个元素中。

接下来的static_dtv[2].pointer.val指向分配的内存地址加上一个tcb_offset再减去一个memsz,从这里开始调用memcpy函数拷贝initimage(如果存在)的内容。

再往下通过INSTALL_DTV宏将static_dtv设置到pthread结构中的tcbhead_t中的dtv成员变量里。

# define INSTALL_DTV(descr, dtvp) \  ((tcbhead_t *) (descr))->dtv = (dtvp) + 1

然后通过TLS_INIT_TP宏初始化pthread结构,并将其设置到fs寄存器中。

最后设置static_map,设置到static_slotinfo中,并通过init_slotinfo函数以及init_static_tls函数完成最后的初始化。

__pthread_initialize_minimal_internal->__libc_setup_tls->TLS_INIT_TP
glibc nptl/sysdeps/x86_64/tls.h

# define TLS_INIT_TP(thrdescr, secondcall) \  ({ void *_thrdescr = (thrdescr);                        \     tcbhead_t *_head = _thrdescr;                        \     int _result;                                 \                                                  \     _head->tcb = _thrdescr;                              \     _head->self = _thrdescr;                             \                           \     asm volatile ("syscall"                              \           : "=a" (_result)                       \           : "0" ((unsigned long int) __NR_arch_prctl),           \             "D" ((unsigned long int) ARCH_SET_FS),           \             "S" (_thrdescr)                          \           : "memory", "cc", "r11", "cx");                \                                          \    _result ? "cannot set %fs base address for thread-local storage" : 0;     \  })

传入的参数thrdescr为pthread结构的指针,也是pthread中第一个成员变量tcbhead_t header指针,TLS_INIT_TP宏首先将该指针赋值给_head。将_head的tcb成员变量指向pthread结构的指针,将self成员变量指向其自身,因为tcbhead_t结构头指针是pthread结构的第一个成员变量,两个指针其实是同一个值。
最后通过arch_prctl系统调用将pthread结构设置到fs寄存器中。

__pthread_initialize_minimal_internal->__libc_setup_tls->TLS_INIT_TP->sys_arch_prctl
linux arch/x86/kernel/process_64.c

long sys_arch_prctl(int code, unsigned long addr){    return do_arch_prctl(current, code, addr);}long do_arch_prctl(struct task_struct *task, int code, unsigned long addr){    int ret = 0;    int doit = task == current;    int cpu;    switch (code) {    case ARCH_SET_GS:        ...    case ARCH_SET_FS:        if (addr >= TASK_SIZE_OF(task))            return -EPERM;        cpu = get_cpu();        if (addr <= 0xffffffff) {            set_32bit_tls(task, FS_TLS, addr);            if (doit) {                load_TLS(&task->thread, cpu);                loadsegment(fs, FS_TLS_SEL);            }            task->thread.fsindex = FS_TLS_SEL;            task->thread.fs = 0;        } else {            task->thread.fsindex = 0;            task->thread.fs = addr;            if (doit) {                loadsegment(fs, 0);                ret = wrmsrl_safe(MSR_FS_BASE, addr);            }        }        put_cpu();        break;    case ARCH_GET_FS: {        ...    }    case ARCH_GET_GS: {        ...    }    default:        ...    }    return ret;}

由前面可知,do_arch_prctl系统调用传入的参数code为ARCH_SET_FS,表示设置fs寄存器,另外的ARCH_GET_FS、ARCH_SET_GS、ARCH_GET_GS分别是取fs寄存器值,存GS寄存器,取GS寄存器值。
TASK_SIZE_OF宏返回用户空间的最高低至,对32位的cpu而言是0xc0000000,传入的参数addr是pthread结构指针,由前面可知该结构是通过sbrk函数在堆上分配内存的,因此判断肯定成立。

再往下考虑两种情况:
第一种情况是pthread的结构地址小于32比特位的最高值,也即0xffffffff。此时首先通过set_32bit_tls函数将pthread结构设置到tls_array数组中,然后通过load_TLS宏将tls_array数组设置到gdt表中,接着通过loadsegment宏将tls_array中pthread的地址信息设置到fs寄存器中,最后设置pthread的fsindex变量为FS_TLS_SEL,FS_TLS_SEL的值与gdt表中的索引相关联。

第二种情况当地址大于0xffffffff时,使用64位的fs寄存器,MSR_FS_BASE是64位fs寄存器的MSR标识。首先通过loadsegment宏清空fs寄存器,wrmsrl_safe函数最终通过wrmsr指令将pthread结构地址写入fs寄存器中。

这两种情况的不同是出于性能的考虑,如果地址小于32位(0xffffffff),就使用gdt表存放地址信息,利用gdt表中每个段所能表示的最高32位地址(参考每个gdt项的格式)进行存储,而不使用msr寄存器,因为在进程切换时,wrmsr或者rdmsr指令的开销较大。

__pthread_initialize_minimal_internal->__libc_setup_tls->TLS_INIT_TP->sys_arch_prctl->set_32bit_tls
linux arch/x86/kernel/process_64.c

static inline void set_32bit_tls(struct task_struct *t, int tls, u32 addr){    struct user_desc ud = {        .base_addr = addr,        .limit = 0xfffff,        .seg_32bit = 1,        .limit_in_pages = 1,        .useable = 1,    };    struct desc_struct *desc = t->thread.tls_array;    desc += tls;    fill_ldt(desc, &ud);}

传入的参数tls为thread_struct结构中tls_array数组的索引值FS_TLS,gdt表中关于tls的项有三个,因此tls_array数组的大小为3,FS_TLS为0表示数组中的第一个元素。

set_32bit_tls函数首先获得tls_array数组中对应的desc_struct结构指针desc,然后根据数组索引值tls获得对应项的指针,最后通过fill_ldt函数将pthread结构的地址addr设置到tls_array数组中。

__pthread_initialize_minimal_internal->__libc_setup_tls->TLS_INIT_TP->sys_arch_prctl->load_TLS
linux arch/x86/include/asm/desc.c

#define load_TLS(t, cpu)            native_load_tls(t, cpu)static inline void native_load_tls(struct thread_struct *t, unsigned int cpu){    struct desc_struct *gdt = get_cpu_gdt_table(cpu);    unsigned int i;    for (i = 0; i < GDT_ENTRY_TLS_ENTRIES; i++)        gdt[GDT_ENTRY_TLS_MIN + i] = t->tls_array[i];}

load_TLS宏进而调用native_load_tls函数,首先获得当前cpu对应的gdt表,然后依次将thread_struct结构中的tls_array数组存入该gdt表中。由于gdt表中有关tls的项有个三个,因此GDT_ENTRY_TLS_ENTRIES的值为3。GDT_ENTRY_TLS_MIN在32位系统中为6,也即第6个gdt表项,在64位系统中为12。

__pthread_initialize_minimal_internal->__libc_setup_tls->TLS_INIT_TP->sys_arch_prctl->loadsegment
linux arch/x86/include/asm/segment.h

#define loadsegment(seg, value)                     \do {                                    \    unsigned short __val = (value);                 \                                    \    asm volatile("                      \n" \             "1:    movl %k0,%%" #seg "     \n" \                                    \             ".section .fixup,\"ax\"            \n" \             "2:    xorl %k0,%k0            \n" \             "      jmp 1b              \n" \             ".previous                 \n" \                                    \             _ASM_EXTABLE(1b, 2b)               \                                    \             : "+r" (__val) : : "memory");          \} while (0)

loadsegment宏用于将值value存入seg标识的寄存器中。底下的fixup段和异常处理有关,这里就不关心了。

__pthread_initialize_minimal_internal->__libc_setup_tls->TLS_INIT_TP->sys_arch_prctl->wrmsrl_safe
linux arch/x86/include/asm/msr.h

#define wrmsrl_safe(msr, val) wrmsr_safe((msr), (u32)(val),     \                         (u32)((val) >> 32))static inline int wrmsr_safe(unsigned msr, unsigned low, unsigned high){    return native_write_msr_safe(msr, low, high);}

wrmsrl_safe宏会将64位地址分为高低两部分,最后通过native_write_msr_safe函数存入64位fs寄存器中,这里就不往下看了。

回到__libc_setup_tls函数中。
__pthread_initialize_minimal_internal->__libc_setup_tls->init_slotinfo
glibc csu/libc-tls.c

static inline void init_slotinfo (void){  static_slotinfo.si.len = (((char *) (&static_slotinfo + 1)                 - (char *) &static_slotinfo.si.slotinfo[0])                / sizeof static_slotinfo.si.slotinfo[0]);  GL(dl_tls_max_dtv_idx) = 1;  GL(dl_tls_dtv_slotinfo_list) = &static_slotinfo.si;}

首先计算static_slotinfo静态结构中dtv_slotinfo数组的长度并保存在dtv_slotinfo_list结构(也即si)的成员变量len中,后面如果有碰到再分析该结构。

GL(dl_tls_max_dtv_idx)初始化为1,表示静态tls中的最大值,后面碰到了再分析,GL(dl_tls_dtv_slotinfo_list)指向dtv_slotinfo_list结构。

回到__pthread_initialize_minimal_internal函数中。
__pthread_initialize_minimal_internal->set_tid_address
linux kernel/fork.c

SYSCALL_DEFINE1(set_tid_address, int __user *, tidptr){    current->clear_child_tid = tidptr;    return task_pid_vnr(current);}static inline pid_t task_pid_vnr(struct task_struct *tsk){    return __task_pid_nr_ns(tsk, PIDTYPE_PID, NULL);}pid_t __task_pid_nr_ns(struct task_struct *task, enum pid_type type,            struct pid_namespace *ns){    pid_t nr = 0;    rcu_read_lock();    if (!ns)        ns = task_active_pid_ns(current);    if (likely(pid_alive(task))) {        nr = pid_nr_ns(task->pids[type].pid, ns);    }    rcu_read_unlock();    return nr;}

set_tid_address系统调用最终返回进程的pid值,往下最后通过__task_pid_nr_ns获得进程所属的最高层级的pid值。下面一一来看。

__pthread_initialize_minimal_internal->set_tid_address->task_active_pid_ns
linux kernel/pid.c

struct pid_namespace *task_active_pid_ns(struct task_struct *tsk){    return ns_of_pid(task_pid(tsk));}static inline struct pid *task_pid(struct task_struct *task){    return task->pids[PIDTYPE_PID].pid;}static inline struct pid_namespace *ns_of_pid(struct pid *pid){    struct pid_namespace *ns = NULL;    if (pid)        ns = pid->numbers[pid->level].ns;    return ns;}

task_active_pid_ns最终获得进程tsk的pid对应的命名空间pid_namespace。

task_struct的pid数组中除了PIDTYPE_PID类型,还有PIDTYPE_PGID进程组ID,PIDTYPE_SID会话ID,多个进程属于一个进程组,多个进程组属于一个会话。

pid的numbers数组类型为upid,pid的level变量代表在pid命名空间中的层级,获得该层级的upid后就获得了其pid命名空间ns。

__pthread_initialize_minimal_internal->set_tid_address->pid_nr_ns
linux kernel/pid.c

pid_t pid_nr_ns(struct pid *pid, struct pid_namespace *ns){    struct upid *upid;    pid_t nr = 0;    if (pid && ns->level <= pid->level) {        upid = &pid->numbers[ns->level];        if (upid->ns == ns)            nr = upid->nr;    }    return nr;}

pid_nr_ns的作用是在pid以及该pid所属的命名空间ns中,找到该进程在该命名空间中的pid值nr。同一个进程的pid在不同的pid命名空间中的值没有关联,因此首先在pid结构中获得pid命名空间对应的upid结构,upid封装了pid值和命名空间ns,因此直接取出该pid值即可。

__pthread_initialize_minimal_internal->_dl_get_tls_static_info
glibc elf/dl-tls.c

void internal_function _dl_get_tls_static_info (size_t *sizep, size_t *alignp){  *sizep = GL(dl_tls_static_size);  *alignp = GL(dl_tls_static_align);}

_dl_get_tls_static_info获得前面在__libc_setup_tls函数中设置的_dl_tls_static_size和_dl_tls_static_align。

__pthread_initialize_minimal_internal->prlimit64
linux kernel/sys.c

SYSCALL_DEFINE4(prlimit64, pid_t, pid, unsigned int, resource,        const struct rlimit64 __user *, new_rlim,        struct rlimit64 __user *, old_rlim){    struct rlimit64 old64, new64;    struct rlimit old, new;    struct task_struct *tsk;    int ret;    rcu_read_lock();    tsk = pid ? find_task_by_vpid(pid) : current;    ret = check_prlimit_permission(tsk);    get_task_struct(tsk);    rcu_read_unlock();    ret = do_prlimit(tsk, resource, new_rlim ? &new : NULL,            old_rlim ? &old : NULL);    if (!ret && old_rlim) {        rlim_to_rlim64(&old, &old64);        if (copy_to_user(old_rlim, &old64, sizeof(old64)))            ret = -EFAULT;    }    put_task_struct(tsk);    return ret;}

参数pid默认值为0。check_prlimit_permission进行权限检查。
get_task_struct和put_task_struct函数分别用于递增和递减task_struct中usage变量。

#define get_task_struct(tsk) do { atomic_inc(&(tsk)->usage); } while(0)

接下来最主要通过do_prlimit函数获取栈的限制值,通过rlim_to_rlim64函数将rlimit结构转化为rlimit64结构,最后拷贝到参数old_rlim中并返回。

__pthread_initialize_minimal_internal->prlimit64->do_prlimit
linux kernel/sys.c

int do_prlimit(struct task_struct *tsk, unsigned int resource,        struct rlimit *new_rlim, struct rlimit *old_rlim){    struct rlimit *rlim;    int retval = 0;    rlim = tsk->signal->rlim + resource;    if (!retval) {        if (old_rlim)            *old_rlim = *rlim;    }    return retval;}

do_prlimit主要是从task结构的成员变量signal的rlim数组中取出resource也即RLIMIT_STACK对应的rlimit结构,拷贝到参数old_rlim中并返回。

__pthread_initialize_minimal_internal->__libc_pthread_init
glibc nptl/sysdeps/unix/sysv/linux/libc_thread_init.c

void __libc_pthread_init (ptr, reclaim, functions)     unsigned long int *ptr;     void (*reclaim) (void);     const struct pthread_functions *functions;{  __fork_generation_pointer = ptr;  __register_atfork (NULL, NULL, reclaim, NULL);}

传入的参数ptr为__fork_generation,reclaim为__reclaim_stacks。

__fork_generation_pointer设置为__fork_generation,用来标识是第几层的子进程,例如一个子进程会继续fork出一个子进程,此时需要递增__fork_generation值。

__reclaim_stacks函数用于回收多余的栈空间,后面碰到了再分析。调用__register_atfork函数注册__reclaim_stacks函数,该注册的函数会在fork函数返回前被调用。

__pthread_initialize_minimal_internal->__libc_pthread_init->__register_atfork
glibc nptl/sysdeps/unix/sysv/linux/register-atfork.c

int __register_atfork (prepare, parent, child, dso_handle)     void (*prepare) (void);     void (*parent) (void);     void (*child) (void);     void *dso_handle;{  struct fork_handler *newp = fork_handler_alloc ();  if (newp != NULL)    {      newp->prepare_handler = prepare;      newp->parent_handler = parent;      newp->child_handler = child;      newp->dso_handle = dso_handle;      __linkin_atfork (newp);    }  return newp == NULL ? ENOMEM : 0;}

fork_handler_alloc会从fork_handler_pool中查找或分配一个fork_handler,然后向其挂入handler,这里只挂入child_handler也即__reclaim_stacks,child_handler中的函数会在fork函数返回前被调用(具体可以查看glibc的__libc_fork源码)。

最后通过__linkin_atfork函数将该fork_handler插入到全局的__fork_handlers链表中。