Linux用户空间线程管理介绍之二:创建线程堆栈
                                                            生活随笔
收集整理的這篇文章主要介紹了
                                Linux用户空间线程管理介绍之二:创建线程堆栈
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.                        
                                轉自:http://www.longene.org/forum/viewtopic.php?f=17&t=429&sid=babec6ba82dd65e29c5fafe03e4d89c0
 
前面已經介紹過了線程結構pthread,下面就需要來看看在創建線程過程中,如何生成這個結構的。Allocate_stack函數位于nptl/allocatestack.c中:
 
309? ?allocate_stack (const struct pthread_attr *attr, struct pthread **pdp,
310? ?? ?? ?ALLOCATE_STACK_PARMS)
311? ?{
312? ?? struct pthread *pd;
313? ?? size_t size;
314? ?? size_t pagesize_m1 = __getpagesize () - 1;
315? ?? void *stacktop;
316? ?
317? ?? assert (attr != NULL);
318? ?? assert (powerof2 (pagesize_m1 + 1));
319? ?? assert (TCB_ALIGNMENT >= STACK_ALIGN);
320? ?
321? ?? /* Get the stack size from the attribute if it is set.? Otherwise we
322? ?? ? ?use the default we determined at start time.? */
323? ?? size = attr->stacksize ?: __default_stacksize;
324? ?
325? ?? /* Get memory for the stack.? */
326? ?? if (__builtin_expect (attr->flags & ATTR_FLAG_STACKADDR, 0))
327? ?? ? {
.........
410? ?? ? }
411? ?? else
412? ?? ? {
用戶程序在調用pthread_create時,可以傳進一個參數pthread_attr,這個參數可以指定堆棧地址、大小等參數,323行的意思就是說在指定堆棧大小的情況下,采用指定大小,否則采用默認大小。__default_stacksize可以有ulimit -s查看,在一般系統中,這個值為8M。在通常情況下,應用程序是不指定堆棧大小的。
326行開始,分兩種情況處理堆棧地址是否由pthread_attr中指定,在通常情況下,這個地址也是不指定的,因此,直接看412行開始的else部分:
代碼:?全選
413? ?? ? ? /* Allocate some anonymous memory.? If possible use the cache.? */
414? ?? ? ? size_t guardsize;
415? ?? ? ? size_t reqsize;
416? ?? ? ? void *mem;
417? ?? ? ? const int prot = (PROT_READ | PROT_WRITE
418? ?? ?? ?? ?| ((GL(dl_stack_flags) & PF_X) ? PROT_EXEC : 0));
419? ?
420? ?#if COLORING_INCREMENT != 0
421? ?? ? ? /* Add one more page for stack coloring.? Don't do it for stacks
422? ?? ? with 16 times pagesize or larger.? This might just cause
423? ?? ? unnecessary misalignment.? */
424? ?? ? ? if (size <= 16 * pagesize_m1)
425? ?? ?size += pagesize_m1 + 1;
426? ?#endif
427? ?
428? ?? ? ? /* Adjust the stack size for alignment.? */
429? ?? ? ? size &= ~__static_tls_align_m1;
430? ?? ? ? assert (size != 0);
431? ?
432? ?? ? ? /* Make sure the size of the stack is enough for the guard and
433? ?? ? eventually the thread descriptor.? */
434? ?? ? ? guardsize = (attr->guardsize + pagesize_m1) & ~pagesize_m1;
435? ?? ? ? if (__builtin_expect (size < ((guardsize + __static_tls_size
436? ?? ?? ?? ?? ?? ? ?+ MINIMAL_REST_STACK + pagesize_m1)
437? ?? ?? ?? ?? ?? ? & ~pagesize_m1),
438? ?? ?? ?? ?? ? 0))
439? ?? ?/* The stack is too small (or the guard too large).? */
440? ?? ?return EINVAL;
441? ?
442? ?? ? ? /* Try to get a stack from the cache.? */
443? ?? ? ? reqsize = size;
444? ?? ? ? pd = get_cached_stack (&size, &mem);
445? ?? ? ? if (pd == NULL)
446? ?? ?{
447? ?? ?? /* To avoid aliasing effects on a larger scale than pages we
448? ?? ?? ? ?adjust the allocated stack size if necessary.? This way
449? ?? ?? ? ?allocations directly following each other will not have
450? ?? ?? ? ?aliasing problems.? */
451? ?#if MULTI_PAGE_ALIASING != 0
452? ?? ?? if ((size % MULTI_PAGE_ALIASING) == 0)
453? ?? ?? ? size += pagesize_m1 + 1;
454? ?#endif
455? ?
456? ?? ?? mem = mmap (NULL, size, prot,
457? ?? ?? ?? ? ? MAP_PRIVATE | MAP_ANONYMOUS | ARCH_MAP_FLAGS, -1, 0);
458? ?
459? ?? ?? if (__builtin_expect (mem == MAP_FAILED, 0))
460? ?? ?? ? {
461? ?#ifdef ARCH_RETRY_MMAP
462? ?? ?? ? ? mem = ARCH_RETRY_MMAP (size);
463? ?? ?? ? ? if (__builtin_expect (mem == MAP_FAILED, 0))
464? ?#endif
465? ?? ?? ?return errno;
466? ?? ?? ? }
467? ?
468? ?? ?? /* SIZE is guaranteed to be greater than zero.
469? ?? ?? ? ?So we can never get a null pointer back from mmap.? */
470? ?? ?? assert (mem != NULL);
471? ?
472? ?#if COLORING_INCREMENT != 0
473? ?? ?? /* Atomically increment NCREATED.? */
474? ?? ?? unsigned int ncreated = atomic_increment_val (&nptl_ncreated);
475? ?
476? ?? ?? /* We chose the offset for coloring by incrementing it for
477? ?? ?? ? ?every new thread by a fixed amount.? The offset used
478? ?? ?? ? ?module the page size.? Even if coloring would be better
479? ?? ?? ? ?relative to higher alignment values it makes no sense to
480? ?? ?? ? ?do it since the mmap() interface does not allow us to
481? ?? ?? ? ?specify any alignment for the returned memory block.? */
482? ?? ?? size_t coloring = (ncreated * COLORING_INCREMENT) & pagesize_m1;
483? ?
484? ?? ?? /* Make sure the coloring offsets does not disturb the alignment
485? ?? ?? ? ?of the TCB and static TLS block.? */
486? ?? ?? if (__builtin_expect ((coloring & __static_tls_align_m1) != 0, 0))
487? ?? ?? ? coloring = (((coloring + __static_tls_align_m1)
488? ?? ?? ?? ? & ~(__static_tls_align_m1))
489? ?? ?? ?? ?& ~pagesize_m1);
490? ?#else
491? ?? ?? /* Unless specified we do not make any adjustments.? */
492? ?# define coloring 0
493? ?#endif
417行是設定堆棧段的權限,在某些情況下,堆棧段內可有存放一些臨時的代碼,這樣就需要有可執行權限,一般情況下,是可讀寫的權限。
設定完堆棧段的權限后,就開始處理堆棧段的大小,主要是一些堆棧大小、對齊的檢查,還有Guard頁的檢查。
在進行堆棧的映射之前,還需要通過get_cached_stack函數,檢查系統中是否存在緩沖著的堆棧,在我們的情景中,我們假定是第一次創建線程,就不存在緩沖的堆棧,這個函數留待后面介紹。
這些檢查都完成后,就需要通過mmap來映射堆棧了。這是一個匿名映射,相當于在用戶空間直接分配內存,有點像brk系統調用,用來分配大塊的內存。
后面一段是對coloring的設定,暫時認為這段代碼沒有起作用吧。
這些完成后,就開始ptherad結構的設定了:
代碼:?全選
495? ?? ?? /* Place the thread descriptor at the end of the stack.? */
496? ?#if TLS_TCB_AT_TP
497? ?? ?? pd = (struct pthread *) ((char *) mem + size - coloring) - 1;
498? ?#elif TLS_DTV_AT_TP
499? ?? ?? pd = (struct pthread *) ((((uintptr_t) mem + size - coloring
500? ?? ?? ?? ?? ?? ? - __static_tls_size)
501? ?? ?? ?? ?? ?? ? & ~__static_tls_align_m1)
502? ?? ?? ?? ?? ?? ?- TLS_PRE_TCB_SIZE);
503? ?#endif
504? ?
505? ?? ?? /* Remember the stack-related values.? */
506? ?? ?? pd->stackblock = mem;
507? ?? ?? pd->stackblock_size = size;
508? ?
509? ?? ?? /* We allocated the first block thread-specific data array.
510? ?? ?? ? ?This address will not change for the lifetime of this
511? ?? ?? ? ?descriptor.? */
512? ?? ?? pd->specific[0] = pd->specific_1stblock;
513? ?
514? ?? ?? /* This is at least the second thread.? */
515? ?? ?? pd->header.multiple_threads = 1;
516? ?#ifndef TLS_MULTIPLE_THREADS_IN_TCB
517? ?? ?? __pthread_multiple_threads = *__libc_multiple_threads_ptr = 1;
518? ?#endif
519? ?
520? ?#ifndef __ASSUME_PRIVATE_FUTEX
521? ?? ?? /* The thread must know when private futexes are supported.? */
522? ?? ?? pd->header.private_futex = THREAD_GETMEM (THREAD_SELF,
523? ?? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? header.private_futex);
524? ?#endif
525? ?
526? ?#ifdef NEED_DL_SYSINFO
527? ?? ?? /* Copy the sysinfo value from the parent.? */
528? ?? ?? THREAD_SYSINFO(pd) = THREAD_SELF_SYSINFO;
529? ?#endif
530? ?
531? ?? ?? /* The process ID is also the same as that of the caller.? */
532? ?? ?? pd->pid = THREAD_GETMEM (THREAD_SELF, pid);
533? ?
534? ?? ?? /* Allocate the DTV for this thread.? */
535? ?? ?? if (_dl_allocate_tls (TLS_TPADJ (pd)) == NULL)
536? ?? ?? ? {
537? ?? ?? ? ? /* Something went wrong.? */
538? ?? ?? ? ? assert (errno == ENOMEM);
539? ?
540? ?? ?? ? ? /* Free the stack memory we just allocated.? */
541? ?? ?? ? ? (void) munmap (mem, size);
542? ?
543? ?? ?? ? ? return EAGAIN;
544? ?? ?? ? }
545? ?
546? ?
547? ?? ?? /* Prepare to modify global data.? */
548? ?? ?? lll_lock (stack_cache_lock, LLL_PRIVATE);
549? ?
550? ?? ?? /* And add to the list of stacks in use.? */
551? ?? ?? list_add (&pd->list, &stack_used);
552? ?
553? ?? ?? lll_unlock (stack_cache_lock, LLL_PRIVATE);
554? ?
555? ?
556? ?? ?? /* There might have been a race.? Another thread might have
557? ?? ?? ? ?caused the stacks to get exec permission while this new
558? ?? ?? ? ?stack was prepared.? Detect if this was possible and
559? ?? ?? ? ?change the permission if necessary.? */
560? ?? ?? if (__builtin_expect ((GL(dl_stack_flags) & PF_X) != 0
561? ?? ?? ?? ?? ?&& (prot & PROT_EXEC) == 0, 0))
562? ?? ?? ? {
563? ?? ?? ? ? int err = change_stack_perm (pd
564? ?#ifdef NEED_SEPARATE_REGISTER_STACK
565? ?? ?? ?? ?? ?? ?? ?, ~pagesize_m1
566? ?#endif
567? ?? ?? ?? ?? ?? ?? ?);
568? ?? ?? ? ? if (err != 0)
569? ?? ?? ?{
570? ?? ?? ?? /* Free the stack memory we just allocated.? */
571? ?? ?? ?? (void) munmap (mem, size);
572? ?
573? ?? ?? ?? return err;
574? ?? ?? ?}
575? ?? ?? ? }
576? ?
577? ?
578? ?? ?? /* Note that all of the stack and the thread descriptor is
579? ?? ?? ? ?zeroed.? This means we do not have to initialize fields
580? ?? ?? ? ?with initial value zero.? This is specifically true for
581? ?? ?? ? ?the 'tid' field which is always set back to zero once the
582? ?? ?? ? ?stack is not used anymore and for the 'guardsize' field
583? ?? ?? ? ?which will be read next.? */
584? ?? ?}
前面說到過,在我所觀察的系統中,TLS_TCB_AT_TP總是被定義,這意味著pthread位于剛才申請的堆棧的頂端,見497行,這里的-1,是減去一個pthread結構的大小。
505~532行,開始設置新線程的pthread結構,堆棧信息設置為剛剛申請的堆棧,并將pthread結構設置成為多線程狀態,futex、sysinfo、pid等則從父線程繼承。
接下來是調用_dl_allocate_tls來設置TLS,這是一個相當重要的過程,如果不能設置TLS,程序很有可能無法運行,目前兼容內核中多線程問題很多時候與此相關。
先看宏TLS_TPADJ,它就定義在nptl/allocatestack.c中
#define TLS_TPADJ (pd) (pd)
也就是pthread結構本身。再看_dl_allocate_tls(),位于elf/dl-tls.c中:
代碼:?全選
460 internal_function
461 _dl_allocate_tls (void *mem)
462 {
463? ?return _dl_allocate_tls_init (mem == NULL
464? ? ? ? ? ? ? ? ?? _dl_allocate_tls_storage ()
465? ? ? ? ? ? ? ? ?: allocate_dtv (mem));
466 }
在我們這個情景中,傳進去的mem值為pthread結構地址,不為NULL,因此調用到了allocate_dtv函數,也是位于elf/dl-tls.c中:
289 static void *
290 internal_function
291 allocate_dtv (void *result)
292 {
293? ?dtv_t *dtv;
294? ?size_t dtv_length;
295?
296? ?/* We allocate a few more elements in the dtv than are needed for the
297? ? ? initial set of modules.? This should avoid in most cases expansions
298? ? ? of the dtv.? */
299? ?dtv_length = GL(dl_tls_max_dtv_idx) + DTV_SURPLUS;
300? ?dtv = calloc (dtv_length + 2, sizeof (dtv_t));
301? ?if (dtv != NULL)
302? ? ?{
303? ? ? ?/* This is the initial length of the dtv.? */
304? ? ? ?dtv[0].counter = dtv_length;
305?
306? ? ? ?/* The rest of the dtv (including the generation counter) is
307? ? ? Initialize with zero to indicate nothing there.? */
308?
309? ? ? ?/* Add the dtv to the thread data structures.? */
310? ? ? ?INSTALL_DTV (result, dtv);
311? ? ?}
312? ?else
313? ? ?result = NULL;
314?
315? ?return result;
316 }
這個函數比較簡單,就是申請一個dtv的數組,然后裝載到pthread結構中,有趣的是INSTALL_DTV這個宏,定義在nptl/sysdep/i386/tls.h中:
代碼:?全選
? ((tcbhead_t *) (descr))->dtv = (dtvp) + 1
Allocate_dtv完成后,需要調用_dl_allocate_tls_init對TLS進行初始化:
代碼:?全選
378 internal_function
379 _dl_allocate_tls_init (void *result)
380 {
381? ?if (result == NULL)
382? ? ?/* The memory allocation failed.? */
383? ? ?return NULL;
384?
385? ?dtv_t *dtv = GET_DTV (result);
386? ?struct dtv_slotinfo_list *listp;
387? ?size_t total = 0;
388? ?size_t maxgen = 0;
389?
390? ?/* We have to prepare the dtv for all currently loaded modules using
391? ? ? TLS.? For those which are dynamically loaded we add the values
392? ? ? indicating deferred allocation.? */
393? ?listp = GL(dl_tls_dtv_slotinfo_list);
394? ?while (1)
395? ? ?{
396? ? ? ?size_t cnt;
397?
398? ? ? ?for (cnt = total == 0 ? 1 : 0; cnt < listp->len; ++cnt)
399? ? ?{
400? ? ? ?struct link_map *map;
401? ? ? ?void *dest;
402?
403? ? ? ?/* Check for the total number of used slots.? */
404? ? ? ?if (total + cnt > GL(dl_tls_max_dtv_idx))
405? ? ? ? ?break;
406?
407? ? ? ?map = listp->slotinfo[cnt].map;
408? ? ? ?if (map == NULL)
409? ? ? ? ?/* Unused entry.? */
410? ? ? ? ?continue;
411?
412? ? ? ?/* Keep track of the maximum generation number.? This might
413? ? ? ? ? not be the generation counter.? */
414? ? ? ?maxgen = MAX (maxgen, listp->slotinfo[cnt].gen);
415?
416? ? ? ?if (map->l_tls_offset == NO_TLS_OFFSET)
417? ? ? ? ?{
418? ? ? ? ? ?/* For dynamically loaded modules we simply store
419? ? ? ? ? the value indicating deferred allocation.? */
420? ? ? ? ? ?dtv[map->l_tls_modid].pointer.val = TLS_DTV_UNALLOCATED;
421? ? ? ? ? ?dtv[map->l_tls_modid].pointer.is_static = false;
422? ? ? ? ? ?continue;
423? ? ? ? ?}
424?
425? ? ? ?assert (map->l_tls_modid == cnt);
426? ? ? ?assert (map->l_tls_blocksize >= map->l_tls_initimage_size);
427 #if TLS_TCB_AT_TP
428? ? ? ?assert ((size_t) map->l_tls_offset >= map->l_tls_blocksize);
429? ? ? ?dest = (char *) result - map->l_tls_offset;
430 #elif TLS_DTV_AT_TP
431? ? ? ?dest = (char *) result + map->l_tls_offset;
432 #else
433 # error "Either TLS_TCB_AT_TP or TLS_DTV_AT_TP must be defined"
434 #endif
435?
436? ? ? ?/* Copy the initialization image and clear the BSS part.? */
437? ? ? ?dtv[map->l_tls_modid].pointer.val = dest;
438? ? ? ?dtv[map->l_tls_modid].pointer.is_static = true;
439? ? ? ?memset (__mempcpy (dest, map->l_tls_initimage,
440? ? ? ? ? ? ? ? ? map->l_tls_initimage_size), '\0',
441? ? ? ? ? ?map->l_tls_blocksize - map->l_tls_initimage_size);
442? ? ?}
443?
444? ? ? ?total += cnt;
445? ? ? ?if (total >= GL(dl_tls_max_dtv_idx))
446? ? ?break;
447?
448? ? ? ?listp = listp->next;
449? ? ? ?assert (listp != NULL);
450? ? ?}
451?
452? ?/* The DTV version is up-to-date now.? */
453? ?dtv[0].counter = maxgen;
454?
455? ?return result;
456 }
這里是一大堆和連接有關的代碼,這里就不做解釋了,如果以后有時間,或許可以多看看連接相關的代碼,梳理一下,連接過程到底是如何完成的。
回到allocate_stack函數中,551行是將此結構連接到stack_used隊列中。當進程退出時,將調用到_deallocate_stack,此時,此結構將從stack_used隊列脫出,加入到stack_cached中,等待下一個pthread_create調用。
接下來一段和可執行堆棧相關,不是我們所關心的,忽略。
代碼:?全選
586? ?? ? ? /* Create or resize the guard area if necessary.? */
587? ?? ? ? if (__builtin_expect (guardsize > pd->guardsize, 0))
588? ?? ?{
589? ?#ifdef NEED_SEPARATE_REGISTER_STACK
590? ?? ?? char *guard = mem + (((size - guardsize) / 2) & ~pagesize_m1);
591? ?#elif _STACK_GROWS_DOWN
592? ?? ?? char *guard = mem;
593? ?# elif _STACK_GROWS_UP
594? ?? ?? char *guard = (char *) (((uintptr_t) pd - guardsize) & ~pagesize_m1);
595? ?#endif
596? ?? ?? if (mprotect (guard, guardsize, PROT_NONE) != 0)
597? ?? ?? ? {
598? ?? ?? ? ? int err;
599? ?? ?? ? mprot_error:
600? ?? ?? ? ? err = errno;
601? ?
602? ?? ?? ? ? lll_lock (stack_cache_lock, LLL_PRIVATE);
603? ?
604? ?? ?? ? ? /* Remove the thread from the list.? */
605? ?? ?? ? ? list_del (&pd->list);
606? ?
607? ?? ?? ? ? lll_unlock (stack_cache_lock, LLL_PRIVATE);
608? ?
609? ?? ?? ? ? /* Get rid of the TLS block we allocated.? */
610? ?? ?? ? ? _dl_deallocate_tls (TLS_TPADJ (pd), false);
611? ?
612? ?? ?? ? ? /* Free the stack memory regardless of whether the size
613? ?? ?? ? of the cache is over the limit or not.? If this piece
614? ?? ?? ? of memory caused problems we better do not use it
615? ?? ?? ? anymore.? Uh, and we ignore possible errors.? There
616? ?? ?? ? is nothing we could do.? */
617? ?? ?? ? ? (void) munmap (mem, size);
618? ?
619? ?? ?? ? ? return err;
620? ?? ?? ? }
621? ?
622? ?? ?? pd->guardsize = guardsize;
623? ?? ?}
624? ?? ? ? else if (__builtin_expect (pd->guardsize - guardsize > size - reqsize,
625? ?? ?? ?? ?? ? 0))
626? ?? ?{
627? ?? ?? /* The old guard area is too large.? */
628? ?
629? ?#ifdef NEED_SEPARATE_REGISTER_STACK
630? ?? ?? char *guard = mem + (((size - guardsize) / 2) & ~pagesize_m1);
631? ?? ?? char *oldguard = mem + (((size - pd->guardsize) / 2) & ~pagesize_m1);
632? ?
633? ?? ?? if (oldguard < guard
634? ?? ?? ? ? && mprotect (oldguard, guard - oldguard, prot) != 0)
635? ?? ?? ? goto mprot_error;
636? ?
637? ?? ?? if (mprotect (guard + guardsize,
638? ?? ?? ?? ?oldguard + pd->guardsize - guard - guardsize,
639? ?? ?? ?? ?prot) != 0)
640? ?? ?? ? goto mprot_error;
641? ?#elif _STACK_GROWS_DOWN
642? ?? ?? if (mprotect ((char *) mem + guardsize, pd->guardsize - guardsize,
643? ?? ?? ?? ?prot) != 0)
644? ?? ?? ? goto mprot_error;
645? ?#elif _STACK_GROWS_UP
646? ?? ?? if (mprotect ((char *) pd - pd->guardsize,
647? ?? ?? ?? ?pd->guardsize - guardsize, prot) != 0)
648? ?? ?? ? goto mprot_error;
649? ?#endif
650? ?
651? ?? ?? pd->guardsize = guardsize;
652? ?? ?}
上面這么一大段,是為了設置Guard頁,總體說來就是把剛才申請到的內存最低幾頁,設置成為PROT_NONE,使著一頁無法訪問。
再下面就是鎖、mutex等一些同步用的字段設置。這樣在新線程創建出來之前,pthread結構的設置工作就基本完成了。
總結
以上是生活随笔為你收集整理的Linux用户空间线程管理介绍之二:创建线程堆栈的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: Cmder--windows 系统命令行
- 下一篇: Seo:入门须知(三)网页快照投诉
