嵌入式操作系统
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

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 核心线程的生命周期