除非显式地声明,否则,你可以假设以下讨论的内容既适合用户模式的线程,也适用于内核模式的线程。
在系统级别上,Windows线程是由一个线程块执行体(ETHREAD)来表示的,如图6.7所示。ETHREAD块和它所指向的结构都位于系统地址空间中,唯一的例外是线程环境快(TEB),它位于进程地址空间中。而且,Windows子系统进程(Csrss)为Windows进程中创建的每个线程维护了一个平行结构。另外,对于那些调用了任何一个Windows子系统USER或GDI函数的线程,Windows子系统内核模式部分(Win32k.sys)为它维护了一个称为W32THREAD的数据结构,线程的ETHREAD块指向此结构。
图6.7中的大多数字段的意义已经很明了,不用多说了。第一个字段是内核线程块(KTHREAD)。紧随其后的是线程标识信息、进程标识信息(包括一个指向所有进程的指针,因而可以访问它的环境信息)、安全信息(包括一个指向访问令牌的指针以及身份模拟信息),最后是与LPC消息和待处理I/O请求有关的字段。关于ETHREAD块的信息,你可以使用内核调试器的lkd> dt nt!_ethread(对应进程的是_eprocess)命令来显示其数据结构。
现在让我们来看一看两个关键的线程数据结构:KTHREAD和TEB。KTHREAD块包含了Windows内核为这些正在运行的线程执行线程调度和同步而需要访问的信息。它的布局结构如图6.8所示。
关于KTHREAD块的信息,你可以使用内核调试器的lkd> dt nt!_kthread(对应进程的是_kprocess)命令来显示其数据结构。
图6.9所示的TEB是本节中介绍的唯一位于进程地址空间中的数据结构。
TEB存储了有关映像加载器和各种Windows DLL的环境信息。因为这些组件运行在用户模式下,所以它们需要一个在用户模式下可写的数据结构。为什么该结构位于进程地址空间中而不是位于系统地址空间中呢?因为系统空间只能在内核模式下才可写。通过内核调试器的lkd> dt nt!thread命令你可以看到TEB的地址,同样,你也可以用lkd> dt nt!_teb(对应进程的是_peb)命令来查看TEB的数据结构。
线程的上下文
线程的上下文本质上是一组处理器的寄存器,有正在执行程序中的指针及堆栈指针。上下文及其转换的过程根据处理器的结构不同会有所不同。我们可以调用内核调试器的lkd> dt nt!_context命令来观察上下文的数据结构。
一个典型的上下文转换需要保存和重载以下的数据:
- 程序计数器;
- 处理器状态寄存器;
- 其他寄存器的内容;
- 用户模式和内核模式的栈指针;
- 指向运行的线程的地址空间的指针,也就是进程的页表项。
内核将旧线程的这些信息保存到当前内核模式的属于旧线程的栈中,然后更新栈指针,再将
栈指针保存到旧线程的KTHREAD模块中。接着,内核栈指针就会指向新线程的内核栈,新线程的上下文将被装载。如果新的线程是属于不同的进程,系统将装载它的页表目录项到一个特殊的处理器寄存器中,让它的地址空间有效。如果有需要发送的内核APC被挂起,中断级别是1的中断产生。否则,控制权将交给新线程保存的程序计数器,线程重新执行。
说明:
本文摘自《Windows Internals》第6章《进程、线程和作业》6.1《线程的内部机理》