__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链表中。