项目的性能测试告一段落,暂时松了一口气。但是也发现很多知识的盲点,也许这就是所谓的知道的越多,不知道的也越多。 
现在所有的程序基本上都是用多线程来实现的,尤其是Unix/Linux server程序。我原本以为线程是直接在内核实现的,或者大部分代码应该在内核中。但是当我找pthread_create或者pthread字眼时,发现Linux内核中的代码根本搜索不到,于是用g++ -M命令找到了pthread的头文件,内秀的pthread才肯出来露面,原来是在glibc中。 
glibc是GNU发布的libc库,提供了Linux系统中底层的API。在这里,把我阅读的glibc代码分享出来,更改一些注释,规划下格式,希望能够让大家看的时候更省心一些(实在不敢恭维glibc的格式,这是我看过的最丑的开源代码)。 
POSIX中使用pthread_create创建线程,glibc中有一个nptl(Native POSIX Thread Library)版本的线程实现。主要阅读其中的pthread_create, pthread_join和pthread_exit这几个常用函数,了解线程是如何创建和退出的。理解了这几个再看其余的,加深对线程的理解,了解更多的线程特性。

数据结构

uintptr_t

这是代码中经常见到的一个变量类型,所以单独提前写出来 
sysdeps/generic/stdint.h

typedef unsigned long int       uintptr_t
  • 1

线程属性 struct pthread_attr

sysdeps/nptl/internaltypes.h

struct pthread_attr
{// 调度参数和优先级 NOTE: sched_param中定义的也就一个优先级的成员struct sched_param schedparam;int schedpolicy;int flags;              // 状态标识,比如detachstate, scope等size_t guardsize;       // 保护区大小void *stackaddr;        // stack内存地址size_t stacksize;cpu_set_t *cpuset;      // 关系映射 Affinity mapsize_t cpusetsize;
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

NOTE: struct sched_param

// 调度算法
#define SCHED_OTHER 0
#define SCHED_FIFO  1
#define SCHED_RR    2struct sched_param
{int __sched_priority;
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

cpu_set_t

// 访问cpu_set_t的函数
# define __CPUELT(cpu)  ((cpu) / __NCPUBITS)
# define __CPUMASK(cpu) ((__cpu_mask) 1 << ((cpu) % __NCPUBITS))typedef struct
{__cpu_mask __bits[__CPU_SETSIZE / __NCPUBITS];
} cpu_set_t;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

资源清理 struct _pthread_cleanup_buffer

// sysdeps/nptl/pthread.h
struct _pthread_cleanup_buffer
{void (*__routine) (void *);                      void *__arg;int __canceltype;struct _pthread_cleanup_buffer *__prev;
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

pthread_unwind_buf

// 存储取消处理器信息缓存的内部实现
struct pthread_unwind_buf
{struct{__jmp_buf jmp_buf;int mask_was_saved;} cancel_jmp_buf[1];union{void *pad[4];   // 公用版本实现的占位符struct{// 指向前一个cleanup bufferstruct pthread_unwind_buf *prev;// 向后兼容:前一个新风格清理处理器安装的时间老风格的清理处理器状态struct _pthread_cleanup_buffer *cleanup;int canceltype; // push 调用前取消(cancellation)的类型} data;} priv;
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

异常处理_Unwind_Exception

这个结构体是用来处理异常的,不了解这个结构体,也不怎么影响阅读整个代码,此处贴出来只是用来浏览。

#define _Unwind_Exception _Unwind_Control_Block
  • 1
struct _Unwind_Control_Block
{
#ifdef _LIBC/* For the benefit of code which assumes this is a scalar.  Allglibc ever does is clear it.  */_uw64 exception_class;
#elsechar exception_class[8];
#endifvoid (*exception_cleanup)(_Unwind_Reason_Code, _Unwind_Control_Block *);/* Unwinder cache, private fields for the unwinder's use */struct{_uw reserved1;  /* Forced unwind stop fn, 0 if not forced */_uw reserved2;  /* Personality routine address */_uw reserved3;  /* Saved callsite address */_uw reserved4;  /* Forced unwind stop arg */_uw reserved5;} unwinder_cache;/* Propagation barrier cache (valid after phase 1): */struct{_uw sp;_uw bitpattern[5];}barrier_cache;/* Cleanup cache (preserved over cleanup): */struct{_uw bitpattern[4];}cleanup_cache;/* Pr cache (for pr's benefit): */struct{_uw fnstart;            /* function start address */_Unwind_EHT_Header *ehtp;   /* pointer to EHT entry header word */_uw additional;     /* additional data */_uw reserved1;}pr_cache;long long int :0;   /* Force alignment to 8-byte boundary */
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41

线程 struct pthread

nptl/descr.h

这里有很多线程中陌生的概念,或者平时工作时也极少用到的,先做入门,不考虑太复杂的场景。 
众所周知,每个进程都有自己的用户空间,但是线程是没有的,所以应该是所有线程公用同一个进程空间。

一个线程要执行代码,必须要有自己的数据区域用来存放执行的函数中的临时变量,这个就是栈。每个线程的栈空间应该是隔离的,但是为了防止线程代码越界访问,超出了栈空间,比如递归程序,可以设置一块内存作为保护区域(将这块内存的权限设置为不可读写或执行),因此就有了保护区(guard)。 
线程还有一个特性,就是可以存放每个线程特有的数据,就是TLS。存放TLS是一个数组指针,也是线程数据的一部分,这样才能做到快速访问当前线程的特有数据。

一个线程关联的主要数据和线程数据结构的描述: 
- 存放线程信息本身数据的内存 
- 线程环境栈

void *stackblock;
size_t stackblock_size;
  • 1
  • 2
  • 栈保护区
size_t guardsize;
  • 1
  • 线程的TLS
struct pthread_key_data specific_1stblock[PTHREAD_KEY_2NDLEVEL_SIZE];
struct pthread_key_data *specific[PTHREAD_KEY_1STLEVEL_SIZE];
  • 1
  • 2

下面是glibc中对线程描述的结构体struct pthread.

struct pthread
{union{
#if !TLS_DTV_AT_TPtcbhead_t header;       /* TLS 使用的TCB,不包含线程 */struct{/* 当进程产生了至少一个线程或一个单线程的进程取消本身时启用multiple_threads。这样就允许在做一些compare_and_exchange操作之前添加一些额外的代码来引入锁,也可以开启取消点(cancellation point)。多个线程的概念和取消点的概念是分开的,因为没有必要为取消点设计成多线程,就跟单线程进程取消本身一样。因为开启多线程就允许在取消点和compare_and_exchange操作中添加一些额外的代码,这样的话对于一个单线程、自取消(self-canceling)的进程可能会有不必要的性能影响。但是这样也没问题,因为仅当它要取消自己并且要结束的时候,一个单线程的进程会开启异步取消*/int multiple_threads;int gscope_flag;
# ifndef __ASSUME_PRIVATE_FUTEXint private_futex;
# endif} header;
#endifvoid *__padding[24];};list_t list;    // `stack_used' 或 `__stack_user' 链表节点pid_t tid;      // 线程ID,也是线程描述符pid_t pid;      // 进程ID,线程组ID// 进程当前持有的robust mutex
#ifdef __PTHREAD_MUTEX_HAVE_PREVvoid *robust_prev;struct robust_list_head robust_head;/* The list above is strange.   It is basically a double linked listbut the pointer to the next/previous element of the list pointsin the middle of the object, the __next element.   Whenevercasting to __pthread_list_t we need to adjust the pointerfirst. */#elseunion{__pthread_slist_t robust_list;struct robust_list_head robust_head;};#endifstruct _pthread_cleanup_buffer *cleanup;    // cleanup缓存链表struct pthread_unwind_buf *cleanup_jmp_buf; // unwind信息int cancelhandling;                         // 判断处理取消的标识int flags;                                  // 标识. 包含从线程属性中复制的信息// 这里分配一个块. 对大多数应用程序应该能够尽量避免动态分配内存struct pthread_key_data{uintptr_t seq;                          // 序列号void *data;                             // 数据指针} specific_1stblock[PTHREAD_KEY_2NDLEVEL_SIZE];// 存放线程特有数据的二维数组// 第1个元素就是specific_1stblockstruct pthread_key_data *specific[PTHREAD_KEY_1STLEVEL_SIZE];bool specific_used;                         // 标识符:是否使用特定(specific)数据(TLS)bool report_events;                         // 是否要汇报事件bool user_stack;                            // 是否用户提供栈bool stopped_start;                         // 启动的时候线程是否应该是停止状态// pthread_create执行的时候,parent的取消处理器。当需要取消的时候才用到int parent_cancelhandling;int lock;                                   // 同步访问的锁int setxid_futex;                           // setxid调用的同步锁#if HP_TIMING_AVAIL/* Offset of the CPU clock at start thread start time.  */hp_timing_t cpuclock_offset;
#endif// 如果线程等待关联另一个线程ID, 就将那个线程的ID放在这里// 如果一个线程状态是detached,这里就存放它自己. struct pthread *joinid;                     // 如果joinid是线程本身,就说明这个线程是detached状态void *result;                               // 线程函数执行的结果// 新线程的调度参数struct sched_param schedparam;              // 只有一个成员: int __sched_priorityint schedpolicy;// 执行函数的地址和参数void *(*start_routine) (void *);void *arg;td_eventbuf_t eventbuf;                     // 调试状态struct pthread *nextevent;                  // 下一个有pending事件的描述符,应该是用来调试的#ifdef HAVE_FORCED_UNWINDstruct _Unwind_Exception exc;               // 与平台相关的unwind信息
#endif// 如果是非0,指栈上分配的区域和大小void *stackblock;size_t stackblock_size;size_t guardsize;                           // 保护区域的大小size_t reported_guardsize;                  // 用户指定的并且展示的保护区大小(就是通过接口获取保护区大小时,返回这个数字)struct priority_protection_data *tpp;       // 线程有限保护数据/* Resolver state.  */struct __res_state res;char end_padding[];} __attribute ((aligned (TCB_ALIGNMENT)));
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119

线程创建和销毁的主要函数

这里面有一些宏定义或者变量的值,可能不止一个,但是为了简单容易理解,只是贴出来一个来看。

pthread_create

线程中最重要的函数pthread_create,功能就是创建线程。它的任务可以分为以下几步: 
1. 初始化线程属性 
如果用户提供了线程属性,就使用用户的,否则复制默认线程属性。 
glibc使用全局变量__default_pthread_attr保存线程默认属性。 
2. 为线程分配栈空间: ALLOCATE_STACK 
3. 启动线程,执行线程函数:create_thread 
一般创建的线程不是立即启动的,而是最后调用create_thread才启动。Posix没有提供一个创建线程,然后在主动启动的接口,所以启动的接口直接在创建的时候调用了。

这里面当然还有一些其它的任务,比如线程调度参数,event report等,但是代码简单,而且与平时接触到的接口关系不大,因此暂时略过不提.

int
__pthread_create_2_1 (newthread, attr, start_routine, arg)pthread_t *newthread;const pthread_attr_t *attr;void *(*start_routine) (void *);void *arg;
{
# define STACK_VARIABLES void *stackaddr = NULL; size_t stacksize = 0STACK_VARIABLES;const struct pthread_attr *iattr = (struct pthread_attr *) attr;struct pthread_attr default_attr;bool free_cpuset = false;/// 1 初始化线程属性if (iattr == NULL)  // 如果传入的属性参数是空,使用默认{// 复制默认线程属性lll_lock (__default_pthread_attr_lock, LLL_PRIVATE);default_attr = __default_pthread_attr;size_t cpusetsize = default_attr.cpusetsize;if (cpusetsize > 0){cpu_set_t *cpuset;if (__glibc_likely (__libc_use_alloca (cpusetsize)))cpuset = __alloca (cpusetsize);else{cpuset = malloc (cpusetsize);if (cpuset == NULL){lll_unlock (__default_pthread_attr_lock, LLL_PRIVATE);return ENOMEM;}free_cpuset = true;}memcpy (cpuset, default_attr.cpuset, cpusetsize);default_attr.cpuset = cpuset;}lll_unlock (__default_pthread_attr_lock, LLL_PRIVATE);iattr = &default_attr;}/// 2. 创建线程栈空间struct pthread *pd = NULL;int err = ALLOCATE_STACK (iattr, &pd);  // 分配一个堆栈int retval = 0;if (__glibc_unlikely (err != 0)){/* 出错了.可能是属性的参数不正确或者不能分配内存.需要转换错误码*/retval = err == ENOMEM ? EAGAIN : err;goto out;}/* 初始化TCB. 都在'get_cached_stack'中被初始化为0. 如果使用'mmap'新分配* 的stack, 这种方法可以避免多次初始化     */#if TLS_TCB_AT_TP/* Reference to the TCB itself. */pd->header.self = pd;/* Self-reference for TLS.  */pd->header.tcb = pd;
#endif// 线程执行的回调函数和参数pd->start_routine = start_routine;pd->arg = arg;// 复制线程属性标识struct pthread *self = THREAD_SELF;pd->flags = ((iattr->flags & ~(ATTR_FLAG_SCHED_SET | ATTR_FLAG_POLICY_SET))| (self->flags & (ATTR_FLAG_SCHED_SET | ATTR_FLAG_POLICY_SET)));// 如果是detached状态,joined就设置为自己本身pd->joinid = iattr->flags & ATTR_FLAG_DETACHSTATE ? pd : NULL;// debug event从父线程继承pd->eventbuf = self->eventbuf;pd->schedpolicy = self->schedpolicy;pd->schedparam = self->schedparam;// 复制栈保护区
#ifdef THREAD_COPY_STACK_GUARDTHREAD_COPY_STACK_GUARD (pd);
#endif// 复制指针保护值
#ifdef THREAD_COPY_POINTER_GUARDTHREAD_COPY_POINTER_GUARD (pd);
#endif// 计算线程的调度参数// NOTE: # define __builtin_expect(expr, val)    (expr)//   如果val是0,它的功能类似于unlikely; 如果val 是1, 功能就是likelyif (__builtin_expect ((iattr->flags & ATTR_FLAG_NOTINHERITSCHED) != 0, 0)&& (iattr->flags & (ATTR_FLAG_SCHED_SET | ATTR_FLAG_POLICY_SET)) != 0){INTERNAL_SYSCALL_DECL (scerr);// 使用用户提供的调度参数if (iattr->flags & ATTR_FLAG_POLICY_SET)pd->schedpolicy = iattr->schedpolicy;else if ((pd->flags & ATTR_FLAG_POLICY_SET) == 0){pd->schedpolicy = INTERNAL_SYSCALL (sched_getscheduler, scerr, 1, 0);pd->flags |= ATTR_FLAG_POLICY_SET;}if (iattr->flags & ATTR_FLAG_SCHED_SET)memcpy (&pd->schedparam, &iattr->schedparam,sizeof (struct sched_param));else if ((pd->flags & ATTR_FLAG_SCHED_SET) == 0){INTERNAL_SYSCALL (sched_getparam, scerr, 2, 0, &pd->schedparam);pd->flags |= ATTR_FLAG_SCHED_SET;}// 检查优先级是否正确int minprio = INTERNAL_SYSCALL (sched_get_priority_min, scerr, 1,iattr->schedpolicy);int maxprio = INTERNAL_SYSCALL (sched_get_priority_max, scerr, 1,iattr->schedpolicy);if (pd->schedparam.sched_priority < minprio|| pd->schedparam.sched_priority > maxprio){// 可能是线程想要改变ID并且等待刚创建就失败的线程if (__builtin_expect (atomic_exchange_acq (&pd->setxid_futex, 0)== -2, 0))lll_futex_wake (&pd->setxid_futex, 1, LLL_PRIVATE);__deallocate_stack (pd);    // 释放申请的栈retval = EINVAL;goto out;}}// 将描述符返回调用者*newthread = (pthread_t) pd;LIBC_PROBE (pthread_create, 4, newthread, attr, start_routine, arg);/// 3. 启动线程retval = create_thread (pd, iattr, STACK_VARIABLES_ARGS);out:if (__glibc_unlikely (free_cpuset))free (default_attr.cpuset);return retval;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155

创建线程栈

线程栈可以由用户提供,也可以让glibc自己分配内存。 
如果是用户提供了内存,由用户自己销毁,glibc会校验栈空间大小是否合适,但是不会为它设置保护区。 
如果是glibc自己分配栈空间,优先从栈缓存中找出一个合适大小的栈(以前创建了线程又销毁了,这个内存会存起来),如果找不到就调用mmap分配一个。glibc自己分配的栈空间会设置保护区。如果是用的缓存中的栈,还会校验以前的保护区的位置和大小是否满足当前的要求,必要时重新设置。 
一个栈,必须要能够容纳线程数据本身(sizeof(struct pthread)),保护区,TLS空间和一个最小的栈预留空间(大部分都是2048). 
这里计算空间大小时,大部分都要按照页对齐。 
struct pthread存放在栈空间的最后,不管栈是向上增长还是向下增长; 
保护区是根据栈增长方向不同存放的位置也不同,都是放在增长方向的尾端。如果是向上增长,保护区就紧贴着struct pthread的内存;如果是向下增长,那就是栈的起始位置。不过还有一种奇葩的保护区存放方式,就是在栈中间劈开(真是毁三观),这种保护区存放的位置自然不用说,就是在栈中间。

创建线程栈的宏定义:ALLOCATE_STACK(源代码中有两个,这里为了简单就分析一个)

# define ALLOCATE_STACK(attr, pd) \allocate_stack (attr, pd, &stackaddr, &stacksize)
  • 1
  • 2

allocate_stack的实现如下


// nptl/allocatestack.c// 通过分配一个新的栈或者重用缓冲区中的栈来创建并返回一个可用的线程
// 参数attr不能是空指针,pdp也不能是空指针
static int
allocate_stack (const struct pthread_attr *attr, struct pthread ##pdp,ALLOCATE_STACK_PARMS)
{struct pthread *pd;size_t size;size_t pagesize_m1 = __getpagesize () - 1; // 暂时假定页大小是4096,其实大部分都是这样的void *stacktop;assert (powerof2 (pagesize_m1 + 1));assert (TCB_ALIGNMENT >= STACK_ALIGN);// 如果用户指定了栈大小,就用用户指定的,否则使用默认的if (attr->stacksize != 0)size = attr->stacksize;else{lll_lock (__default_pthread_attr_lock, LLL_PRIVATE);size = __default_pthread_attr.stacksize;lll_unlock (__default_pthread_attr_lock, LLL_PRIVATE);}// 获取栈的内存if (__glibc_unlikely (attr->flags & ATTR_FLAG_STACKADDR)){// 如果用户给定了栈内存,直接用用户指定的// 可以用pthread_attr_setstack设置栈内存uintptr_t adj;      // uintptr_t: unsigned long int// 验证用户指定的栈内存大小是否充足if (attr->stacksize != 0&& attr->stacksize < (__static_tls_size + MINIMAL_REST_STACK))return EINVAL;// NOTE:__static_tls_size在代码中设置的值是2048(_dl_tls_static_size)// # define MINIMAL_REST_STACK  2048    正好也是一页大小// 调整栈大小,按照TLS块对齐// 看下面的代码,attr->stackaddr应该是在分配的一块内存的最顶端
#if TLS_TCB_AT_TPadj = ((uintptr_t) attr->stackaddr - TLS_TCB_SIZE)  // TLS_TCB_SIZE:sizeof (struct pthread) (在x86_64系统上)& __static_tls_align_m1;    // __static_tls_align_m1 = STACK_ALIGN = __alignof__ (long double)assert (size > adj + TLS_TCB_SIZE);
#elif TLS_DTV_AT_TPadj = ((uintptr_t) attr->stackaddr - __static_tls_size)& __static_tls_align_m1;assert (size > adj);
#endif// 用户提供的内存. 如果是用户自己提供的栈,不会分配保护页.
#if TLS_TCB_AT_TPpd = (struct pthread *) ((uintptr_t) attr->stackaddr- TLS_TCB_SIZE - adj);
#elif TLS_DTV_AT_TPpd = (struct pthread *) (((uintptr_t) attr->stackaddr- __static_tls_size - adj)- TLS_PRE_TCB_SIZE);
#endif// 清零memset (pd, '\0', sizeof (struct pthread));// 第一个TSD块包含在TCB中pd->specific[0] = pd->specific_1stblock;// 记录栈相关的值pd->stackblock = (char *) attr->stackaddr - size;   // size就是栈大小pd->stackblock_size = size;// 用户提供的栈. 不会放在栈缓存中或者释放它(除了TLS内存)pd->user_stack = true;// 最少是第二个线程(肯定是其它线程创建出来的线程)pd->header.multiple_threads = 1;
#ifndef TLS_MULTIPLE_THREADS_IN_TCB__pthread_multiple_threads = *__libc_multiple_threads_ptr = 1;
#endif#ifndef __ASSUME_PRIVATE_FUTEX// 线程知道什么时候支持私有的futex(简单的理解为轻量级的mutex)pd->header.private_futex = THREAD_GETMEM (THREAD_SELF,header.private_futex);
#endif#ifdef NEED_DL_SYSINFO// 从父线程中复制系统信息THREAD_SYSINFO(pd) = THREAD_SELF_SYSINFO;
#endif// 进程ID与调用者的进程ID相同(肯定是同一个进程)pd->pid = THREAD_GETMEM (THREAD_SELF, pid);// 完全结束克隆后(cloned)才允许setxid(xid是什么)pd->setxid_futex = -1;// 为线程分配DTV(Dynamic Thread Vector)if (_dl_allocate_tls (TLS_TPADJ (pd)) == NULL){assert (errno == ENOMEM);return errno;}// 准备修改全局变量lll_lock (stack_cache_lock, LLL_PRIVATE);// 将栈添加到当前正在使用的栈链表中(感觉名字应该叫__stack_use)list_add (&pd->list, &__stack_user);lll_unlock (stack_cache_lock, LLL_PRIVATE);}else    // 不是用户提供的栈, 需要glib分配内存{// 分配匿名内存. 可能使用缓存size_t guardsize;size_t reqsize;void *mem;const int prot = (PROT_READ | PROT_WRITE| ((GL(dl_stack_flags) & PF_X) ? PROT_EXEC : 0));#if COLORING_INCREMENT != 0// 添加一页或多页用来做栈着色(着色到底是什么,做什么用的?)// 16整数倍的或更大的页不需要再这样做. 否则可能不必要的未对齐(unnecessary misalignment)if (size <= 16 * pagesize_m1)size += pagesize_m1 + 1;
#endif// 对齐栈大小size &= ~__static_tls_align_m1;assert (size != 0);// 确保栈大小足够存放保护区, 还有线程描述符,就是trhead// pagesize_m1 是pagesize - 1, 这个语句会将guardsize按照pagesize对齐guardsize = (attr->guardsize + pagesize_m1) & ~pagesize_m1; if (__builtin_expect (size < ((guardsize + __static_tls_size+ MINIMAL_REST_STACK + pagesize_m1)& ~pagesize_m1),0))// 这个if语句是判断size(即当前的栈大小)是否满足需求(保护区大小 + TLS大小 + 最小的栈尺寸)return EINVAL;// 首先尝试从缓存中获取栈内存reqsize = size;     // reqsize记录了最原始计算出来的期望的栈空间大小,size在get_cached_stack返回后可能会改变pd = get_cached_stack (&size, &mem);if (pd == NULL){// 避免造成比调整后的分配栈大小更大范围的影响.// 这种方法直接分配不会造成混淆的问题
#if MULTI_PAGE_ALIASING != 0if ((size % MULTI_PAGE_ALIASING) == 0) // MULTI_PAGE_ALIASING默认定义是0size += pagesize_m1 + 1;    // 很奇怪,这里不是做对齐处理,而是非要多出来一点或一页
#endif// 使用内存映射创建需要的栈空间mem = mmap (NULL, size, prot,MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0);if (__glibc_unlikely (mem == MAP_FAILED))return errno;assert (mem != NULL);// 这段代码做着色计算。 着色我理解为按照一定算法计算出一个随机数// 线程栈空间中存放的struct pthread按照这个着色参数进行一定偏移,// 提高一定的安全度,类似于TCP的SYN初始序列号的功能(以上都是我的猜想)
#if COLORING_INCREMENT != 0 // COLORING_INCREMENT 默认定义是0// 自增长NCREATED  (猜测是每创建一个新线程,这个值就增长固定的值)// atomic_increment_val每次加1// nptl_ncreated是在nptl/allocatestack.c中定义的一个静态变量unsigned int ncreated = atomic_increment_val (&nptl_ncreated);// 选择一个使用每个新线程固定自增长着色的偏移量(就是上一行自增长的NCREATED).// 这个偏移量对pagesize取模. // 即使着色会相对更高的对齐值更好, 但是这样做没有意义,// mmap()接口并不允许我们指定任何返回对齐的内存块size_t coloring = (ncreated * COLORING_INCREMENT) & pagesize_m1;// 确保着色偏移量不会干扰TCB和静态TLS区的对齐, 就是在对coloring做对齐计算// 这里为啥用unlikely? 我认为大部分情况应该都不是按照tls_align对齐的, 除非// COLORING_INCREMENT 按照tls_align的倍数配置if (__glibc_unlikely ((coloring & __static_tls_align_m1) != 0))coloring = (((coloring + __static_tls_align_m1)& ~(__static_tls_align_m1))   // 这两行对__static_tls_align对齐& ~pagesize_m1);               // 功能是 round_low(pagesize)
#else// 没有特别指定,不做任何调整操作
# define coloring 0
#endif// 线程的数据放到栈的尾部, 会把着色的那部分内存在顶端空出来
#if TLS_TCB_AT_TPpd = (struct pthread *) ((char *) mem + size - coloring) - 1;
#elif TLS_DTV_AT_TP // 把着色和TLS的空间都在顶端空出来pd = (struct pthread *) ((((uintptr_t) mem + size - coloring- __static_tls_size)& ~__static_tls_align_m1)- TLS_PRE_TCB_SIZE);    // TLS_PRE_TCB_SIZE: sizeof (struct pthread), 不同系统可能不同
#endif// 设置栈相关的值pd->stackblock = mem;pd->stackblock_size = size;// 分配第一个线程相关数据数组块// 这个地址在进程描述符的整个生命空间都不会改变pd->specific[0] = pd->specific_1stblock;// 最少是第二个线程pd->header.multiple_threads = 1;
#ifndef TLS_MULTIPLE_THREADS_IN_TCB__pthread_multiple_threads = *__libc_multiple_threads_ptr = 1;
#endif#ifndef __ASSUME_PRIVATE_FUTEX// 线程知道什么时候支持私有的futex(简单的理解为轻量级的mutex)pd->header.private_futex = THREAD_GETMEM (THREAD_SELF,header.private_futex);
#endif#ifdef NEED_DL_SYSINFO// 从父线程中复制系统信息THREAD_SYSINFO(pd) = THREAD_SELF_SYSINFO;
#endif// 完全结束克隆后(cloned)才允许setxid(xid是什么)pd->setxid_futex = -1;// 进程号跟调用者相同pd->pid = THREAD_GETMEM (THREAD_SELF, pid);// 为线程分配DTVif (_dl_allocate_tls (TLS_TPADJ (pd)) == NULL){assert (errno == ENOMEM);(void) munmap (mem, size);return errno;}lll_lock (stack_cache_lock, LLL_PRIVATE);stack_list_add (&pd->list, &stack_used);lll_unlock (stack_cache_lock, LLL_PRIVATE);// 在准备生成这个栈的时候,另一个线程可能是栈有可执行权限// 也就是修改GL(dl_stack_flags)的值// 这里只是检测这个发生的可能性// GL(dl_stack_flags) : _dl_stack_flags 全局变量// #define PF_X     (1 << 0)    /* Segment is executable */// #define PROT_EXEC    0x4     /* Page can be executed.    */// const int prot = (PROT_READ | PROT_WRITE//      | ((GL(dl_stack_flags) & PF_X) ? PROT_EXEC : 0));// 按照上面的prot赋值语句,这个可能性是很小的,除非修改全局变量_dl_stack_flagsif (__builtin_expect ((GL(dl_stack_flags) & PF_X) != 0&& (prot & PROT_EXEC) == 0, 0)) {int err = change_stack_perm (pd
#ifdef NEED_SEPARATE_REGISTER_STACK, ~pagesize_m1
#endif);if (err != 0){/* Free the stack memory we just allocated. */(void) munmap (mem, size);return err;}}// NOTE:所有的栈和线程描述符都是用零填充的// 就是说不用再用0初始化一些字段// 对于'tid'字段更确定是0, 一个栈一旦不再使用就会设置成0// 对于'guardsize' 字段,下一次还会再用不会清零}// 创建或者重新规划保护区的大小// pd->guardsize中存放着原先栈空间中的保护区大小(如果是从缓存中捞出来的栈)if (__glibc_unlikely (guardsize > pd->guardsize)){
#ifdef NEED_SEPARATE_REGISTER_STACK // 大概从一般的时候开始char *guard = mem + (((size - guardsize) / 2) & ~pagesize_m1);
#elif _STACK_GROWS_DOWN // 自顶向下增长的栈,保护区就在栈的末尾,即栈内存的起始处char *guard = mem;
# elif _STACK_GROWS_UP  // 自底向上增长的栈,保护区在线程描述符(pd)减去保护区大小(页对齐)char *guard = (char *) (((uintptr_t) pd - guardsize) & ~pagesize_m1);
#endif// mprotect是POSIX系统标准接口,设置内存访问权限if (mprotect (guard, guardsize, PROT_NONE) != 0){
mprot_error:lll_lock (stack_cache_lock, LLL_PRIVATE);// 从列表中删除这个线程stack_list_del (&pd->list);lll_unlock (stack_cache_lock, LLL_PRIVATE);// 删除分配的TLS块_dl_deallocate_tls (TLS_TPADJ (pd), false);// 不管缓冲区的size是否超过了限制,释放栈内存// 如果这片内存造成一些异常就最好不要再用了.// 忽略可能出现的错误, 就是出错也无能为力(void) munmap (mem, size);return errno;}pd->guardsize = guardsize;}else if (__builtin_expect (pd->guardsize - guardsize > size - reqsize,0))    // size当前的值是栈缓存分配的内存大小或mmap分配的内存大小, reqsize是原先需求的栈大小// 为什么是size - reqsize? 因为上一次的保护区范围已经超出了当前的栈空间// 当前的栈空间大小其实是reqsize{// 旧的保护区太大#ifdef NEED_SEPARATE_REGISTER_STACKchar *guard = mem + (((size - guardsize) / 2) & ~pagesize_m1);char *oldguard = mem + (((size - pd->guardsize) / 2) & ~pagesize_m1);if (oldguard < guard&& mprotect (oldguard, guard - oldguard, prot) != 0)goto mprot_error;if (mprotect (guard + guardsize,oldguard + pd->guardsize - guard - guardsize,prot) != 0)goto mprot_error;
#elif _STACK_GROWS_DOWNif (mprotect ((char *) mem + guardsize, pd->guardsize - guardsize,prot) != 0)goto mprot_error;
#elif _STACK_GROWS_UPif (mprotect ((char *) pd - pd->guardsize,pd->guardsize - guardsize, prot) != 0)goto mprot_error;
#endifpd->guardsize = guardsize;}// pthread_getattr_np()调用需要传递请求的属性大小,不论guardsize使用的实际上是多大。pd->reported_guardsize = guardsize;}// 初始化锁. 任何情况下都要做这个动作,因为创建失败的线程可能在加锁的状态被取消pd->lock = LLL_LOCK_INITIALIZER;// robust mutex链表必须被初始化, 内核中可能正好在清理前一个栈pd->robust_head.futex_offset = (offsetof (pthread_mutex_t, __data.__lock)- offsetof (pthread_mutex_t,__data.__list.__next));pd->robust_head.list_op_pending = NULL;
#ifdef __PTHREAD_MUTEX_HAVE_PREVpd->robust_prev = &pd->robust_head;
#endifpd->robust_head.list = &pd->robust_head;// 将线程描述符放在栈的尾部*pdp = pd;#if TLS_TCB_AT_TP// 栈是从TCB和静态TLS块之前开始的stacktop = ((char *) (pd + 1) - __static_tls_size);
#elif TLS_DTV_AT_TPstacktop = (char *) (pd - 1);
#endif#ifdef NEED_SEPARATE_REGISTER_STACK*stack = pd->stackblock;*stacksize = stacktop - *stack;
#elif _STACK_GROWS_DOWN*stack = stacktop;
#elif _STACK_GROWS_UP*stack = pd->stackblock;assert (*stack > 0);
#endifreturn 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291
  • 292
  • 293
  • 294
  • 295
  • 296
  • 297
  • 298
  • 299
  • 300
  • 301
  • 302
  • 303
  • 304
  • 305
  • 306
  • 307
  • 308
  • 309
  • 310
  • 311
  • 312
  • 313
  • 314
  • 315
  • 316
  • 317
  • 318
  • 319
  • 320
  • 321
  • 322
  • 323
  • 324
  • 325
  • 326
  • 327
  • 328
  • 329
  • 330
  • 331
  • 332
  • 333
  • 334
  • 335
  • 336
  • 337
  • 338
  • 339
  • 340
  • 341
  • 342
  • 343
  • 344
  • 345
  • 346
  • 347
  • 348
  • 349
  • 350
  • 351
  • 352
  • 353
  • 354
  • 355
  • 356
  • 357
  • 358
  • 359
  • 360
  • 361
  • 362
  • 363
  • 364
  • 365
  • 366
  • 367
  • 368
  • 369
  • 370
  • 371
  • 372
  • 373
  • 374
  • 375
  • 376
  • 377
  • 378
  • 379
  • 380
  • 381
  • 382
  • 383
  • 384
  • 385
  • 386
  • 387

再次简单描述如下: 
1. 确定栈大小 
如果用户提供了栈大小,就用用户提供的,否则使用默认的。 
2. 栈内存 
3. 保护区 
4. TLS 
5. 着色

create_thread

pthread_create前半部分只是为创建线程做准备,但是真正的创建线程是在create_thread,这里初始化创建线程参数,调用内核接口。

代码中对创建线程的一个参数clone_flag做了重要介绍,这里单独列出来.

CLONE_VM, CLONE_FS, CLONE_FILES
这几个标识符是按照POSIX的要求,共享地址空间和文件描述符CLONE_SIGNAL
应用POSIX信号
#define CLONE_SIGNAL        (CLONE_SIGHAND | CLONE_THREAD)CLONE_SETTLS
第六个克隆的参数决定了新线程的TLS区域CLONE_PARENT_SETTID
内核将新创建的线程的ID写到克隆的第五个参数指定的位置上与CLONE_CHILD_SETTID语义相同,但是在内核中消耗更大.CLONE_CHILD_CLEARTID
内核清理调用sys_exit的线程的线程ID, 这个数据是在CLONE的第7个参数指定的位置上.终止的信号设置为0,就是不需要发送信号CLONE_SYSVSEM
共享sysvsem
// nptl/createthread.c
static int
create_thread (struct pthread *pd, const struct pthread_attr *attr,STACK_VARIABLES_PARMS)
{
#if TLS_TCB_AT_TPassert (pd->header.tcb != NULL);
#endifint clone_flags = (CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGNAL| CLONE_SETTLS | CLONE_PARENT_SETTID| CLONE_CHILD_CLEARTID | CLONE_SYSVSEM| 0);if (__glibc_unlikely (THREAD_GETMEM (THREAD_SELF, report_events))){// 这个report events到底是做什么的, 不得而知, 因此先跳过此段...// 父线程支持report event, 检查是否需要TD_CREATEconst int _idx = __td_eventword (TD_CREATE);const uint32_t _mask = __td_eventmask (TD_CREATE);if ((_mask & (__nptl_threads_events.event_bits[_idx]| pd->eventbuf.eventmask.event_bits[_idx])) != 0){// 线程启动时一定是stopped状态pd->stopped_start = true;// 创建线程. 使用stopped状态创建线程,这样的话通知调试器时程序不会已经运行很多了int res = do_clone (pd, attr, clone_flags, start_thread,STACK_VARIABLES_ARGS, 1);if (res == 0){// 在新创建的线程数据结构中填充新线程的信息. // 新线程不能做这个任务,因为在发送这个事件时不能确定它是否已经调度过了pd->eventbuf.eventnum = TD_CREATE;pd->eventbuf.eventdata = pd;dopd->nextevent = __nptl_last_event;while (atomic_compare_and_exchange_bool_acq (&__nptl_last_event,pd, pd->nextevent)!= 0);// 发送事件__nptl_create_event ();// 启动线程lll_unlock (pd->lock, LLL_PRIVATE);}return res;}}#ifdef NEED_DL_SYSINFOassert (THREAD_SELF_SYSINFO == THREAD_SYSINFO (pd));
#endif// 因为需要设置调度参数和亲属关系(就是与父线程的关系),所以需要判断新线程// 是否需要启动或者停止// 疑问:cpuset是做什么用的bool stopped = false;if (attr != NULL && (attr->cpuset != NULL|| (attr->flags & ATTR_FLAG_NOTINHERITSCHED) != 0))stopped = true;pd->stopped_start = stopped;pd->parent_cancelhandling = THREAD_GETMEM (THREAD_SELF, cancelhandling);// do_clone会调用内核接口创建线程int res = do_clone (pd, attr, clone_flags, start_thread,STACK_VARIABLES_ARGS, stopped);if (res == 0 && stopped)lll_unlock (pd->lock, LLL_PRIVATE);return res;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78

do_clone

这个函数是由create_thread调用的,调用内核接口创建线程的接口

// nptl/createthread.cstatic int
do_clone (struct pthread *pd, const struct pthread_attr *attr,int clone_flags, int (*fct) (void *), STACK_VARIABLES_PARMS,int stopped)
{TLS_DEFINE_INIT_TP (tp, pd);if (__glibc_unlikely (stopped != 0))// 强制性加锁可以防止线程运行太多. 加锁后可以让线程停止继续执行直到通知它开始lll_lock (pd->lock, LLL_PRIVATE);// 新增一个线程. 不能在线程中处理, 因为可能这个线程已经存在但是在返回时还没有调度到.// 如果失败, 临时放一个错误的值; 这样做也没关系, 因为没有一个合适的信号处理机制在这里中断下来// 去关注线程计数是否正确// 这样说感觉太生硬, 原文的意思就是这个全局变量的线程计数在创建线程前就做了加1操作, 就是有短暂// 的时间是错误的, 因为线程毕竟还没有创建出来. 但是这样做没有关系, 也没有一种合适的机制去处理这// 种异常atomic_increment (&__nptl_nthreads);// 调用内核接口// # define ARCH_CLONE __clone  // 这是内核特定的函数了int rc = ARCH_CLONE (fct, STACK_VARIABLES_ARGS, clone_flags, // fct = start_threadpd, &pd->tid, tp, &pd->tid);if (__glibc_unlikely (rc == -1)){// 如果线程创建失败,线程计数会有短暂的错误值,在这里才将计数减去atomic_decrement (&__nptl_nthreads);// 可能会有线程想要改变ID并且等待这个创建失败的线程if (__builtin_expect (atomic_exchange_acq (&pd->setxid_futex, 0)== -2, 0))lll_futex_wake (&pd->setxid_futex, 1, LLL_PRIVATE);__deallocate_stack (pd);return errno == ENOMEM ? EAGAIN : errno;}// 设置调度参数if (__glibc_unlikely (stopped != 0)){INTERNAL_SYSCALL_DECL (err);int res = 0;// 设置affinity(亲密关系)if (attr->cpuset != NULL){res = INTERNAL_SYSCALL (sched_setaffinity, err, 3, pd->tid,attr->cpusetsize, attr->cpuset);if (__glibc_unlikely (INTERNAL_SYSCALL_ERROR_P (res, err))){// 设置失败. 杀掉线程,首先发送取消信号INTERNAL_SYSCALL_DECL (err2);
err_out:(void) INTERNAL_SYSCALL (tgkill, err2, 3,THREAD_GETMEM (THREAD_SELF, pid),pd->tid, SIGCANCEL);// NOTE: 这里没有释放线程栈,放到了取消线程中处理了return (INTERNAL_SYSCALL_ERROR_P (res, err)? INTERNAL_SYSCALL_ERRNO (res, err): 0);}}// 设置调度参数// ATTR_FLAG_NOTINHERITSCHED: 不知道这个标志的具体含义//  从字面意思理解,就是不继承父线程的调度参数if ((attr->flags & ATTR_FLAG_NOTINHERITSCHED) != 0){res = INTERNAL_SYSCALL (sched_setscheduler, err, 3, pd->tid,pd->schedpolicy, &pd->schedparam);if (__glibc_unlikely (INTERNAL_SYSCALL_ERROR_P (res, err)))goto err_out;}}// 这里的时候已经确定确实多了一个线程了. // 主线程中可能还没有这个标志. 全局变量已经不需要再次设置了(就是这个__nptl_nthreads)THREAD_SETMEM (THREAD_SELF, header.multiple_threads, 1);return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89

start_thread

// nptl/pthread_create.cstatic int
start_thread (void *arg)
{struct pthread *pd = (struct pthread *) arg;#if HP_TIMING_AVAIL// 记录线程启动时间hp_timing_t now;HP_TIMING_NOW (now);THREAD_SETMEM (pd, cpuclock_offset, now);
#endif// 初始化解析器状态指针__resp = &pd->res;// 初始化本地数据指针__ctype_init ();// 现在开始允许setxidif (__glibc_unlikely (atomic_exchange_acq (&pd->setxid_futex, 0) == -2))lll_futex_wake (&pd->setxid_futex, 1, LLL_PRIVATE);#ifdef __NR_set_robust_list
# ifndef __ASSUME_SET_ROBUST_LISTif (__set_robust_list_avail >= 0)
# endif{INTERNAL_SYSCALL_DECL (err);// 这个函数永远不会失败(太强大了)// 应该就是在一个链表中添加一个节点INTERNAL_SYSCALL (set_robust_list, err, 2, &pd->robust_head,sizeof (struct robust_list_head));}
#endif// 如果在创建线程的时候父线程正在执行取消的代码, 新线程就继承信号mask标识// 重置取消信号mask标志位if (__glibc_unlikely (pd->parent_cancelhandling & CANCELING_BITMASK)){INTERNAL_SYSCALL_DECL (err);sigset_t mask;__sigemptyset (&mask);__sigaddset (&mask, SIGCANCEL);(void) INTERNAL_SYSCALL (rt_sigprocmask, err, 4, SIG_UNBLOCK, &mask,NULL, _NSIG / 8);}// 这是一个try/finally 代码块. 编译器没有这个功能就是用setjmpstruct pthread_unwind_buf unwind_buf;unwind_buf.priv.data.prev = NULL;unwind_buf.priv.data.cleanup = NULL;// 这里是设置异常处理的代码,比如线程不是在函数中正常的return退出,而是pthread_exit中途// 退出. 如果是中途退出的代码, setjmp返回的不是0,是longjmp设置的值(当然也可以设置为0,但// 这样做没有意义)int not_first_call;not_first_call = setjmp ((struct __jmp_buf_tag *) unwind_buf.cancel_jmp_buf);if (__glibc_likely (! not_first_call))  // 第一次进入这个逻辑setjmp返回0{// 存储新清理处理器信息THREAD_SETMEM (pd, cleanup_jmp_buf, &unwind_buf);if (__glibc_unlikely (pd->stopped_start)){int oldtype = CANCEL_ASYNC ();// 这里加锁又解锁,只是为了强制性同步一下lll_lock (pd->lock, LLL_PRIVATE);lll_unlock (pd->lock, LLL_PRIVATE);CANCEL_RESET (oldtype);}// LIBC_PROBE, 按照代码中的宏定义,其实这句话什么也没有生成,不知道它的用意是什么LIBC_PROBE (pthread_start, 3, (pthread_t) pd, pd->start_routine, pd->arg);// 调用用户提供的函数
#ifdef CALL_THREAD_FCTTHREAD_SETMEM (pd, result, CALL_THREAD_FCT (pd));
#elseTHREAD_SETMEM (pd, result, pd->start_routine (pd->arg));
#endif}// 一个线程结束后,就会执行下面的代码,清理一些资源// 调用线程局部TLS变量的析构函数
#ifndef SHAREDif (&__call_tls_dtors != NULL)
#endif// 这里只是调用所有的析构函数而已// 根据glibc中的解释,这些析构函数都是C++编译器生成的TLS变量的析构函数__call_tls_dtors ();    // 调用线程局部数据的析构函数__nptl_deallocate_tsd ();// 清理所有的线程局部变量的状态// 调用这些函数// arena_thread_freeres// res_thread_freeres// __rpc_thread_destroy// strerror_thread_freeres// 实现方式可以需要看GCC编译器中attribute section的说明__libc_thread_freeres ();// 判断是否是进程中最后一个线程. 不会通知调试器if (__glibc_unlikely (atomic_decrement_and_test (&__nptl_nthreads)))// 最后一个线程直接调用exitexit (0);// 汇报线程结束的事件if (__glibc_unlikely (pd->report_events)){// 判断事件mask标志位中是否包含TD_DEATHconst int idx = __td_eventword (TD_DEATH);const uint32_t mask = __td_eventmask (TD_DEATH);if ((mask & (__nptl_threads_events.event_bits[idx]| pd->eventbuf.eventmask.event_bits[idx])) != 0){// 必须发送线程结束的信号. 将描述符添加到链表中if (pd->nextevent == NULL){pd->eventbuf.eventnum = TD_DEATH;pd->eventbuf.eventdata = pd;dopd->nextevent = __nptl_last_event;while (atomic_compare_and_exchange_bool_acq (&__nptl_last_event,pd, pd->nextevent));}// 汇报事件__nptl_death_event ();}}// 线程已经退出. 在触发事件汇报断点前不能设置这个bit标记, 因此当断点汇报状态时,// td_thr_get_info获取的状态是TD_THR_RUN而不是TD_THR_ZOMBIEatomic_bit_set (&pd->cancelhandling, EXITING_BIT);#ifndef __ASSUME_SET_ROBUST_LIST// 如果线程加了什么robust的锁,就在这里处理掉
# ifdef __PTHREAD_MUTEX_HAVE_PREVvoid *robust = pd->robust_head.list;
# else__pthread_slist_t *robust = pd->robust_list.__next;
# endif// 如果可以的话,让内核做这个通知.// 这里做的事情就是确保不涉及PI mutex, 因为内核能更好的支持(文中说more recent)if (__set_robust_list_avail < 0&& __builtin_expect (robust != (void *) &pd->robust_head, 0)){do{struct __pthread_mutex_s *this = (struct __pthread_mutex_s *)((char *) robust - offsetof (struct __pthread_mutex_s,__list.__next));robust = *((void ##) robust);# ifdef __PTHREAD_MUTEX_HAVE_PREVthis->__list.__prev = NULL;
# endifthis->__list.__next = NULL;atomic_or (&this->__lock, FUTEX_OWNER_DIED);lll_futex_wake (this->__lock, 1, /* XYZ */ LLL_SHARED);}while (robust != (void *) &pd->robust_head);}
#endif// 将栈的内存标记为对内核可用. 除了TCB, 释放所有的空间size_t pagesize_m1 = __getpagesize () - 1;
#ifdef _STACK_GROWS_DOWNchar *sp = CURRENT_STACK_FRAME;size_t freesize = (sp - (char *) pd->stackblock) & ~pagesize_m1;
#else   // 这里就是说只能定义栈向下增长? 之前的UP又有什么意义?!还是我下载的代码版本不正确
# error "to do"
#endifassert (freesize < pd->stackblock_size);if (freesize > PTHREAD_STACK_MIN)__madvise (pd->stackblock, freesize - PTHREAD_STACK_MIN, MADV_DONTNEED);// 如果线程是detached状态,就释放TCBif (IS_DETACHED (pd))__free_tcb (pd);else if (__glibc_unlikely (pd->cancelhandling & SETXID_BITMASK)){// 其它的线程可能调用了setXid相关的函数, 并且期望得到回复. // 这样的话, 就必须一致等待一直到这里做这样的处理dolll_futex_wait (&pd->setxid_futex, 0, LLL_PRIVATE);while (pd->cancelhandling & SETXID_BITMASK);// 重置setxid_futex, stack就可以再次使用pd->setxid_futex = 0;}// 不能调用'_exit'. '_exit'会终止进程.// 因为'clone'函数设置了参数CLONE_CHILD_CLEARTID标志位, 在进程真正的结束后,// 内核中实现的'exit'会发送信号.// TCB中的'tid'字段会设置成0// 退出代码是0,以防所有线程调用'pthread_exit'退出__exit_thread ();return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215

pthread_exit

线程中调用这个函数时,即使没有return也会直接退出。

void
__pthread_exit (value)void *value;
{THREAD_SETMEM (THREAD_SELF, result, value);__do_cancel ();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

其中的__do_cancel是一个隐藏很深的函数

// nptl/pthreadP.h
// 当一个线程执行取消请求时调用
static inline void
__attribute ((noreturn, always_inline))
__do_cancel (void)
{struct pthread *self = THREAD_SELF;// 确保不会取消多次THREAD_ATOMIC_BIT_SET (self, cancelhandling, EXITING_BIT);// cleanup_jmp_buf这个变量的值是在start_thread中使用setjmp调用的// 这里调用clean_up相关的函数,然后做longjmp__pthread_unwind ((__pthread_unwind_buf_t *)THREAD_GETMEM (self, cleanup_jmp_buf));
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

再来看__pthread_unwind。

// nptl/unwind.c
void
__cleanup_fct_attribute __attribute ((noreturn))
__pthread_unwind (__pthread_unwind_buf_t *buf)
{// 这个buf是在start_thread中设置的struct pthread_unwind_buf *ibuf = (struct pthread_unwind_buf *) buf;struct pthread *self = THREAD_SELF;#ifdef HAVE_FORCED_UNWIND// 也不是一个可以捕获的异常, 因此不用提供关于异常类型的任何信息.// 但是需要初始化这些异常的字段THREAD_SETMEM (self, exc.exception_class, 0);THREAD_SETMEM (self, exc.exception_cleanup, &unwind_cleanup);_Unwind_ForcedUnwind (&self->exc, unwind_stop, ibuf);
#else// 首先处理兼容的字段. 执行所有注册的函数. 但是不是按照顺序执行的struct _pthread_cleanup_buffer *oldp = ibuf->priv.data.cleanup;struct _pthread_cleanup_buffer *curp = THREAD_GETMEM (self, cleanup);if (curp != oldp){do{// 遍历链表,执行所有的handlerstruct _pthread_cleanup_buffer *nextp = curp->__prev;curp->__routine (curp->__arg);curp = nextp;}while (curp != oldp);THREAD_SETMEM (self, cleanup, curp);}// 调用longjmp,跳转到setjmp注册的地方__libc_unwind_longjmp ((struct __jmp_buf_tag *) ibuf->cancel_jmp_buf, 1);
#endif// 正常情况下都不会到这里abort ();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

创建和释放TLS

创建TLS相关的接口是_dl_allocate_tls。TLS牵涉范围很广,阅读创建线程相关的代码时,遇到了很多陌生的概念,我也不能一次弄清楚这么多,因为太发散,后面再找资料,专门阅读下TLS相关的代码,这里先简单看下,有个印象。 
这里的代码主要功能是为线程分配DTV,复制每个模块的TLS部分的全局变量,并清零BSS数据段。

// elf/dl-tls.c
void *
internal_function
_dl_allocate_tls (void *mem)
{return _dl_allocate_tls_init (mem == NULL? _dl_allocate_tls_storage (): allocate_dtv (mem));// 这里的mem不是NULL,因此参数就是allocate_dtv(mem)的返回值
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

allocate_dtv

DTV: Dynamic Thread Vector

在x86_64系统上, dtv_t的定义如下

/* Type for the dtv.    */
typedef union dtv
{size_t counter;struct{void *val;bool is_static;} pointer;
} dtv_t;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
static void *
internal_function
allocate_dtv (void *result)
{dtv_t *dtv;size_t dtv_length;// 分配的比实际需要的DTV要多一点。可以避免大部分需要扩展DTV的情况。// #define DTV_SURPLUS              (14)dtv_length = GL(dl_tls_max_dtv_idx) + DTV_SURPLUS;dtv = calloc (dtv_length + 2, sizeof (dtv_t));if (dtv != NULL){dtv[0].counter = dtv_length;    // dtv的第一个元素只是用来计数// 剩下的DTV都初始化为0INSTALL_DTV (result, dtv);  // 将dtv设置到struct pthread中
# define INSTALL_DTV(descr, dtvp) \((tcbhead_t *) (descr))->dtv = (dtvp) + 1       // 只是设置成员变量}elseresult = NULL;return result;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

_dl_allocate_tls_init

EXTERN struct dtv_slotinfo_list
{size_t len;struct dtv_slotinfo_list *next;struct dtv_slotinfo{size_t gen;struct link_map *map;} slotinfo[0];  // 这里是结构体中最后一个元素,可以随意扩展
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
void *
internal_function
_dl_allocate_tls_init (void *result)
{if (result == NULL)return NULL;dtv_t *dtv = GET_DTV (result);struct dtv_slotinfo_list *listp;size_t total = 0;size_t maxgen = 0;// 为所有当前已经加载使用TLS的模块准备DTV// 动态加载的模块,设置为延迟分配listp = GL(dl_tls_dtv_slotinfo_list);while (1){size_t cnt;// 遍历所有的模块for (cnt = total == 0 ? 1 : 0; cnt < listp->len; ++cnt){struct link_map *map;void *dest;// 检查使用的slot总数if (total + cnt > GL(dl_tls_max_dtv_idx))   // dl_tls_max_dtv_idx不知道这个是啥,而且不用加锁访问全局变量感觉很奇怪break;                                  // 难道这个数字是初始化后固定死的?map = listp->slotinfo[cnt].map;if (map == NULL)continue;// 跟踪最大的generation值. 这可能不是generation的个数assert (listp->slotinfo[cnt].gen <= GL(dl_tls_generation));maxgen = MAX (maxgen, listp->slotinfo[cnt].gen);if (map->l_tls_offset == NO_TLS_OFFSET                  // # define NO_TLS_OFFSET   0|| map->l_tls_offset == FORCED_DYNAMIC_TLS_OFFSET)  // FORCED_DYNAMIC_TLS_OFFSET 小于0的数字{// 对于动态加载的模块,只是存储这个值表示延迟分配dtv[map->l_tls_modid].pointer.val = TLS_DTV_UNALLOCATED;dtv[map->l_tls_modid].pointer.is_static = false;continue;}assert (map->l_tls_modid == cnt);assert (map->l_tls_blocksize >= map->l_tls_initimage_size);
#if TLS_TCB_AT_TPassert ((size_t) map->l_tls_offset >= map->l_tls_blocksize);dest = (char *) result - map->l_tls_offset;     // 为啥要弄这个偏移量呢?
#elif TLS_DTV_AT_TPdest = (char *) result + map->l_tls_offset;
#else
# error "Either TLS_TCB_AT_TP or TLS_DTV_AT_TP must be defined"
#endif// 复制初始化区域并清除BSS部分数据// BSS:Block Started by Symbol segment, 存放程序中未初始化的全局变量dtv[map->l_tls_modid].pointer.val = dest;dtv[map->l_tls_modid].pointer.is_static = true;memset (__mempcpy (dest, map->l_tls_initimage,map->l_tls_initimage_size), '\0',map->l_tls_blocksize - map->l_tls_initimage_size);}total += cnt;if (total >= GL(dl_tls_max_dtv_idx))break;listp = listp->next;assert (listp != NULL); // 这个list没有结尾吗?}dtv[0].counter = maxgen;    // DTV的第一个元素只是一个计数return result;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78

释放pthread中TLS的数据

// nptl/pthread_create.c// 释放POSIX TLS
void
attribute_hidden
__nptl_deallocate_tsd (void)
{struct pthread *self = THREAD_SELF;// 可能没有分配过数据. // 这样的情况会经常出现,所以使用一个标识记录一下if (THREAD_GETMEM (self, specific_used)){size_t round;size_t cnt;round = 0;do{size_t idx;// 到目前为止已经没有非零数据了THREAD_SETMEM (self, specific_used, false);// TLS数组:// struct pthread_key_data *specific[PTHREAD_KEY_1STLEVEL_SIZE]// struct pthread_key_data *specific_1stblock[PTHREAD_KEY_2NDLEVEL_SIZE];// 并且specific[0] = specific_1stblockfor (cnt = idx = 0; cnt < PTHREAD_KEY_1STLEVEL_SIZE; ++cnt){struct pthread_key_data *level2;level2 = THREAD_GETMEM_NC (self, specific, cnt);if (level2 != NULL){size_t inner;for (inner = 0; inner < PTHREAD_KEY_2NDLEVEL_SIZE;++inner, ++idx){void *data = level2[inner].data;if (data != NULL){level2[inner].data = NULL;// 确保这里的数据包含一个正确的key// 如果这个key已经被释放了,并且是重新分配的,这个检测就会失败// 这种情况下就由用户来确保内存的释放// 这里的代码与pthread_key_create等函数相关,暂时不做理解,只是留个印象if (level2[inner].seq== __pthread_keys[idx].seq&& __pthread_keys[idx].destr != NULL)// 这个析构函数并不是一定要提供的,比如只是一个int数字的时候__pthread_keys[idx].destr (data);}}}elseidx += PTHREAD_KEY_1STLEVEL_SIZE;}if (THREAD_GETMEM (self, specific_used) == 0)goto just_free;}// PTHREAD_DESTRUCTOR_ITERATIONS 4// 这里为啥做个循环?不能一次性清理掉数据?线程都退出了,为什么还要多次遍历释放// 其实在goto just_free那里,就已经退出了,因为这个循环开始就设置了specific_used = falsewhile (__builtin_expect (++round < PTHREAD_DESTRUCTOR_ITERATIONS, 0));// 清空第一个数据库,就是specific_1stblock[PTHREAD_KEY_2NDLEVEL_SIZE],固定存放的这个数组// 按说也不用做这个处理,因为这是线程退出,这个线程已经不会再用这个内存了// 新线程再创建的时候,也会将这块内存清零memset (&THREAD_SELF->specific_1stblock, '\0',sizeof (self->specific_1stblock));just_free:// specific中除了第一个元素不用释放,其它的都要释放掉for (cnt = 1; cnt < PTHREAD_KEY_1STLEVEL_SIZE; ++cnt){struct pthread_key_data *level2;level2 = THREAD_GETMEM_NC (self, specific, cnt);if (level2 != NULL){free (level2);THREAD_SETMEM_NC (self, specific, cnt, NULL);}}THREAD_SETMEM (self, specific_used, false); // 设置了一次又一次,怕它跑了?}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90

简易流程图

pthread_create, POSIX创建线程接口初始化线程属性pthread_attr_t设置pthread中eventbuf, sched,priority等参数创建线程栈ALLOCATE_STACK用户指定线程栈?校验栈大小是否合适设置pthread内存和参数为pthread分配DTV(DTV是TLS的必要内容)执行线程函数设置异常处理点执行用户提供线程接口调用所有TLS的析构函数调用局部变量的析构函数清理所有的线程局部变量的状态汇报线程退出事件是否detached状态释放TCB退出线程__exit_thread线程结束从缓存中分配线程栈内存从缓存分配成功?设置pthread内存和参数为pthread分配DTV(DTV是TLS的必要内容)调用mmap从内存分配栈内存yesnoyesnoyesno

参考文章

  1. POSIX NPTL: http://www.cnblogs.com/leaven/archive/2010/05/23/1741941.html
  2. Linux 线程模型的比较:LinuxThreads 和 NPTL: http://www.ibm.com/developerworks/cn/linux/l-threading.html
  3. Native POSIX Thread Library: http://en.wikipedia.org/wiki/Native_POSIX_Thread_Library
  4. Green Thread: http://en.wikipedia.org/wiki/Green_threads
  5. Library(Computing): http://en.wikipedia.org/wiki/Library_(computing)
  6. NPTL trace tool: http://nptltracetool.sourceforge.net/
  7. GNU C library version 2.3.3 release: http://www.akkadia.org/drepper/lt2002talk.pdf
  8. NPTL thread design: http://www.akkadia.org/drepper/nptl-design.pdf
  9. 关于Linux线程的线程栈以及TLS:http://blog.csdn.net/dog250/article/details/7704898
  10. Linux用户空间线程管理介绍之二:创建线程堆栈http://www.longene.org/forum/viewtopic.php?f=17&t=429
  11. Linux线程之线程栈: http://blog.chinaunix.net/uid-24774106-id-3651266.html
  12. 浅析glibc中thread tls的一处bug: http://www.udpwork.com/item/13359.html
  13. 理解堆栈及其利用方法:http://blog.aliyun.com/964?spm=0.0.0.0.kXku9Q

GLIBC中NPTL线程实现代码阅读相关推荐

  1. 一段挂起进程中所有线程的代码

    今天看书核心编程看到第7章,中的一段代码很有意思,win7下对记事本进程进行测试,可以挂起,挺有意思的 //windows核心编程 第5版中的一段代码 /* 函数功能:挂起进程中的所有线程 参数1:进 ...

  2. Linux中的线程同步机制-futex

    Linux中的线程同步机制(一) -- Futex 引子 在编译2.6内核的时候,你会在编译选项中看到[*] Enable futex support这一项,上网查,有的资料会告诉你"不选这 ...

  3. Linux 平台 C/C++ 代码中设置线程名

    一般来说,Linux 平台的 C/C++ 程序可以用 prctl() 或 pthreads 的 pthread_setname_np() 接口为一个线程设置线程名.prctl() 可以用于为当前线程设 ...

  4. ORB_SLAM2代码阅读(3)——LocalMapping线程

    ORB_SLAM2代码阅读(3)--LocalMapping线程 1.说明 2.简介 3.处理关键帧 4. 地图点剔除 5. 创建新的地图点 6.相邻搜索 6.剔除冗余关键帧 1.说明 本文介绍ORB ...

  5. ORB_SLAM2代码阅读(4)——LoopClosing线程

    ORB_SLAM2代码阅读(4)--LoopClosing线程 1.说明 2.简介 3.检测回环 4.计算Sim3 4.1 为什么在进行回环检测的时候需要计算相似变换矩阵,而不是等距变换? 4.2 累 ...

  6. ORB_SLAM2代码阅读(2)——tracking线程

    ORB_SLAM2代码阅读(2)--Tracking线程 1. 说明 2. 简介 2.1 Tracking 流程 2.2 Tracking 线程的二三四 2.2.1 Tracking 线程的二种模式 ...

  7. 【代码阅读】PointNet++中ball query的CUDA实现

    文章目录 本文为PointNet++ CUDA代码阅读系列的第三部分,其他详见: (一)PointNet++代码梳理 (二)PointNet++中的FPS的CUDA实现 (三)PointNet++中b ...

  8. ORB-SLAM2代码阅读笔记(五):Tracking线程3——Track函数中单目相机初始化

    Table of Contents 1.特征点匹配相关理论简介 2.ORB-SLAM2中特征匹配代码分析 (1)Tracking线程中的状态机 (2)单目相机初始化函数MonocularInitial ...

  9. python停止线程池_详解python中Threadpool线程池任务终止示例代码

    需求 加入我们需要处理一串个位数(0~9),奇数时需要循环打印它:偶数则等待对应时长并完成所有任务:0则是错误,但不需要终止任务,可以自定义一些处理. 关键点 定义func函数处理需求 callbac ...

最新文章

  1. Swift与LLVM-Clang原理与示例
  2. 中小企业对于云计算的3大误解
  3. EntityFramework之领域驱动设计实践(十)(转)
  4. 深入理解ES6笔记(九)JS的类(class)
  5. queueing 优化_简单聊聊网页的资源加载优化
  6. CentOS8下安装docker
  7. SQL注入学习——sqli-labs闯关(Basic Challenges)
  8. java 第十一章总结
  9. “之”字形打印矩阵~
  10. 升级python以及安装anaconda
  11. FinTech:一个单体系统足以撑起银行持续交付全球大项目
  12. 蚁群算法python_想要学习启发式算法?推荐你看看这个价值极高的开源项目
  13. python plt 绘制直方图概率密度和不为1
  14. 机器学习cs229——(一)概要
  15. 多路测温系统C51语言,基于单片机的多路温度检测系统的.docx
  16. 如何在Apple Watch上查看实时照片
  17. 配置VScode上基于WSL的lc3汇编语言环境
  18. typeScript的定义类型:不能将类型“Timeout”分配给类型“number”;
  19. ubuntu中如何运行exe文件
  20. 阿里云域名购买与域名解析使用教程(图文教程)

热门文章

  1. OpenCV中直方图对比
  2. 突破传统生物3D打印技术局限-王秀杰/Charlie C.L. Wang/刘永进团队合作开发新型生物3D打印体系...
  3. R沟通|Bookdown中文书稿写作手册(中)
  4. 1.4编程基础之逻辑表达式与条件分支 05 整数大小比较
  5. Python批量替换目录下文件后缀脚本实例
  6. EXCEL表格转换成json数据工具
  7. phpStrom编辑器常用功能教程
  8. Java笔记-AnnotationConfigApplicationContext在Spring中的例子
  9. Java工作笔记-接入互联网的免费WebService
  10. 如何把网上下载的前端页面在Spring Boot中跑起来(CSS,JavaScript,程序运行等路径设置)