4.5.4 核心线程的创建和初始化
相对于Hello China V1.0,V1.5的实现中,CreateKernelThread函数也有所不同。V1.5的实现代码如下,其中黑体标注部分,是与V1.0的实现有显著差别的地方。为了便于描述,对每个黑体标注的差别部分,采用“//POSITION x”进行标注,以方便后面的描述:
static __KERNEL_THREAD_OBJECT* CreateKernelThread( __COMMON_OBJECT* lpThis, DWORD dwStackSize, DWORD dwStatus, DWORD dwPriority, __KERNEL_THREAD_ROUTINE lpStartRoutine, LPVOID lpRoutineParam, LPVOID lpReserved, //POSITION 1 LPSTR lpszName) { __KERNEL_THREAD_OBJECT* lpKernelThread =NULL; __KERNEL_THREAD_MANAGER* lpMgr =NULL; LPVOID lpStack =NULL; BOOL bSuccess =FALSE; DWORD* lpStackPtr =NULL; DWORD i; if((NULL==lpThis) || (NULL==lpStartRoutine)) goto __TERMINAL; if((KERNEL_THREAD_STATUS_READY !=dwStatus) && (KERNEL_THREAD_STATUS_SUSPENDED !=dwStatus)) goto __TERMINAL; lpMgr=(__KERNEL_THREAD_MANAGER*)lpThis; lpKernelThread = (__KERNEL_THREAD_OBJECT*)ObjectManager.CreateObject(&ObjectManager, NULL, OBJECT_TYPE_KERNEL_THREAD); if(NULL==lpKernelThread) goto __TERMINAL; if(!lpKernelThread->Initialize((__COMMON_OBJECT*)lpKernelThread)) goto __TERMINAL; if(0==dwStackSize) { dwStackSize=DEFAULT_STACK_SIZE; } else { if(dwStackSize < MIN_STACK_SIZE) { dwStackSize=MIN_STACK_SIZE; } } lpStack=KMemAlloc(dwStackSize,KMEM_SIZE_TYPE_ANY); if(NULL==lpStack) //Failed to create kernel thread stack. { goto __TERMINAL; } //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->bUsedMath =FALSE; lpKernelThread->dwStackSize =dwStackSize ? dwStackSize : DEFAULT_STACK_SIZE; lpKernelThread->lpInitStackPointer =(LPVOID)((DWORD) lpStack+dwStackSize); lpKernelThread->KernelThreadRoutine=lpStartRoutine; lpKernelThread->lpRoutineParam =lpRoutineParam; lpKernelThread->ucMsgQueueHeader =0; lpKernelThread->ucMsgQueueTrial =0; lpKernelThread->ucCurrentMsgNum =0; lpKernelThread->dwLastError =0L; lpKernelThread->dwWaitingStatus =OBJECT_WAIT_WAITING; //Copy kernel thread name. //POSITION 2 if(lpszName) { for(i=0;i < MAX_THREAD_NAME-1;i++) { if(lpszName[i]==0) //End. { break; } lpKernelThread->KernelThreadName[i]=lpszName[i]; } } lpKernelThread->KernelThreadName[i]=0; //Set string's terminator. // //The following routine initializes the hardware context //of the kernel thread. //It's implementation depends on the hardware platform,so //this routine is implemented in ARCH directory. // //POSITION 3 InitKernelThreadContext(lpKernelThread,KernelThreadWrapper); if(KERNEL_THREAD_STATUS_READY==dwStatus) { lpMgr->AddReadyKernelThread((__COMMON_OBJECT*)lpMgr, lpKernelThread); } else //Add into Suspended Queue. { if(!lpMgr->lpSuspendedQueue->InsertIntoQueue( (__COMMON_OBJECT*)lpMgr->lpSuspendedQueue, (__COMMON_OBJECT*)lpKernelThread,dwPriority)) goto __TERMINAL; } //Call the create hook. //POSITION 4 lpMgr->CallThreadHook(THREAD_HOOK_TYPE_CREATE,lpKernelThread, NULL); bSuccess=TRUE; __TERMINAL: if(!bSuccess) { //First,release the resources created successfully. if(NULL !=lpKernelThread) ObjectManager.DestroyObject(&ObjectManager, (__COMMON_OBJECT*) lpKernelThread); if(NULL !=lpStack) KMemFree(lpStack,KMEM_SIZE_TYPE_ANY,0L); return NULL; } else return lpKernelThread; }
与V1.0相比,主要有三个不同的地方:
(1)在V1.5的实现中,允许为核心线程指定一个名称(对应代码中POSITION1和POSITION2)。这样可对核心线程进行有意义的标识,在CPU统计等一些系统输出中,也会同时输出核心线程的名字和ID,这样使得每个核心线程的标识更加直观。当然,在V1.5的实现中,为核心线程对象的定义增加了一个字符串数组成员,用于存放核心线程的名字。
(2)对核心线程上下文的初始化(对应代码中POSITION3)。在V1.0的实现中,使用了一个宏INIT_KERNEL_THREAD_CONTEXT完成对核心线程上下文对象的初始化。而在V1.5的实现中,由于硬件上下文直接保存在堆栈中,因此在创建线程过程中,其硬件上下文的初始化方式需要改变。在V1.5的实现中,只需要建立合适的堆栈框架即可,这个堆栈框架,应该是符合__SwitchTo函数要求的,因为线程刚创建完毕后,并不会马上投入运行,而是插到就绪队列中,直到下一次调度时机(比如,中断或另外线程的系统调用)到来的时候,才会被调度。在V1.5的实现中,通过一个InitKernelThreadContext函数,完成了对核心线程上下文的初始化。详细的实现,请参考后文。
(3)调用了线程创建回调函数(对应代码中POSITION4)。线程回调函数是V1.5引入的一个机制,可在核心线程被创建、被销毁、被换出CPU、被调入CPU的四个时刻得到调用,从而完成一些系统级的任务。
InitKernelThreadContext函数初始化了一个核心线程的硬件上下文,该函数代码如下:
VOID InitKernelThreadContext(__KERNEL_THREAD_OBJECT* lpKernelThread, __KERNEL_THREAD_WRAPPER lpStartAddr) { DWORD* lpStackPtr=NULL; DWORD dwStackSize=0; if((NULL==lpKernelThread) || (NULL==lpStartAddr)) { return; } lpStackPtr=(DWORD*)lpKernelThread->lpInitStackPointer; __PUSH(lpStackPtr,lpKernelThread); //Push lpKernelThread to stack. __PUSH(lpStackPtr,NULL); //Push a new return address,simulate a call. __PUSH(lpStackPtr,INIT_EFLAGS_VALUE); //Push EFlags. __PUSH(lpStackPtr,0x00000008); //Push CS. __PUSH(lpStackPtr,lpStartAddr); //Push start address. __PUSH(lpStackPtr,0L); //Push eax. __PUSH(lpStackPtr,0L); __PUSH(lpStackPtr,0L); __PUSH(lpStackPtr,0L); __PUSH(lpStackPtr,0L); __PUSH(lpStackPtr,0L); __PUSH(lpStackPtr,0L); //Save context. lpKernelThread->lpKernelThreadContext= (__KERNEL_THREAD_CONTEXT*)lpStackPtr; return; }
其中,PUSH是预定义的一个宏,如下:
#define __PUSH(stackptr,val) \ do{ \ (DWORD*)(stackptr)-=1; \ *((DWORD*)stackptr)=(DWORD)(val); \ }while(0)
这个宏模拟了一个堆栈PUSH动作,首先把堆栈指针减去1(实际上是减去四个字节),然后把val存放在栈顶。
InitkernelThreadContext函数通过PUSH宏,建立了如图4-19所示的堆栈框架。
图4-19 新创建的核心线程的堆栈框架
其中,lpStartAddr就是Hello China提供的核心线程封装函数(KernelThreadWrapper)。堆栈框架建立完成之后,就把lpStackPtr赋值给当前核心线程对象的lpKernelThreadContext变量。待该核心线程得到调度的时候,通过lpKernelThreadContext变量,就可得到上述堆栈框架的栈顶指针,然后依次恢复通用寄存器,并执行iretd指令,就可跳转到lpStartAddr位置处(即KernelThreadWrapper函数处,这是所有核心线程的统一入口点)。
由于KernelThreadWrapper是一个函数,接受一个核心线程对象作为其参数,因此我们在开始的时候,压入了当前核心线程对象的指针和一个NULL值,以模拟一个CALL指令执行过程。