线程库的真相-TCB藏在用户态tid是地址join在等什么
线程库的真相TCB 藏在用户态、tid 是地址、join 在等什么8-12 ~ 8-15我的github(https://github.com/xcx55/ubuntu-linux-project)感谢各位大佬参观我的github上一篇说了轻量级进程的概念这一篇往下挖一层pthread 库到底在忙什么。核心结论先放这——Linux 内核确实没有 TCB但用户态的线程库里真有一个tid 是地址、返回值不走寄存器、线程栈开在共享区全是这个用户态 TCB带来的连锁设计。一、主线程与子线程不平等的根源8-12线程分主线程和子线程。主线程特殊——保留了进程特性一旦主线程 return整个进程死亡回收其余子线程全部陪葬明明执行流地位平等为什么主线程能决定一众线程的生死——历史原因线程概念提出之前main 所在的执行流就是进程本体后来加进来的线程只能寄居在它的进程下。要真正互不影响只有一条路创建子进程。子线程无论用什么函数哪怕是后面讲的分离都摆脱不了连累整个进程的宿命。二、pthread 库的真相内核没有 TCB库里有8-121. tid 为什么是一个地址pthread_create返回的 tid 是一个巨大的数字——因为它本质是一个地址库里线程控制块的地址。内核里的 PCB 没有线程专属结构所以线程的身份信息只能放在用户态的动态库pthread里内核里面确实没有 struct TCB但是库里面确实有工作方式PCB 对这个用户态结构体输入信息然后 PCB 回去继续干别的——线程的属性是否分离、回调入口、参数、返回值……全维护在库里。2. 线程栈的申请还是 mmappthread_create无论如何都要调clone创建前先申请空间内部调用 mmap——申请的就是三件套线程栈 线程控制块 线程局部存储塞进共享区。主线程的栈则由 exec 早就分配好了。3. 返回值为什么不走寄存器wait 的时候拿返回值习惯上我们觉得函数返回值在寄存器里——线程偏不线程返回值不靠寄存器靠库里面的一个变量存储*result汇编再把这块地址的值交给接收变量。原因很直白执行流一切换寄存器的值就可能被覆盖不安全。寄存器是临时工库里的变量才是档案柜。这和上次讲的用户态寄存器要保存在内核栈因为用户栈不可信是同一个安全哲学。4. 分离状态怎么看线程属性里有个巧妙的判断看tid 指针指向谁——指向别人未分离还等着被 join指向自己已分离退出即自动回收。5. 动态库的代码段里也有数据pthread 库本身作为动态库被映射进来它的代码由当前进程的执行流执行而库的数据段指全局变量和 static 数据——每个线程的 TCB、返回值槽位就长在这些数据结构里。Linux 线程的本质是用户态库里的结构体。三、线程控制join、detach 与不会收集信号信息8-121. pthread_join阻塞等待intpthread_join(pthread_ttid,void**retval);返回 0 等待成功失败返回错误比如tid 错误、没有这个线程返回 -1线程死亡必须被等待——不等待就类似进程的僵尸执行流没了代码和数据但内核 PCB 还挂着。2. 为什么 join 不收集退出码和信号对比 waitpid进程等待时能拿到退出码 信号位图两份信息pthread_join 只拿retval——线程出异常了怎么办推演一遍就通了子线程异常 进程异常 整个进程直接死异常信息交给bash去处理wait 那套——主线程根本没有机会去等一个异常的线程。所以主线程等待子线程不会搜集信号信息——因为信号异常一来进程就死了轮不到 join。这也解释了等待失败的两种情况①线程异常退出进程崩溃没机会等待②tid 错误返回 -1。3. pthread_detach不想等就分离主线程如果不关心返回结果阻塞的 join 就是负担。pthread_detach()把线程设为分离态线程退出时自动回收资源不需要也不能再 join。两个注意点一旦设置分离主线程退出前要慎重——主线程一死全家死所以实践中主线程常常是个甚至死循环的值守角色即使分离了线程异常照样连累整个进程——真正的分家只有 fork。4. exec 与 fork线程函数里能不能调exec——不行程序替换会把整个进程的地址空间换掉所有线程全部遭殃fork呢——可以fork 创建的是子进程与线程们平级分家。四、线程局部存储__thread8-12__threadintcount0;// 每个线程一份独立副本全局变量本来是所有线程共享的加上__threadGCC 的线程局部存储提示词之后每个线程拥有自己独立的一份。价值两条简化归属感——变量明确属于当前这个线程不会导致线程并发问题——各写各的副本天然无竞争。线程回调的局部状态、errno 这类每线程一份才合理的东西就靠它。C 的多线程在 Linux 上底层同样落到这套机制。五、线程和进程关系的最终版总结8-158-15 的两页短笔记把整个线程章收口本来线程应该单开一个结构体和基础结构区分但操作系统为复用代码没有更新内核——而是依据线程的特性在用户态共享区创建了线程的专属结构main 函数只是个历史问题——它本质上可以变为任意的函数只是编译器/链接器保存了这个约定。所以主线程的判定标准就是执行 main 的那个执行流进程主线程和线程都用 PCB——内核视角完全一视同仁给线程单独开存储技术上完全可以但不推荐、也懒得写代码——直接复用共享区的 mmap 映射。这就是 Linux 的工程美学能用现有机制解决的绝不新增机制。六、小结疑问答案内核有没有 TCB没有TCB 在用户态 pthread 库里tid 就是它的地址线程栈在哪子线程 mmap 申请、映射进共享区主线程栈由 exec 分配返回值放哪库里的变量*result不走寄存器切换不安全join 为什么不带信号信息子线程异常进程异常进程直接死信息归 bash 管join 等不到怎么判断分离tid 指针指向自己 已分离主线程 return等价 exit进程全灭想只退自己用 pthread_exit线程内能 exec 吗不能换掉整个地址空间fork 可以全局变量怕竞争__thread线程局部存储每线程一份副本一句话Linux 的线程是内核提供轻量级执行流clone 出的 PCB用户态库补全线程语义TCB、栈、局部存储、返回值的双层合作——内核复用到极致库负责体验。