4.2.4 线程的上下文
线程的上下文(Context)是一个类型为__KERNEL_THREAD_CONTEXT的结构体,该结构体与特定的硬件平台(CPU)有强关联关系,不同的硬件平台,该结构体的定义不同。在本书中,我们重点关注Intel IA32构架的CPU。在这种硬件平台下,该结构体的定义如下。
BEGIN_DEFINE_OBJECT(__KERNEL_THREAD_CONTEXT) #ifdef __I386 DWORD dwEFlags; WORD wCS; WORD wReserved; DWORD dwEIP; DWORD dwEAX; DWORD dwEBX; DWORD dwECX; DWORD dwEDX; DWORD dwESI; DWORD dwEDI; DWORD dwEBP; DWORD dwESP; // //The following macro is used to initialize a kernel thread's executation //context. // #define INIT_EFLAGS_VALUE 512 #define INIT_KERNEL_THREAD_CONTEXT_I(lpContext,initEip,initEsp) \ (lpContext)->dwEFlags =INIT_EFLAGS_VALUE; \ (lpContext)->wCS =0x08; \ (lpContext)->wReserved =0x00; \ (lpContext)->dwEIP =initEip; \ (lpContext)->dwEAX =0x00000000; \ (lpContext)->dwEBX =0x00000000; \ (lpContext)->dwECX =0x00000000; \ (lpContext)->dwEDX =0x00000000; \ (lpContext)->dwESI =0x00000000; \ (lpContext)->dwEDI =0x00000000; \ (lpContext)->dwEBP =0x00000000; \ (lpContext)->dwESP =initEsp; // //In order to access the members of a context giving it's base address, //we define the following macros to make this job easy. // #define CONTEXT_OFFSET_EFLAGS 0x00 #define CONTEXT_OFFSET_CS 0x04 #define CONTEXT_OFFSET_EIP 0x08 #define CONTEXT_OFFSET_EAX 0x0C #define CONTEXT_OFFSET_EBX 0x10 #define CONTEXT_OFFSET_ECX 0x14 #define CONTEXT_OFFSET_EDX 0x18 #define CONTEXT_OFFSET_ESI 0x1C #define CONTEXT_OFFSET_EDI 0x20 #define CONTEXT_OFFSET_EBP 0x24 #define CONTEXT_OFFSET_ESP 0x28 #else #endif END_DEFINE_OBJECT()
该结构体的定义可以分为三部分。
(1)数据成员部分,与IA32硬件平台的相关寄存器相对应。
(2)初始化宏(INIT_KERNEL_THREAD_CONTEXT_I),这个宏用于初始化一个IA32构架下的上下文结构,其中第一个参数(lpContext)给出了要初始化的上下文结构的指针,initEip和initEsp是赋予dwEIP和dwESP(分别与EIP和ESP寄存器对应)的初始化值,上下文结构中dwEflags初始化为512,wCS初始化为8,所有其他变量初始化为0。这样初始化的含义后续会有详细介绍。
(3)成员访问偏移,为了访问一个上下文结构的成员变量(比如,dwESP等),采用上下文结构的指针(基地址)再加上特定的宏,这应用于嵌入式汇编语言的编程中。
表4-5给出了对应的IA32硬件平台的硬件寄存器与上述结构中成员的对应关系。
表4-5 核心线程各寄存器的初始化值
按照IA32的体系结构,EFLAGS寄存器的内容如图4-3所示。
图4-3 EFLAGS寄存器各比特的含义
每个核心线程初始化的时候,我们把EFLAGS(dwEflags)寄存器的值设置为512,也就是在上述比特中,只把IF(中断允许标记)标志设置为1,这样允许中断,其他所有标志均设置为0。
目前Hello China的实现没有引入进程的概念,整个操作系统只有一个地址空间,所有核心线程共享这个地址空间,因此,所有线程对应的CS寄存器值相同,都为8(代码段的索引值)。针对每个线程,操作系统都创建一个堆栈,该堆栈实际上是一块物理内存,在初始化的时候,lpESP指向了该物理内存的末端(不是首地址,因为堆栈是按照从上往下的方向增长的,但也不是严格的末地址,而是在末地址的基础上,再减去8,详细信息,请参考4.2.6节)。
而对于EIP寄存器的值,设置为线程起始函数的地址。需要注意的是,这个起始函数并不是线程工作函数,线程工作函数被线程起始函数调用来完成具体的工作,而在调用线程工作函数之前,线程起始函数还需要做一些其他工作,比如初始化核心线程对象等。下面是Hello China实现的线程起始函数的部分代码。
static VOID KernelThreadWrapper(__COMMON_OBJECT* lpKThread) { __KERNEL_THREAD_OBJECT* lpKernelThread =NULL; __KERNEL_THREAD_OBJECT* lpWaitingThread =NULL; __PRIORITY_QUEUE* lpWaitingQueue =NULL; __PRIORITY_QUEUE* lpReadyQueue =NULL; DWORD dwRetValue =0L; DWORD dwFlags =0L; if(NULL==lpKThread) //Parameter check. goto __TERMINAL; lpKernelThread=(__KERNEL_THREAD_OBJECT*)lpKThread; if(NULL==lpKernelThread->KernelThreadRoutine) //If the main routine is empty goto __TERMINAL; dwRetValue= lpKernelThread->KernelThreadRoutine(lpKernelThread->lpRoutineParam); //ENTER_CRITICAL_SECTION(); __ENTER_CRITICAL_SECTION(NULL,dwFlags); lpKernelThread->dwReturnValue=dwRetValue; //Set the return value of this thread. lpKernelThread->dwThreadStatus=KERNEL_THREAD_STATUS_TERMINAL; //Change the status. //LEAVE_CRITICAL_SECTION(); __LEAVE_CRITICAL_SECTION(NULL,dwFlags); … … … }
注意上述代码中的黑体部分,该部分实际上是调用了线程的功能函数(以用户提供的参数为参数,在应用程序调用CreateKernelThread的时候,CreateKernelThread创建一个核心线程对象,并把用户提供的线程功能函数和参数存储到该对象中,详细信息请参考4.2.6节)。需要注意的是,在从功能函数调用返回后,线程起始函数并没有马上返回,而是做了一些收尾处理,比如设置线程的返回值,设置线程核心对象的状态(设置为TERMINAL)等。
从上述代码中还可用看出,应用程序可以创建一个没有任何功能函数的“空线程”,因为在调用线程的功能函数前,线程起始函数先做了检查,若功能函数为空,则直接跳转到末尾,否则再调用功能函数。图4-4显示了线程起始函数和线程功能函数之间的关系。
图4-4 核心线程的生命周期