当前位置: 首页 > news >正文

Linux中NPTL线程库的线程ID、内存布局与独立上下文

目录

一、线程ID的生成与存储

1、内核线程标识(LWP)

2、pthread_create生成的线程ID

二、线程ID的获取

三、pthread_t类型的本质

1、Linux的线程实现背景

2、关于动态链接、动态库加载和地址重定位(回顾)

动态链接与动态库加载

动态链接的优势

3、线程库的动态库特性

四、进程地址空间与线程资源

1、线程栈

2、struct pthread

3、线程局部存储(TLS)

五、线程描述与内存管理

六、示例代码与结果分析

七、线程的内存布局和动态库加载

知识点

1、内核空间与用户空间

2、内存布局

3、动态库(pthread.so)

4、struct pthread

工作流程

1、线程创建(pthread_create)

2、线程维护

3、线程终止(pthread_join)

总结

八、独立上下文和独立栈

1、独立上下文

PCB(Process Control Block,进程控制块)

TCP(Thread Control Block,线程控制块,或在用户层由pthread库维护的结构)

2、独立栈

栈的作用

栈的来源

栈的管理

3、工作流程

线程创建

线程执行

线程终止

4、总结


一、线程ID的生成与存储

    pthread_create函数在创建新线程时,会产生一个线程ID,并将该ID存储在第一个参数所指向的内存地址中。需要特别注意的是,这里产生的线程ID与内核层面的线程标识(LWP,Light Weight Process)并非同一概念。

1、内核线程标识(LWP)

  • 在操作系统层面,线程作为轻量级进程,是调度器进行任务调度的最小单位。

  • 为了唯一标识和管理这些线程,内核需要为每个线程分配一个数值型的标识符,即LWP。

  • 这个标识符用于进程调度、资源分配等核心操作。

2、pthread_create生成的线程ID

  • 该线程ID属于NPTL(Native POSIX Threads Library)线程库的范畴。

  • pthread_create函数的第一个参数指向一个虚拟内存单元,这个内存单元的地址就被用作新创建线程的线程ID。

  • 线程库在后续的线程操作中,如线程的启动、终止、同步等,都是基于这个线程ID来进行的。


二、线程ID的获取

线程库NPTL提供了pthread_self函数,用于获取线程自身的ID。该函数的原型如下:

pthread_t pthread_self(void);
  • 调用pthread_self函数返回的线程ID,与通过pthread_create函数第一个参数获取的线程ID是相同的。

  • 这意味着,无论是创建线程时获取的ID,还是线程自身通过pthread_self获取的ID,都是用于标识同一个线程的。


三、pthread_t类型的本质

    pthread_t类型的具体定义取决于线程库的实现。在Linux系统目前广泛使用的NPTL线程库实现中,pthread_t类型的线程ID本质上是一个进程地址空间共享区上的虚拟地址。

1、Linux的线程实现背景

  • Linux内核本身并不提供真正的线程概念,而是提供了轻量级进程(LWP)作为调度的基本单位。

  • 这意味着,操作系统只需要对内核执行流LWP进行管理,而用户层面的线程接口、数据结构等则由线程库自行管理。

  • 这种“先描述,再组织”的管理方式在线程库内部实现。

2、关于动态链接、动态库加载和地址重定位(回顾)

        在Linux系统中,当一个可执行程序是动态链接的ELF(Executable and Linkable Format)文件时,它依赖于动态库(共享对象,如.so文件)来提供运行时所需的功能。以下是关于动态链接、动态库加载和地址重定位的详细解释:

动态链接与动态库加载

ELF可执行程序

  • ELF是一种通用的文件格式,用于可执行文件、目标代码、共享库和核心转储。

  • 动态链接的ELF可执行程序在编译时不包含所有需要的代码,而是依赖于运行时加载的共享库。

动态库(Shared Libraries)

  • 动态库(.so文件)包含可以被多个程序共享的代码和数据。

  • 使用动态库可以减少可执行文件的大小,并允许库的更新而不必重新编译依赖它的程序。

动态链接过程

  • 程序启动:当运行一个动态链接的可执行程序时,操作系统创建进程,并开始执行其入口点(通常是_start函数)。

  • 动态链接器介入:在程序启动的早期阶段,动态链接器(如ld-linux.so)被调用。

  • 加载动态库:动态链接器读取可执行文件的动态段(.dynamic),确定所需的共享库,并将这些库加载到内存中。

  • 地址重定位

    • 动态链接器执行地址重定位,将库中的符号地址解析为实际的内存地址。

    • 这一过程可能包括修正代码中的绝对地址引用,以适应库在内存中的实际加载位置。

内存映射

  • 加载的共享库被映射到进程的地址空间中,通常是在mmap区域或共享区。

  • 每个库可能被映射到不同的虚拟地址,但通过地址重定位,所有引用都能正确解析。

符号解析

  • 动态链接器解析可执行程序和共享库之间的符号引用。

  • 符号可以是函数或全局变量,动态链接器确保每个符号引用指向正确的定义。

初始化

  • 某些共享库可能需要执行初始化代码,动态链接器负责调用这些初始化例程。

动态链接的优势

  • 节省内存:多个进程可以共享同一个库的代码段,减少物理内存的使用。

  • 易于更新:共享库可以独立于可执行程序进行更新,只要接口保持不变,可执行程序无需重新编译。

  • 减少磁盘空间:由于库是共享的,不需要为每个可执行程序存储一份库的副本。

3、线程库的动态库特性

  • 通过ldd命令可以查看,我们使用的线程库实际上是一个动态库。

  • 在进程运行时,这个动态库会被加载到内存中,并通过页表映射到进程地址空间的共享区。

  • 这样,进程内的所有线程都可以访问这个动态库,从而使用线程库提供的各种功能。


四、进程地址空间与线程资源

1、线程栈

  • 每个线程都有自己私有的栈空间。

  • 其中,主线程使用的栈是进程地址空间中原生的栈,而其他通过pthread_create创建的线程,其栈空间则是在共享区中开辟的。

2、struct pthread

  • 每个线程都有自己的struct pthread结构体,这个结构体中包含了对应线程的各种属性,如线程状态、优先级、调度策略等。

3、线程局部存储(TLS)

  • 每个线程还有自己的线程局部存储区域,用于存储线程被切换时的上下文数据,如寄存器状态、栈指针等。

  • 这些数据在线程切换时会被保存和恢复,以确保线程能够正确继续执行。


五、线程描述与内存管理

  • 每一个新线程在共享区都有一块对应的内存区域,用于描述该线程的各种属性和状态。

  • 因此,要找到一个用户级线程,只需要找到该线程内存块的起始地址,就可以获取到该线程的各种信息。

  • 上面提到的各种线程函数,本质上都是在库内部对线程属性进行的各种操作,最后将要执行的代码交给对应的内核级LWP去执行。也就是说,线程数据的管理本质是在共享区的。


六、示例代码与结果分析

由于pthread_t在NPTL线程库中本质上是一个虚拟地址,我们可以通过打印这个地址来验证这一点。以下是一个简单的示例代码:

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>void* Routine(void* arg) {while (1) {printf("new thread tid: %p\n", pthread_self());sleep(1);}
}int main() {pthread_t tid;pthread_create(&tid, NULL, Routine, NULL);while (1) {printf("main thread tid: %p\n", pthread_self());sleep(2);}return 0;
}

        这个警告是关于格式字符串与参数类型不匹配的问题,但是我们这里直接就是想转化为地址的格式来观察,也就是16进制的格式,,所以这里的报错不用关心:

        运行上述代码后,我们会看到主线程和新创建的线程分别打印出自己的线程ID。从打印结果可以看出,这些线程ID看起来很像内存地址。

        而现在我们可以明确地说,在NPTL线程库的实现中,这些线程ID本质上就是进程地址空间共享区上的虚拟地址。由于同一个进程中的所有虚拟地址都是不同的,因此可以用这些地址来唯一区分每一个线程。


七、线程的内存布局和动态库加载

这两张图展示了在Linux系统中使用NPTL(Native POSIX Threads Library)实现线程的内存布局和动态库如何与线程结构交互的过程。

知识点

1、内核空间与用户空间

  • 内核空间是操作系统内核运行的空间,用户空间是应用程序运行的空间。

  • 线程的创建和管理在用户空间由线程库(如NPTL)处理,而调度和执行由内核处理。

2、内存布局

  • 主线程栈:每个线程都有自己独立的栈空间,主线程栈是进程启动时创建的。

  • mmap区域:用于动态内存映射的区域,可以用于共享内存或线程栈的分配。

  • :动态分配内存的区域,所有线程共享。

  • 代码段/数据段:存储程序代码和全局/静态变量。

3、动态库(pthread.so)

  • 提供线程相关的函数,如pthread_createpthread_join

  • 维护struct pthread,其中包含线程局部存储、线程栈等信息。

4、struct pthread

  • 包含线程的标识符(pthread_t tid)、线程局部存储和线程栈等信息。

  • 每个线程在动态库中都有一个对应的struct pthread

工作流程

1、线程创建(pthread_create)

  • 加载动态库:当程序启动时,pthread.so库被加载到内存中。

  • 库映射:库的代码和数据被映射到每个进程的地址空间,通常是在mmap动态映射区域或共享区。

  • 初始化线程结构:调用pthread_create时,动态库会在堆或共享区中分配一个struct pthread,并初始化其中的字段,包括线程ID、栈空间等。

  • 设置线程栈:为新线程分配栈空间,可能是在mmap区域。

  • 内核介入:线程库通过系统调用(如clone)请求内核创建一个新的执行上下文(LWP)。

2、线程维护

  • 线程局部存储:每个线程有自己的局部存储,用于保存线程特定的数据。

  • 线程栈管理:线程栈用于函数调用和局部变量存储,线程库确保每个线程有独立的栈空间。

  • 调度与执行:内核调度器负责线程的实际执行,线程库提供同步和通信机制(如互斥锁、条件变量)。

3、线程终止(pthread_join)

  • 线程执行完毕后,线程库负责回收资源,如释放栈空间和struct pthread

  • pthread_join用于等待特定线程终止,并获取其退出状态。

总结

  • 线程创建涉及用户空间的线程库和内核的协作,动态库负责管理线程的数据结构,而内核负责实际的调度和执行。

  • 内存布局确保每个线程有独立的执行环境,避免资源冲突。

  • 线程维护包括资源管理、同步和通信,确保线程高效、安全地运行。

这种设计使得线程的创建和管理高效且灵活,充分利用了用户空间和内核的优势。


八、独立上下文和独立栈

在多线程编程中,每个线程都有其独立的上下文和栈空间,这是实现并发执行的基础。以下是对独立上下文和独立栈的详细解释:

1、独立上下文

PCB(Process Control Block,进程控制块)

  • 内核层面:在操作系统内核中,每个线程(或轻量级进程,LWP)都有一个对应的PCB或类似的数据结构。PCB包含了线程的调度信息(如优先级、状态)、寄存器状态、内存映射信息等。

  • 作用:内核通过PCB来管理和调度线程,确保每个线程能够按照预期执行。

TCP(Thread Control Block,线程控制块,或在用户层由pthread库维护的结构)

  • 用户层面:在用户空间的线程库(如NPTL)中,每个线程也有一个对应的控制结构,通常称为struct pthread或类似的名称。这个结构包含了线程的用户级信息,如线程ID、线程局部存储、栈地址、错误码等。

  • 作用:线程库通过这个结构来管理线程的创建、销毁、同步等操作,而不必每次都与内核交互。

2、独立栈

栈的作用

  • 栈是线程执行函数时用来存储局部变量、函数参数、返回地址等信息的内存区域。

  • 每个线程都需要有自己独立的栈空间,以避免不同线程之间的数据干扰。

栈的来源

  • 进程原有的栈:主线程通常使用进程创建时分配的栈。

  • 动态申请的栈:对于新创建的线程,线程库(如通过pthread_create)会在堆或通过mmap系统调用动态申请一块内存区域作为新线程的栈。

栈的管理

  • 线程库负责为新线程分配和初始化栈空间。

  • 栈的大小可以在创建线程时指定,或者使用默认值。

  • 线程执行完毕后,线程库负责回收栈空间。

3、工作流程

线程创建

  • 当调用pthread_create时,线程库在用户空间分配一个struct pthread,并初始化其中的字段。

  • 线程库通过系统调用(如clone)请求内核创建一个新的执行上下文(LWP),并关联到新线程的struct pthread

  • 线程库为新线程分配栈空间,并设置栈指针等寄存器状态。

线程执行

  • 内核调度器负责选择哪个线程(LWP)来运行。

  • 当线程获得CPU时间片时,其上下文(包括寄存器状态、栈指针等)被加载到CPU中,开始执行线程的代码。

线程终止

  • 线程执行完毕后,线程库负责回收其栈空间和struct pthread等资源。

  • 如果调用了pthread_join,则等待线程终止的线程会继续执行,并可能获取终止线程的退出状态。

4、总结

        每个线程都有独立的上下文(包括内核层面的PCB和用户层面的struct pthread)和独立的栈空间。这种设计使得线程能够并发执行,而不会相互干扰。线程库负责管理线程的创建、销毁、同步等操作,并与内核协作实现高效的线程调度。

http://www.dtcms.com/a/546317.html

相关文章:

  • 做前端网站要注意哪些wordpress 只显示标题
  • PaddleOCR-VL:基于0.9B超轻量视觉-语言模型的高效多语言文档解析
  • 门户网站广告是什么网站设计风格有哪些
  • 网站建设系统规划南昌网站建设那家好
  • 一个专门做各种恐怖片的电影网站筛网怎么做网站
  • 网站seo诊断工具长沙便宜网站建设
  • JDBC快速入门
  • 国家2000(CGCS2000)是什么?
  • 以下哪些是付费推广方式seo作弊
  • Linux : I/O 模型
  • Rust——或模式(Or Patterns)的语法:Rust模式匹配的优雅演进
  • 教做3d的网站宁津哪个网络公司做网站比较好
  • 仓颉语言异常处理入门:从特性解读到实践落地
  • 通义DeepResearch技术报告解读
  • Java—代码块、内部类
  • 照片做视频模板下载网站旅游网站建设步骤
  • 狂人站群系统在线制作图谱
  • 婚礼策划网站模板中文中山做网站的
  • 专业建设网站多少钱淘客返利网站怎么做
  • 解决C++内存泄漏:Effective STL第7条的实践与智能指针的应用
  • 导入谷歌的zxing,实现二维码
  • 花生壳内网穿透网站如何做seo优化个人外贸公司网站
  • Unity jar更新不生效怎么解决
  • 边缘计算场景模型推理延迟的评估
  • 李沧做网站公司wordpress 导航不可点击
  • 淄博网站建设淄博深圳企业网站制作中心
  • 现在建设网站都用什么软件江门网站制作流程
  • 【第六章:项目实战之推荐/广告系统】3.精排算法-(3)精排模块多目标融合:从线性融合到Bayes方程融合原理
  • Linux小课堂: 磁盘容量配额配置与管理
  • SMT 卡扣贴片治具怎么设计,主要解决哪些问题