pthread_create源码分析
pthread_create源碼分析
下面來看glibc中pthread_create函數(shù)的源碼,分為兩部分來看。
__pthread_create_2_1第一部分
nptl/pthread_create.c
傳入的參數(shù)newthread為即將創(chuàng)建的新線程的pthread結(jié)構(gòu)指針,attr為用戶指定的線程創(chuàng)建屬性,start_routine為新線程執(zhí)行的函數(shù)指針,arg為新線程函數(shù)的參數(shù)地址。
STACK_VARIABLES宏定義了新的堆棧指針stackaddr,stackaddr指向即將分配的線程棧的有效棧頂(不含保護區(qū))。
# define STACK_VARIABLES void *stackaddr = NULL如果用戶未指定線程創(chuàng)建的屬性attr,則使用默認的屬性值default_attr,其定義如下。
static const struct pthread_attr default_attr ={.guardsize = 1,};默認的屬性只定義了棧保護區(qū)guardsize的大小,該保護區(qū)通常用來檢測棧的數(shù)據(jù)是否到達保護區(qū),即是否下溢。
# define ALLOCATE_STACK(attr, pd) allocate_stack (attr, pd, &stackaddr)ALLOCATE_STACK宏用來分配線程棧,并在棧底創(chuàng)建pthread結(jié)構(gòu)并初始化。
然后設(shè)置執(zhí)行線程函數(shù)的地址start_routine和參數(shù)地址arg。
下面重點來看allocate_stack函數(shù)的源碼。
__pthread_create_2_1->allocate_stack 第一部分
glibc nptl/allocatestack.c
首先,如果屬性attr中沒有設(shè)置堆棧大小stacksize,則使用默認值__default_stacksize,在不同的cpu體系結(jié)構(gòu)中的默認值不一樣。__default_stacksize變量在__pthread_initialize_minimal_internal函數(shù)中根據(jù)系統(tǒng)的限制值計算得出。而__pthread_initialize_minimal_internal會在main函數(shù)前調(diào)用,因此在main函數(shù)前就為linux線程做了初始化工作,具體可參考《__pthread_initialize_minimal_internal源碼分析》。
ATTR_FLAG_STACKADDR標志位置位表示由用戶指定棧的地址空間。如果用戶指定了堆棧大小,就檢查該堆棧大小是否小于__static_tls_size + MINIMAL_REST_STACK。MINIMAL_REST_STACK在x64系統(tǒng)中的默認值為2048。__static_tls_size由__pthread_initialize_minimal_internal的_dl_get_tls_static_info函數(shù)賦值并對齊,其默認值為 GL(dl_tls_static_size),而GL(dl_tls_static_size)也是在__pthread_initialize_minimal_internal中初始化,如果可執(zhí)行文件中并沒有定義tls段,則該變量的默認值為初始化的2048個字節(jié)加上pthread結(jié)構(gòu)的大小(參考init_static_tls函數(shù))。
第一個if循環(huán)表示由用戶指定棧的最高地址stackaddr(下面假設(shè)棧由高地址向低地址拓展),TLS_TCB_SIZE宏表示pthread結(jié)構(gòu)的大小。
# define TLS_TCB_SIZE sizeof (struct pthread)接下來計算即將在棧上初始化的pthread結(jié)構(gòu)按照__static_tls_align_m1對齊后,需要扣除多少地址adj。
然后再計算在棧上分配的pthread結(jié)構(gòu)的地址pd,并通過memset函數(shù)將其清0,可以看出pthread結(jié)構(gòu)在新線程的棧底。
specific_1stblock和specific在pthread結(jié)構(gòu)中的定義如下,
struct pthread_key_data{uintptr_t seq;void *data;} specific_1stblock[PTHREAD_KEY_2NDLEVEL_SIZE];struct pthread_key_data *specific[PTHREAD_KEY_1STLEVEL_SIZE];PTHREAD_KEY_2NDLEVEL_SIZE宏的默認值為32。specific相當于二維數(shù)組,specific_1stblock為第一組的數(shù)組,當?shù)谝淮纬^PTHREAD_KEY_2NDLEVEL_SIZE時,就要分配新的pthread_key_data數(shù)組,并將specific[1]指向新分配的數(shù)組,以此類推。
再往下繼續(xù)設(shè)置stackblock記錄線程棧的低端地址也即起始地址,設(shè)置stackblock_size記錄線程棧的大小。
接下來的user_stack成員變量標識這是一個根據(jù)用戶提供的線程棧。
THREAD_SELF返回調(diào)用進程的pthread結(jié)構(gòu),再通過THREAD_GETMEM宏獲取其中的pid值,因此誰調(diào)用了pthread_create函數(shù),就初始化為誰的pid,也即當前進程的pid。
接下來設(shè)置setxid_futex防止setxid函數(shù)的調(diào)用,這里和同步機制有關(guān),回頭碰到了再研究。
再往下通過_dl_allocate_tls函數(shù)在pthread結(jié)構(gòu)中分配dtv并初始化。
然后將剛剛初始化的pthread結(jié)構(gòu)添加到全局的__stack_user鏈表中。
__pthread_create_2_1->allocate_stack->_dl_allocate_tls
elf/dl-tls.c
首先計算即將分配的dtv的個數(shù)dtv_length,其中dl_tls_max_dtv_idx的值在__pthread_initialize_minimal_internal函數(shù)中被初始化為1,DTV_SURPLUS是額外需要分配的內(nèi)存。
dtv結(jié)構(gòu)的定義如下,
接下來通過calloc分配dtv數(shù)組內(nèi)存,返回內(nèi)存的起始指針dtv。注意這里多分配了兩個dtv,其中dtv數(shù)組的第一個項用來記錄dtv數(shù)組的有效大小dtv_length,因此dtv是一個union結(jié)構(gòu)。
INSTALL_DTV將剛剛分配的dtv數(shù)組設(shè)置到pthread結(jié)構(gòu)中。
注意INSTALL_DTV宏是將dtv數(shù)組的第二個元素設(shè)置到pthread結(jié)構(gòu)的dtv成員變量中。
__pthread_create_2_1->allocate_stack->_dl_allocate_tls->_dl_allocate_tls_init
elf/dl-tls.c
傳入的參數(shù)result為pthread結(jié)構(gòu)的地址。
GET_DTV宏和INSTALL_DTV宏相反,用來獲取pthread結(jié)構(gòu)中的dtv結(jié)構(gòu)。
該dtv結(jié)構(gòu)在前面分析的allocate_dtv函數(shù)中分配并初始化。
dl_tls_dtv_slotinfo_list為static_slotinfo中的si成員變量,類型為dtv_slotinfo_list,定義如下
該結(jié)構(gòu)中的len變量記錄了slotinfo中數(shù)組的長度,根據(jù)《__pthread_initialize_minimal_internal源碼分析》中的init_slotinfo函數(shù),dl_tls_dtv_slotinfo_list結(jié)構(gòu)中的slotinfo數(shù)組的第二項也即slotinfo[1]的ink_map變量存儲了程序初始化時的static_map。
接下來遍歷dl_tls_dtv_slotinfo_list中的每個dtv_slotinfo_list結(jié)構(gòu)的每個slotinfo,如果對應(yīng)的link_map中的l_tls_offset為NO_TLS_OFFSET,則清空pthread結(jié)構(gòu)中對應(yīng)位置上dtv結(jié)構(gòu),否則將tls段的數(shù)據(jù)也即l_tls_initimage拷貝到對應(yīng)的dtv結(jié)構(gòu)中。
__pthread_create_2_1->allocate_stack 第二部分
glibc nptl/allocatestack.c
第二種情況是用戶沒有提供新線程的棧空間。
此時首先將size對齊,static_tls_align_m1在__pthread_initialize_minimal_internal中的_dl_get_tls_static_info函數(shù)被初始化,默認為__alignof (struct pthread)。
再往下將guardsize按頁pagesize_m1對齊,前面看到傳入的參數(shù)屬性attr中的guardsize成員變量默認為1,guardsize為一個頁面。
再往下檢查即將分配的棧的大小size是否充足。
get_cached_stack從stack_cache緩存鏈表中獲得空閑的棧和棧的大小,分別保存在mem和size中。
如果從緩存中沒有找到合適的棧,就通過mmap函數(shù)在堆上分配size大小內(nèi)存空間,然后在棧底初始化一個pthread結(jié)構(gòu),重點是設(shè)置了棧的起始地址(最低地址)和大小到stackblock和stackblock_size成員變量中,并且將pthread結(jié)構(gòu)的pid設(shè)置為調(diào)用進程的pid。
_dl_allocate_tls函數(shù)分配并初始化pthread結(jié)構(gòu)的dtv數(shù)組,該函數(shù)在前面已經(jīng)分析過了。
再往下如果有別的進程修改了dl_stack_flags變量,即將原來對應(yīng)的PF_X的值由0修改為了1,就要修改內(nèi)存的屬性。change_stack_perm函數(shù)內(nèi)部通過mprotect系統(tǒng)調(diào)用在棧對應(yīng)的內(nèi)存空間增加PF_X屬性,表示對應(yīng)的內(nèi)存段可執(zhí)行。
__pthread_create_2_1->allocate_stack->get_cached_stack
glibc nptl/allocatestack.c
stack_cache被初始化為一個鏈表頭,即其next變量指向自身。
static LIST_HEAD (stack_cache);list_for_each宏遍歷該鏈表,再利用list_entry宏根據(jù)entry指針獲得pthread的結(jié)構(gòu)指針。pthread結(jié)構(gòu)中的list結(jié)構(gòu)變量組成鏈表stack_cache,因此獲得list地址entry之后,將其減去list變量在pthread結(jié)構(gòu)中的偏移,就獲得了pthread結(jié)構(gòu)的起始地址。
FREE_P宏檢查該pthread結(jié)構(gòu)是否在使用中,其實是檢查pthread結(jié)構(gòu)的tid成員變量是否小于等于0。
如果有空閑的pthread結(jié)構(gòu),并且其棧的大小stackblock_size等于即將建立的棧的大小size,就直接使用該pthread結(jié)構(gòu),否則就選擇所有棧大于即將分配的棧的大小size中最小的一個空閑的pthread結(jié)構(gòu)。
再往下如果沒有空閑的pthread結(jié)構(gòu),或者空閑的pthread結(jié)構(gòu)大小大于四倍的需求大小size,就返回null。
如果從鏈表stack_cache中找到了合適的pthread結(jié)構(gòu),接下來就重新初始化該結(jié)構(gòu),將其從鏈表stack_cache中刪除,加入stack_used鏈表中,表示該pthread結(jié)構(gòu)已經(jīng)在使用中了。
stack_cache_actsize記錄了stack_cache鏈表中所有空閑pthread結(jié)構(gòu)的stackblock_size大小的和,因此將其減去即將使用的pthread結(jié)構(gòu)的stackblock_size大小。
然后設(shè)置傳入的參數(shù)sizep和mem,分別表示新的棧的大小和起始地址,用于返回。
再往下清空pthread結(jié)構(gòu)的dtv變量,注意dtv數(shù)組的第一個項是用來記錄dtv數(shù)組長度的,因此從第二個項開始釋放內(nèi)存。然后遍歷每個項,通過free函數(shù)將非靜態(tài)并且已經(jīng)分配內(nèi)存的dtv_t結(jié)構(gòu)釋放,最后通過memset函數(shù)清空該數(shù)組,注意這里講counter加1是因為allocate_dtv函數(shù)中通過calloc分配dtv內(nèi)存時多分配了一塊內(nèi)存。
最后通過_dl_allocate_tls_init初始化該結(jié)構(gòu),該函數(shù)在前面已經(jīng)分析過了,然后返回剛剛從stack_cache中找到的pthread結(jié)構(gòu)。
__pthread_create_2_1->allocate_stack 第三部分
glibc nptl/allocatestack.c
執(zhí)行到這里,已經(jīng)在棧上分配了內(nèi)存并在棧底初始化了pthread結(jié)構(gòu)。接下來調(diào)整棧上的保護區(qū)即guardsize的大小。
第一種情況是用戶需要的保護區(qū)大小guardsize大于當前pthread結(jié)構(gòu)中g(shù)uardsize的大小,如果pthread是剛剛在堆上通過mmap分配的,則此時pthread結(jié)構(gòu)的guardsize成員變量為0,因此條件成立,此時通過mprotect函數(shù)將棧頂向上guardsize大小的內(nèi)存屬性設(shè)置為PROT_NONE,因此當有數(shù)據(jù)訪問這段內(nèi)存時,便會拋出異常,起到了保護線程棧的效果。
第二種情況是從緩存中找到的空閑的??臻g的保護區(qū)的大小大于需要的保護區(qū)的大小,此時如果空閑的棧沒有足夠的空間,就要通過mprotect函數(shù)刪除多余的保護區(qū)大小。
最后將pthread結(jié)構(gòu)的地址存入pdp指針中并返回,并設(shè)置棧的有效棧頂?shù)牡刂穝tacktop,即棧底減去一個pthread結(jié)構(gòu)的大小再減去__static_tls_size的大小,從前面的分析可知,此時有效的棧大小就為2048個字節(jié),即_dl_tls_static_size的默認值。
最后將該地址存入傳入的參數(shù)stack中并返回。
__pthread_create_2_1第二部分
nptl/pthread_create.c
首先通過THREAD_SELF獲取當前進程的pthread結(jié)構(gòu)。
接下來當前pthread結(jié)構(gòu)的ATTR_FLAG_SCHED_SET和ATTR_FLAG_POLICY_SET值設(shè)置flag。
然后設(shè)置pthread的各個成員變量,其中schedpolicy表示線程的調(diào)度策略,schedparam表示線程調(diào)度的優(yōu)先級,首先使用調(diào)用進程的schedpolicy和schedparam進行賦值。
如果用戶指定了ATTR_FLAG_NOTINHERITSCHED標志,則表示新的線程不使用調(diào)用進程的調(diào)度策略,并且用戶指定了ATTR_FLAG_SCHED_SET和ATTR_FLAG_POLICY_SET其中一個,此時就要重新設(shè)置新線程的調(diào)度策略。
如果用戶指定了ATTR_FLAG_POLICY_SET標志位,則直接使用用戶指定的調(diào)度策略schedpolicy,否則,如果調(diào)用進程的ATTR_FLAG_POLICY_SET標志位被置位,則新線程通過sched_getscheduler系統(tǒng)調(diào)用獲得當前進程的調(diào)度策略。
類似的,如果用戶指定了ATTR_FLAG_SCHED_SET標志位,則直接使用用戶指定的調(diào)度優(yōu)先級schedparam,否則,如果調(diào)用進程的ATTR_FLAG_SCHED_SET標志位被置位,則新線程通過sched_getparam系統(tǒng)調(diào)用獲得當前進程的調(diào)度優(yōu)先級。
再往下檢查新線程的調(diào)度優(yōu)先級是否在合理的范圍內(nèi),分別通過sched_get_priority_min和sched_get_priority_max函數(shù)獲得優(yōu)先級的最小值和最大值。
最后將前面創(chuàng)建的pthread結(jié)構(gòu)賦值給傳入的參數(shù)newthread,最后調(diào)用create_thread繼續(xù)創(chuàng)建線程。create_thread函數(shù)留在下一章繼續(xù)分析。
__pthread_create_2_1->sched_getscheduler
linux kernel/sched/core.c
傳入的參數(shù)pid為0,因此find_process_by_pid函數(shù)會簡單返回當前進程的task_struct結(jié)構(gòu),然后獲得當前進程的調(diào)度策略policy并返回。
__pthread_create_2_1->sched_get_priority_min
linux kernel/sched/core.c
sched_get_priority_min系統(tǒng)調(diào)用返回調(diào)度優(yōu)先級的最小值,當調(diào)度策略為SCHED_FIFO或SCHED_RR時,默認的調(diào)度優(yōu)先級最小值為1。
__pthread_create_2_1->sched_get_priority_max
linux kernel/sched/core.c
MAX_USER_RT_PRIO的默認值為100,因此當調(diào)度策略為SCHED_FIFO或SCHED_RR時,調(diào)度優(yōu)先級的最大值為99。
總結(jié)
以上是生活随笔為你收集整理的pthread_create源码分析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 微信小程序输入框输入换行
- 下一篇: 电信宽带服务器维护,电信网络维护部工作日