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

4.2.6 线程的创建

CreateKernelThread函数完成线程的创建功能,该函数执行下列动作。

① 调用CreateObject(ObjectManager提供)函数,创建一个核心线程对象;

② 初始化该核心线程对象;

③ 创建线程堆栈,并初始化堆栈;

④ 把核心线程对象插入就绪队列(初始状态为就绪)或挂起队列(初始状态为挂起)。

下面是该函数的实现代码,为了方便阅读,我们分段列举解释。

  static  __KERNEL_THREAD_OBJECT*  CreateKernelThread(__COMMON_OBJECT*
lpThis,
          DWORD                       dwStackSize,
          DWORD                       dwStatus,
          DWORD                       dwPriority,
          __KERNEL_THREAD_ROUTINE     lpStartRoutine,
          LPVOID                      lpRoutineParam,
          LPVOID                      lpReserved)
  {
    __KERNEL_THREAD_OBJECT*        lpKernelThread    =NULL;
    __KERNEL_THREAD_MANAGER*       lpMgr             =NULL;
    LPVOID                          lpStack           =NULL;
    BOOL                            bSuccess          =FALSE;
    if((NULL==lpThis)||(NULL==lpStartRoutine))   //Parameter check.
        goto__TERMINAL;
    if((KERNEL_THREAD_STATUS_READY !=dwStatus) && (KERNEL_THREAD_
STATUS_SUSPENDED !=dwStatus))
/*The initial status of a kernel thread should only be READY or SUSPENDED.
If the initial status is READY,then the kernel thread maybe scheduled to
run in the NEXT schedule circle(please note the kernel thread does not be
scheduled immediately),else,the kernel thread will be suspended,the kernel
thread in this status can be activated by ResumeKernelThread routine.*/
        goto__TERMINAL;

与注释描述的一样,上述代码主要是检查创建线程的初始状态(也可以认为是参数合法性检查),本函数只创建状态为READY(就绪)或SUSPENDED(挂起)的线程,所有以其他值调用该函数的尝试,都将会失败。

lpMgr=(__KERNEL_THREAD_MANAGER*)lpThis;
lpKernelThread=
    (__KERNEL_THREAD_OBJECT*)ObjectManager.CreateObject(&ObjectMana
ger,
                                  NULL,
                                  OBJECT_TYPE_KERNEL_THREAD);
if(NULL==lpKernelThread)   //If failed to create the kernel thread
object.
    goto __TERMINAL;
if(!lpKernelThread->Initialize((__COMMON_OBJECT*)lpKernelThread))
//Failed to initialize.
    goto __TERMINAL;

上述代码调用ObjectManager提供的CreateObject函数创建核心线程对象。在目前的实现中,对核心线程对象也归纳到ObjectManager的管理框架中,即系统中创建的任何核心线程对象都会被ObjectManager记录,这样便于管理。

if(0==dwStackSize)         //If the dwStackSize is zero,then allocate
                //the default size's stack.
    dwStackSize=DEFAULT_STACK_SIZE;
else
{
    if(dwStackSize < KMEM_MIN_ALOCATE_BLOCK)
                                  //If dwStackSize is too small.
        dwStackSize=KMEM_MIN_ALOCATE_BLOCK;
}
  lpStack=KMemAlloc(dwStackSize,KMEM_SIZE_TYPE_ANY);
  if(NULL==lpStack)   //Failed to create kernel thread stack.
    goto __TERMINAL;

上述代码完成线程堆栈的创建。线程的堆栈实际上就是一块物理内存,对于堆栈的大小(dwStackSize),用户可以根据需要自行指定(在CreateKernelThread函数调用中通过参数传递),也可以不指定。若用户不指定堆栈大小(dwStackSize参数设为0),则系统创建一个默认大小(DEFAULT_STACK_SIZE,目前定义为16KB)的堆栈,否则根据用户指定的大小创建。但若用户指定的堆栈尺寸太小(小于KMEM_MIN_ALLOCATE_BLOCK),则系统会采用KMEM_MIN_ALLOCATE_BLOCK代替用户指定的值。

若堆栈创建失败(内存申请失败),则CreateKernelThread函数会以失败告终。

  //The following code initializes the kernel thread object created just
now.
  lpKernelThread->dwThreadID       =lpKernelThread->dwObjectID;
  lpKernelThread->dwThreadStatus   =dwStatus;
  lpKernelThread->dwThreadPriority =dwPriority;
  lpKernelThread->dwScheduleCounter=dwPriority;
                                     //***** CAUTION!!! *****
  lpKernelThread->dwReturnValue    =0L;
  lpKernelThread->dwTotalRunTime   =0L;
  lpKernelThread->dwTotalMemSize   =0L;
  lpKernelThread->lpCurrentDirectory=NULL;
                                     //Maybe updated in the future.
  lpKernelThread->lpRootDirectory  =NULL;
  lpKernelThread->lpModuleDirectory=NULL;
  lpKernelThread->bUsedMath        =FALSE;
                                     //May be updated in the future.
  lpKernelThread->dwStackSize      =dwStackSize  ?  dwStackSize  :
DEFAULT_STACK_SIZE;
  lpKernelThread->lpInitStackPointer=  (LPVOID)((DWORD)lpStack+
dwStackSize);
  lpKernelThread->KernelThreadRoutine  =lpStartRoutine;
                              //Will be updated.
  lpKernelThread->lpRoutineParam       =lpRoutineParam;
  lpKernelThread->ucMsgQueueHeader     =0;
  lpKernelThread->ucMsgQueueTrial     =0;
  lpKernelThread->ucCurrentMsgNum     =0;
  lpKernelThread->dwLastError         =0L;
  lpKernelThread->lpParentKernelThread=NULL;     //Will be updated.

上述代码完成核心线程对象的部分初始化(有一些成员,需要进一步初始化),包括设置线程的ID、优先级、当前状态等,需要注意的是,对线程核心对象中lpInitStackPointer的设置,不是设置为堆栈的起始地址,而是设置为堆栈的终止地址,因为堆栈是从高地址到低地址增长的。

    //
    //The initializating value of Instruction Pointer Register is
KernelThreadWrapper,
    //this routine has a parameter,lpKernelThread,in order to pass this
parameter to the
    //routine,we must build the stack frame correctly.
    //The following code is used to build the kernel thread's stack
frame.
    //It first 'push' the lpKernelThread into the stack,and substract
8 from the stack
    //pointer's value to simulate a procedure call,then initializes the
context of the
    //kernel thread created just now by using INIT_KERNEL_THREAD_
CONTEXT_X macros.
    //
*(DWORD*)((DWORD)lpStack+dwStackSize-4)=(DWORD)lpKernelThread;
//"Push" the lpKernelThread into it's stack.
#ifdef __I386
    INIT_KERNEL_THREAD_CONTEXT_I(&(lpKernelThread->KernelThreadCont
ext),  //Initialize context.
        (DWORD)KernelThreadWrapper,
        ((DWORD)lpStack+dwStackSize-8))
                    //Please notice the 8 is necessary.
#endif

上述代码比较关键,完成线程堆栈的初始化,以及线程硬件上下文的初始化。对于一个创建的线程,其起始运行地址并不是CreateKernelThread函数中用户指定的函数地址(lpStartRoutine),而是由系统提供的一个通用函数KernelThreadWrapper的地址,在KernelThreadWrapper函数中,调用了用户提供的线程功能函数lpStartRoutine。因此,在初始化线程硬件上下文(INIT_KERNEL_THREAD_CONTEXT_I宏)的时候,对于EIP寄存器,是设置为KernelThreadWrapper函数的首地址的,这样一旦线程得到调度,将会从KernelThreadWrapper函数开始执行。对于线程硬件上下文初始化的详细过程,请参考本章中4.2.4节。

KernelThreadWrapper函数的原型如下。

  static VOID KernelThreadWrapper(__COMMON_OBJECT* lpKThread);

若线程一旦得到调度,将会开始执行该函数,而该函数却有一个线程核心对象指针(即刚创建的线程核心对象)为参数,如何把这个参数传递到该函数呢?显然,函数参数的获取是直接从堆栈中进行的,因此,为了让该函数能够访问lpKThread参数,我们必须把这个指针压入线程的堆栈,这就是上述代码中黑体部分的含义了。由于堆栈是从高往低增长的,因此必须把该函数压入线程堆栈所在内存的末尾。图4-6显示了这个过程。

图4-6 线程功能函数在堆栈中的起始位置

其中,lpStack指向了堆栈所在内存的基地址,而lpStack+dwStackSize则指向了堆栈所在内存的高地址,为了向堆栈中最后一个双字中写入lpKernelThread,必须以lpStack+dwStackSize-4为指针。

这样KernelThreadWrapper函数的参数被压入堆栈了。另外一个问题就是,新创建的线程一旦得到调用,其起始执行地址将直接跳转到KernelThreadWrapper函数开始执行,需要注意的是,这个过程不是函数调用,整个过程中没有CALL指令。而通常情况下,对于函数参数的访问是建立在函数调用基础上的,对于函数调用,CPU会执行如下的操作。

① 把CALL指令后的第一条指令(下一条执行指令)的地址压入堆栈;

② 跳转到目标函数。

这个过程会导致堆栈中被压入一个双字的元素,堆栈指针会变化。为了迎合这个过程,在初始化堆栈指针的时候,需要再减去4,这即是INIT_KERNEL_THREAD_CONTEX_I宏调用中第三个参数的含义了。减去8的理由是这样的。

(1)在堆栈中压入lpKernelThread,需要减去4;

(2)为迎合CALL指令压入一个返回地址的操作,虚拟一个PUSH动作,使堆栈再减去4。

因此,线程创建完毕后,其堆栈初始状态如图4-7所示。

图4-7 线程创建完毕后的堆栈状态

在这种堆栈框架下,线程一旦被调度执行,KernelThreadWrapper函数就可以访问到自己的参数(lpKernelThread)了。

需要注意的是,在堆栈中压入了一个“空”双字,不会导致异常的发生,因为这仅仅是为了迎合CALL指令的动作而完成的一个虚拟操作。实际上,KernelThreadWrapper函数永远不会返回,在KernelThreadWrapper函数的结尾处,已经把当前的核心线程对象从就绪队列中删除,这样该函数就不可能被再次调度,从而没有返回的机会,该“伪位置”就不可能被访问,详细信息,请参考本章4.2.7节。

    if(KERNEL_THREAD_STATUS_READY==dwStatus)
                          //Add into Ready Queue.
    {
if(!lpMgr->lpReadyQueue->InsertIntoQueue((__COMMON_OBJECT*)lpMgr->
lpReadyQueue,
            (__COMMON_OBJECT*)lpKernelThread,dwPriority))
            goto __TERMINAL;
    }
    else                                         //Add into Suspended
Queue.
    {
            if(!lpMgr->lpSuspendedQueue->InsertIntoQueue((__COMMON_
OBJECT*)lpMgr->lpSuspendedQueue,
          (__COMMON_OBJECT*)lpKernelThread,dwPriority))
          goto __TERMINAL;
    }
    bSuccess=TRUE;  //Now,the TRANSACTION of create a kernel thread
is successfully.
  __TERMINAL:
    … …//Deal with some errors.
        return lpKernelThread;
  }

上述代码根据创建的线程的状态(READY或SUSPENDED),把线程插入对应的队列。若线程的初始状态为READY,CreateKernelThread函数会把线程插入lpReadyQueue,这样一旦下一个调度时机(时钟中断或系统调用发生)到达,该线程就可能会被调度执行(根据线程的优先级确定)。若线程的初始状态为SUSPENDED,则该线程会被放入lpSuspendedQueue,除非该线程被手工恢复(ResumeKernelThread),否则不会被调度。