__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链表中。
- __pthread_initialize_minimal源码分析
- 源码分析
- 源码分析
- 源码分析
- 源码分析
- 源码分析
- 源码分析
- 源码分析
- 源码分析
- 源码分析:SparseArray分析
- 源码- Spark Broadcast源码分析
- Android源码/框架源码分析
- 【Android应用源码分析】HandlerThread 源码分析
- 【Android应用源码分析】IntentService 源码分析
- java源码分析01-Object源码分析
- VC++源码分析 - 中国象棋源码分析
- [Java源码分析]ArrayList源码分析
- [java源码分析]LinkedList源码分析
- 字符串压缩算法
- MacOS 开发
- 程序16
- HDU 6201 transaction transaction transaction(网络流+最短路)
- 2017.9月计划
- __pthread_initialize_minimal源码分析
- 程序17
- JS扩展、密封、冻结三大特性
- Kotlin笔记(一)
- leetcode 92. Reverse Linked List II 反转链表
- 关于SVN下 如何添加xcode版本控制
- 小米蓝牙手柄与UnityJoyStick 按键对应 2017
- 2-2、PCA降维
- 频率和概率以及均值和期望的联系区别