附录:glibc-2.4 pthread 源码简要(了解)
目录
一、线程创建核心流程分析
1、入口函数:__pthread_create_2_1
2、核心数据结构
3、关键函数实现
二、线程创建的核心机制总结
三、关键问题与解决方案
四、代码注释与补充说明
一、线程创建核心流程分析
1、入口函数:__pthread_create_2_1
路径:nptl/pthread_create.c
函数签名:
int __pthread_create_2_1(pthread_t *newthread, const pthread_attr_t *attr,void *(*start_routine)(void *),void *arg
);
关键代码解析:
线程属性处理
const struct pthread_attr *iattr = (struct pthread_attr *)attr;
if (iattr == NULL)iattr = &default_attr; // 使用默认属性
- 若用户未指定属性,使用全局默认属性
default_attr。
线程控制块(TCB)分配
struct pthread *pd = NULL;
int err = ALLOCATE_STACK(iattr, &pd); // 分配栈空间并初始化TCB
通过宏ALLOCATE_STACK调用allocate_stack函数,完成以下操作:
-
申请内存(优先从缓存获取,失败则调用
mmap)。 -
初始化线程描述符(TCB)结构体
struct pthread。 -
设置栈保护区域(
guardsize)。
TCB关键字段初始化
pd->start_routine = start_routine; // 用户线程函数
pd->arg = arg; // 用户参数
pd->flags = ...; // 继承父线程调度属性
pd->joinid = (iattr->flags & ATTR_FLAG_DETACHSTATE) ? pd : NULL; // 分离状态标记
调度参数配置
if (attr != NULL && (iattr->flags & ATTR_FLAG_NOTINHERITSCHED)) {// 显式设置用户指定的调度策略和优先级pd->schedpolicy = iattr->schedpolicy;memcpy(&pd->schedparam, &iattr->schedparam, sizeof(struct sched_param));
}
线程创建与启动
err = create_thread(pd, iattr, STACK_VARIABLES_ARGS);
*newthread = (pthread_t)pd; // 返回TCB地址作为线程ID
2、核心数据结构
线程属性结构体 (struct pthread_attr)
struct pthread_attr {struct sched_param schedparam; // 调度参数int schedpolicy; // 调度策略(SCHED_OTHER/FIFO/RR)int flags; // 属性标志位(如分离状态)size_t guardsize; // 栈保护区域大小void *stackaddr; // 用户指定栈地址(可选)size_t stacksize; // 栈大小cpu_set_t *cpuset; // CPU亲和性设置
};
线程控制块 (struct pthread)
struct pthread {union {tcbhead_t header; // TCB头部(含TLS信息)void *__padding[16]; // 保留字段};list_t list; // 栈缓存链表节点pid_t tid; // 线程IDpid_t pid; // 进程IDvoid *(*start_routine)(void *);// 用户线程函数void *arg; // 用户参数void *result; // 线程返回值(供pthread_join使用)int cancelhandling; // 取消状态标志位struct pthread_key_data *specific[PTHREAD_KEY_1STLEVEL_SIZE]; // TLS数据// ... 其他字段(锁、事件、栈信息等)
};
3、关键函数实现
1. allocate_stack: 栈与TCB分配
路径:nptl/allocatestack.c
逻辑流程:
计算栈大小
size = attr->stacksize ?: __default_stacksize;
size &= ~__static_tls_align_m1; // 对齐调整
内存分配策略
-
优先从缓存获取:调用
get_cached_stack。 -
失败后动态分配:
mem = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); -
TCB位置计算:
pd = (struct pthread *)((char *)mem + size - TLS_TCB_SIZE) - 1;
保护区域设置
if (mprotect(guard, guardsize, PROT_NONE) != 0) {// 处理保护区域创建失败
}
2. create_thread: 线程启动
核心操作:
设置clone标志
int clone_flags = CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGNAL|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID;
调用do_clone
int res = do_clone(pd, attr, clone_flags, start_thread, STACK_VARIABLES_ARGS, stopped);
do_clone内部通过汇编宏ARCH_CLONE调用系统调用clone。
异常处理:若线程创建失败,释放资源:
__deallocate_stack(pd);
3. do_clone: 系统调用封装
汇编实现(x86_64示例):
ENTRY(__clone)/* 参数校验与栈调整 */movq %rcx, 8(%rsi) # 保存用户参数movq %rdi, 0(%rsi) # 保存线程函数地址/* 执行clone系统调用 */movl $SYS_ify(clone), %eaxsyscalltestq %rax, %raxjl SYSCALL_ERROR_LABELjz L(thread_start) # 子线程入口
二、线程创建的核心机制总结
-
内存管理
-
TCB与栈空间连续分配,TCB位于栈顶或栈底(依赖架构)。
-
默认栈大小通过
__default_stacksize定义(通常为2MB)。
-
-
系统调用集成
-
通过
clone创建轻量级进程,共享地址空间但独立调度。 -
使用
CLONE_SETTLS设置线程本地存储(TLS)。
-
-
扩展性设计:用户态调度器可行性:通过拦截
pthread_create和自定义队列,可在用户态实现调度策略(如协程库)。
三、关键问题与解决方案
-
NUMA架构优化
-
问题:默认属性可能导致远端内存访问。
-
建议:显式设置
stackaddr或使用mbind绑定内存节点。
-
-
栈溢出防护:通过
guardsize创建不可访问的保护页,触发SIGSEGV。 -
资源泄漏防护:分离线程(
detached)自动释放TCB,非分离线程需通过pthread_join回收。
四、代码注释与补充说明
-
ALLOCATE_STACK宏展开:参数stackaddr和stacksize用于回传栈信息。#define ALLOCATE_STACK(attr, pd) \allocate_stack(attr, pd, &stackaddr, &stacksize) -
TLS布局控制
-
TLS_TCB_AT_TP:TCB位于线程指针(TP)地址。 -
TLS_DTV_AT_TP:动态线程向量(DTV)位于TP地址。
-
-
性能优化细节:栈着色(Stack Coloring):通过
COLORING_INCREMENT避免多线程栈地址冲突。
通过以上分析可见,glibc的pthread实现通过分层设计(用户API→运行时库→系统调用)实现了高效的线程管理,同时保留了足够的扩展接口供用户态调度器实现。
