GLIBC中NPTL线程实现代码阅读

项目的性能测试告一段落,暂时松了一口气。但是也发现很多知识的盲点,也许这就是所谓的知道的越多,不知道的也越多。
现在所有的程序基本上都是用多线程来实现的,尤其是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

线程属性 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 map
    size_t cpusetsize;
};

NOTE: struct sched_param

// 调度算法
#define SCHED_OTHER 0
#define SCHED_FIFO  1
#define SCHED_RR    2

struct sched_param
{
    int __sched_priority;
};

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;

资源清理 struct _pthread_cleanup_buffer

// sysdeps/nptl/pthread.h
struct _pthread_cleanup_buffer
{
    void (*__routine) (void *);                      
    void *__arg;
    int __canceltype;
    struct _pthread_cleanup_buffer *__prev; 
};

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 buffer
            struct pthread_unwind_buf *prev;

            // 向后兼容:前一个新风格清理处理器安装的时间老风格的清理处理器状态
            struct _pthread_cleanup_buffer *cleanup;
            int canceltype; // push 调用前取消(cancellation)的类型
        } data;
    } priv;
};

异常处理_Unwind_Exception

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

#define _Unwind_Exception _Unwind_Control_Block
struct _Unwind_Control_Block
{
#ifdef _LIBC
    /* For the benefit of code which assumes this is a scalar.  All
     glibc ever does is clear it.  */
    _uw64 exception_class;
#else
    char exception_class[8];
#endif
    void (*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 */
};

线程 struct pthread

nptl/descr.h

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

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

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

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

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

struct pthread
{
    union
    {
#if !TLS_DTV_AT_TP
        tcbhead_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_FUTEX
            int private_futex;
# endif
        } header;
#endif
        void *__padding[24];
    };

    list_t list;    // `stack_used' 或 `__stack_user' 链表节点
    pid_t tid;      // 线程ID,也是线程描述符
    pid_t pid;      // 进程ID,线程组ID

    // 进程当前持有的robust mutex
#ifdef __PTHREAD_MUTEX_HAVE_PREV
    void *robust_prev;
    struct robust_list_head robust_head;

    /* The list above is strange.   It is basically a double linked list
         but the pointer to the next/previous element of the list points
         in the middle of the object, the __next element.   Whenever
         casting to __pthread_list_t we need to adjust the pointer
         first. */

#else
    union
    {
        __pthread_slist_t robust_list;
        struct robust_list_head robust_head;
    };

#endif

    struct _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_1stblock
    struct 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_priority
    int schedpolicy;

    // 执行函数的地址和参数
    void *(*start_routine) (void *);
    void *arg;

    td_eventbuf_t eventbuf;                     // 调试状态
    struct pthread *nextevent;                  // 下一个有pending事件的描述符,应该是用来调试的

#ifdef HAVE_FORCED_UNWIND
    struct _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)));

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

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

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 = 0
    STACK_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_GUARD
    THREAD_COPY_STACK_GUARD (pd);
#endif

    // 复制指针保护值
#ifdef THREAD_COPY_POINTER_GUARD
    THREAD_COPY_POINTER_GUARD (pd);
#endif

    // 计算线程的调度参数
    // NOTE: # define __builtin_expect(expr, val)    (expr)
    //   如果val是0,它的功能类似于unlikely; 如果val 是1, 功能就是likely
    if (__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;
}

创建线程栈

线程栈可以由用户提供,也可以让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)

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_TP
        adj = ((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_TP
        adj = ((uintptr_t) attr->stackaddr - __static_tls_size)
            & __static_tls_align_m1;
        assert (size > adj);
#endif

        // 用户提供的内存. 如果是用户自己提供的栈,不会分配保护页. 
#if TLS_TCB_AT_TP
        pd = (struct pthread *) ((uintptr_t) attr->stackaddr
                - TLS_TCB_SIZE - adj);
#elif TLS_DTV_AT_TP
        pd = (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 != 0
            if ((size % MULTI_PAGE_ALIASING) == 0) // MULTI_PAGE_ALIASING默认定义是0
                size += 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_TP
            pd = (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);

            // 为线程分配DTV
            if (_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_flags
            if (__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_STACK
            char *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_DOWN
            if (mprotect ((char *) mem + guardsize, pd->guardsize - guardsize,
                prot) != 0)
                goto mprot_error;
#elif _STACK_GROWS_UP
            if (mprotect ((char *) pd - pd->guardsize,
                pd->guardsize - guardsize, prot) != 0)
                goto mprot_error;
#endif

            pd->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_PREV
    pd->robust_prev = &pd->robust_head;
#endif
    pd->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_TP
    stacktop = (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);
#endif

    return 0;
}

再次简单描述如下:
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区域,kernel会将struct thread *设置到fs段寄存器指定的段内存中。不过fs寄存器一直是0(参考内核代码do_arch_prctl@process_64.c linux 3.13)

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_TP
    assert (pd->header.tcb != NULL);
#endif

    int 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_CREATE
        const 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状态创建线程,这样的话通知调试器时程序不会已经运行很多了
            // clone_flags的CLONE_SETTLS标识,会告诉kernel将pd参数设置到fs段的内存中去
            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;

                do
                    pd->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_SYSINFO
    assert (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;
}

do_clone

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

// nptl/createthread.c

static 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_thread
                     pd, &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;
}

start_thread

// nptl/pthread_create.c

static 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 ();

    // 现在开始允许setxid
    if (__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_LIST
    if (__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 代码块. 编译器没有这个功能就是用setjmp
    struct 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_FCT
        THREAD_SETMEM (pd, result, CALL_THREAD_FCT (pd));
#else
        THREAD_SETMEM (pd, result, pd->start_routine (pd->arg));
#endif
    }

    // 一个线程结束后,就会执行下面的代码,清理一些资源

    // 调用线程局部TLS变量的析构函数
#ifndef SHARED
    if (&__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)))
        // 最后一个线程直接调用exit
        exit (0);

    // 汇报线程结束的事件
    if (__glibc_unlikely (pd->report_events))
    {
        // 判断事件mask标志位中是否包含TD_DEATH
        const 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;

                do
                    pd->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_ZOMBIE
    atomic_bit_set (&pd->cancelhandling, EXITING_BIT);

#ifndef __ASSUME_SET_ROBUST_LIST
    // 如果线程加了什么robust的锁,就在这里处理掉
# ifdef __PTHREAD_MUTEX_HAVE_PREV
    void *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_PREV
            this->__list.__prev = NULL;
# endif
            this->__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_DOWN
    char *sp = CURRENT_STACK_FRAME;
    size_t freesize = (sp - (char *) pd->stackblock) & ~pagesize_m1;
#else   // 这里就是说只能定义栈向下增长? 之前的UP又有什么意义?!还是我下载的代码版本不正确
# error "to do"
#endif
    assert (freesize < pd->stackblock_size);
    if (freesize > PTHREAD_STACK_MIN)
        __madvise (pd->stackblock, freesize - PTHREAD_STACK_MIN, MADV_DONTNEED);

    // 如果线程是detached状态,就释放TCB
    if (IS_DETACHED (pd))
        __free_tcb (pd);
    else if (__glibc_unlikely (pd->cancelhandling & SETXID_BITMASK))
    {
        // 其它的线程可能调用了setXid相关的函数, 并且期望得到回复. 
        // 这样的话, 就必须一致等待一直到这里做这样的处理
        do
            lll_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;
}

pthread_exit

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

void
__pthread_exit (value)
     void *value;
{
    THREAD_SETMEM (THREAD_SELF, result, value);
    __do_cancel ();
}

其中的__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)); 
}

再来看__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
        {
            // 遍历链表,执行所有的handler
            struct _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 ();
}

创建和释放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)的返回值
}

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;
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都初始化为0
        INSTALL_DTV (result, dtv);  // 将dtv设置到struct pthread中
# define INSTALL_DTV(descr, dtvp) \
    ((tcbhead_t *) (descr))->dtv = (dtvp) + 1       // 只是设置成员变量
    }
    else
        result = NULL;

    return result;
}

_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];  // 这里是结构体中最后一个元素,可以随意扩展
};
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_TP
            assert ((size_t) map->l_tls_offset >= map->l_tls_blocksize);
            dest = (char *) result - map->l_tls_offset;     // 为啥要弄这个偏移量呢?
#elif TLS_DTV_AT_TP
            dest = (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;
}

释放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_1stblock
            for (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);
                        }
                    }
                }
                else
                    idx += PTHREAD_KEY_1STLEVEL_SIZE;
            }

            if (THREAD_GETMEM (self, specific_used) == 0)
                goto just_free;
        }
        // PTHREAD_DESTRUCTOR_ITERATIONS 4
        // 这里为啥做个循环?不能一次性清理掉数据?线程都退出了,为什么还要多次遍历释放
        // 其实在goto just_free那里,就已经退出了,因为这个循环开始就设置了specific_used = false
        while (__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); // 设置了一次又一次,怕它跑了?
    }
}

简易流程图

Created with Raphaël 2.1.2 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从内存分配栈内存 yes no yes no yes no

参考文章

  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
  • 8
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
### 回答1: glibc是GNU计划的一部分,是一套C语言标准库。内存管理是其的一个重要组件。而ptmalloc2是glibc内存管理的一种算法,用于分配和释放内存块。 要下载glibc内存管理ptmalloc2源代码,可以通过以下几个步骤进行: 1. 打开GNU官方网站,找到glibc的相关页面,通常在https://www.gnu.org/software/libc/ 。 2. 在该页面上,找到下载链接或源代码仓库地址,这个地址通常会提供给用户下载最新版本的glibc。 3. 点击下载链接或者复制源代码仓库地址,将其粘贴到浏览器地址栏。 4. 打开该链接后,您将能够下载一个压缩文件(通常是tar.gz或tar.bz2格式),包含了glibc的全部源代码。 5. 下载完毕后,解压压缩文件。您可以使用解压软件,如WinRAR或7-Zip。解压缩后,您将获得一个包含许多目录和文件的文件夹。 6. 在解压后的文件夹,找到与ptmalloc2相关的源代码文件。通常这些文件会位于glibc代码的malloc目录下。 7. 在malloc目录,您将能够找到ptmalloc2源代码文件,这些文件名通常以"ptmalloc"或"ptmalloc2"开头。 以上是下载glibc内存管理ptmalloc2源代码的一个大致过程。通过该源代码,您可以深入了解ptmalloc2算法是如何在glibc实现内存分配和释放的。但是请注意,阅读和理解源代码需要一定的计算机编程经验和相关背景知识。 ### 回答2: glibcLinux操作系统非常重要的一个C标准库,ptmalloc2是glibc负责内存管理的模块之一。该模块负责动态分配和释放内存,并提供了多种内存分配器算法。 ptmalloc2源代码分析是深入研究该模块源代码的过程。通过分析ptmalloc2源代码,可以了解到它的实现原理、内存分配算法以及性能优化等方面的细节。 在下载ptmalloc2源代码之后,我们可以通过阅读和分析源代码来了解其内部结构和工作原理。在源代码,我们可以找到一些关键的数据结构和函数,如malloc、free、realloc等。这些函数实现了动态内存分配和释放的基本功能。 通过阅读代码,我们可以学习到ptmalloc2内存管理器的特点和优势。例如,ptmalloc2采用了分离的空闲链表来管理不同大小的内存块,利用了空闲块合并和分割等技术来提高内存的利用率和性能。此外,源代码还可能包含一些与内存操作相关的底层函数和宏定义。 分析ptmalloc2源代码不仅可以帮助我们理解其内部实现,还可以为我们定位和解决内存管理相关的问题提供指导。如果遇到性能问题或者内存泄漏等现象,我们可以通过分析源代码来找到问题的根源,并提出相应的优化措施。 总之,通过对glibc内存管理模块ptmalloc2的源代码进行分析,我们可以深入了解其实现原理和内部机制,为我们在实际项目正确、高效地使用内存管理功能提供帮助。 ### 回答3: glibcLinux系统上使用最广泛的C语言函数库,而ptmalloc2则是glibc负责内存分配和管理的部分源代码。 首先,需要明确的是,glibc的ptmalloc2源代码并不是一个独立的项目,而是glibc的一部分。如果需要下载该源代码,可以通过访问glibc的官方网站或者使用git等工具来获取。 分析glibc内存管理ptmalloc2源代码可以帮助开发者更好地理解和使用glibc的内存分配功能。ptmalloc2实现了一种基于堆的内存分配算法,它采用了多种策略来管理内存,如bin和fastbin等。源代码的分析可以帮助我们了解这些策略的具体实现细节,以及它们在不同场景下的行为。 要对ptmalloc2源代码进行分析,可以首先阅读相关文档,如glibc的官方文档或论文。 掌握ptmalloc2的整体架构、数据结构和算法等基本知识后,可以通过逐行或逐函数地阅读代码来深入理解其内部工作机制。可以关注一些关键函数的实现,如malloc、free、realloc等,以及相关的数据结构和算法。 此外,还可以参考开源社区对ptmalloc2源代码的分析和解读,如一些博客文章、论文或代码注释等。这些资源通常提供了对源代码更深入的解释和讨论,对于理解ptmalloc2的实现细节会有所帮助。 总之,通过下载并分析glibc内存管理ptmalloc2源代码,可以帮助我们更好地理解和使用glibc的内存分配功能。同时,也可以通过分析源代码来提高我们的代码调试和性能优化能力,并为开发更高效的内存管理算法提供参考。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值