在当前的编程语言家族中,多线程已成为编程语言的重要组成部分,无论编程语言是.NET,Java还是C++。要写出高响应度和可扩展的应用程序,你必须充分的发挥多线程编程的价值。在使用.NET框架时,我无意中发现了各种用于并行任务处理的框架类库,例如:任务并行类库(TPL),并行的LINQ(PLINQ),任务工厂,线程池,异步编程模式等,所有这些的背后都充分利用了Windows的线程来实现并行处理。理解Windows线程的基础结构可以帮助开发者更好的实施和理解TPL,PLINQ等这些高级特征。同时可以帮助开发者理解多线程在系统中是如何一起工作的,在定位多线程应用的问题时,这一功能是对开发者很有帮助的。在本文中,我将分享Windows线程的基础知识,这将有益于理解操作系统是如何实现多线程的。
其它翻译版本 (1) 加载中让我们一起看看线程的基本组成部分,Windows的线程由三个基本的组成部分:
线程内核对象
栈
TEB
Windows线程组成部分:
这三部分一起构成了Windows的线程。我将对三者逐一的解释,但在深入剖析三者之前我们先来了解一下Windows内核和内核对象,因为它们是Windows操作系统最重要的部分。
内核是任一操作系统的主要组件,它是应用程序和硬件之间的桥梁。内核提供了应用程序与硬件之间交互的抽象层。
内核是操作系统最先加载的部分,它驻留在物理内存中。内核的首要功能是管理计算机的硬件和资源,并允许其它程序运行和使用这些资源。要了解关于内核的更多资料,请访问此链接。
内核需要维护关于进行、线程、文件等各种资源的大量的数据。因此内核使用了“内核数据结构”,即众所周知的内核对象。每个内核对象都只是内核可以定位到的一块内存,并且只可以由内核访问。这块内存就是一种数据结构,这种数据结构的成员维护了内核对象的信息。在不同的内核类型中,这些成员(例如安全描述符,使用计数器等)是相同的,但是大部分的成员是特定于某类内核对象的。内核创建并使用多个不同类型的内核对象,例如进程对象,线程对象,事件对象,文件对象、文件映射对象,输入/输出完成端口对象,作业对象,互斥对象,管理对象,信号量对象等。
Winobj屏幕截图
如果你对于上图列表所示的内核对象类型感兴趣,你可以通过使用这的Sysinternals了解更多。
Windows 线程最基础的构件是线程内核对象。对于系统中的每个线程,操作系统都会为它们创建一个内线程内核对象。操作系统使用这些线程内核对象来管理和执行跨系统的线程。这些内核对象同时也是系统保存线程的全部统计信息的位置。下图所示是线程内核对象的重要属性。
每个线程内核对象都包含一套CPU的寄存器,称之为线程环境。线程环境体现了线程最近一次执行时CPU的寄存器的状态。线程的这一套CPU的寄存器保存在CONTEXT结构中。指令指针和栈指针寄存器是线程环境中两个最重要的寄存器。栈指针寄存器存储了在线程内当前执行的功能的栈结构的内存起始位置。指令指针寄存器标识了当前需要CPU执行的指令。操作系统在执行线程环境的切换时需要使用内核对象的环境信息。环境切换是保存和恢复线程环境状态,使得线程可以恢复到最近一次的状态,线程的执行可以重新的开始。
如下列表所示的是线程内核对象所持有的线程的重要信息。
属性 描述
创建时间 这个字段包含了线程的创建时间。
线程的进程 这个字段包含了指向持有这个线程的进程的进程结构。
栈基 这个字段是线程的栈的基本们置。
栈限额 这个字段是线程的内核模式栈的最大限额。
TEB 这个字段是线程的环境块的指针。
状态 这个字段是线程的当前状态。
优先级 这个字段是线程的当前优先级。
环境切换 这个字段是线程环境切换的计数器。
等待时间 这个字段是线程直到退休的等待时间。
队列 这个字段是线程的队列信息。
预占标识 这个字段是线程是否先占的标识。
亲和力 这个字段是线程内核的亲和力(类同)。
内核时间 这个字段是线程在内核模式的时间信息。
用户时间 这个字段是线程在用户模式的时间信息。
模拟信息 这个字段包含了一个指针,这个指针指向了线程在模拟其它线程时所用的结构。
暂停计数器 这个字段包含了线程暂停次数的计数器。
线程的第二个重要的组成部分是栈。一旦线程内核对象被创建,系统就会给它分配内存,这些内存用于线程的栈。每个线程用它自己的栈来维护函数的本地变量并在线程执行时传递参数给函数。当一个函数被执行时,它会在栈顶增加参数和本地变量等状态数据,当函数退出时又会相应的移除栈中的这些数据。除此以外,线程的栈还会用于存储函数调用的位置,这样就确保了执行return语句时返回到正确的位置。
操作系统给每个线程分配两种类型的栈,一种是用户模式的栈,另一种是内核模式的栈。
用户态堆栈用于局部变量和传递给方法的参数。它也包含了当前方法返回时线程应该继续执行的地址。默认情况下,Windows 为每个线程的用户态堆栈分配了 1 MB 内存。
内核态堆栈在应用程序代码传递参数给操作系统中的内核函数时被使用。 出于安全考虑,Windows 会将从用户态代码传递到内核的任意参数从线程的用户态堆栈复制到线程的内核态堆栈上。一旦复制完成,内核会校验这些参数值,并且由于应用程序代码无法访问内核态堆栈,应用程序也就无法在参数验证完成、操作系统内核代码开始操作它们之后修改参数值。此外,内核调用自身的方法,并且使用内核态堆栈来传递自己的参数、存储函数的局部变量,并且存储返回地址。内核态堆栈运行在 32位 Windows 操作系统下是 12 KB,运行在 64位 Windows 操作系统下是 24 KB。
你可以在下面关于线程堆栈的链接中学到更多:
每个线程都使用的重要的数据结构是线程环境块(TEB)。TEB是在用户模式下分配和初始化。(在用户模式下地址空间可以直接访问应用程序的代码,在内核模式下地址空间不能直接访问应用程序的代码。)TEB会消耗一页内存(当CPUs是x86和x64时是4KB)。
TEB包含的重要信息是用于微软结构化的异常处理的异常处理信息。TEB包含了线程异常处理链的头信息。线程进入每个try语句块时都会在这个链的头部插入一个结点。线程退出try语句块时结点的信息又会从链中移除。你可以访问此链接了解关于SEH的更多信息。
此外,TEB 包含了线程本地存储数据。在多线程的应用程序中,经常有维护线程专用数据的需要。这个线程专用数据存储的位置被称为线程本地存储。你从这里可以学到更多线程本地存储相关的知识。
下面提及的表格显示了 TEB 的几个重要数据:
属性名 | 描述 |
ThreadLocalStorage | 这个字段包含了线程专用数据。 |
ExceptionList | 这个字段包含了 SEH 所用的异常处理列表 |
ExceptionCode | 这个字段包含了最近一次线程产生的异常码。 |
LastErrorValue | 这个字段包含了最近一次线程产生的动态链接库错误值。 |
CountOwnedCriticalSections | 这个字段是线程拥有的临界区(一种同步机制)的个数。 |
IsImpersonating | 这个字段标记着线程是否正在进行身份模拟。 |
ImpersonationLocale | 这个字段包含了线程正在进行身份模拟的局部 ID。 |
2KB项目(www.2kb.com,源码交易平台),提供担保交易、源码交易、虚拟商品、在家创业、在线创业、任务交易、网站设计、软件设计、网络兼职、站长交易、域名交易、链接买卖、网站交易、广告买卖、站长培训、建站美工等服务