4.2 Hello China V1.0版本的线程实现
与大多数嵌入式操作系统一样,Hello China实现了多任务、多线程的构架。但在Hello China的实现中,只引用了线程的概念,没有引用任务的概念,因为从本质上讲,任务就是一个线程,所不同的是,任务是一个无限循环,因此,实现线程比实现任务具有更广泛的适应性。
4.2.1 核心线程管理对象
在Hello China的实现中,一个全局对象KernelThreadManager(核心线程管理对象)用来完成对整个操作系统线程的管理,包括线程的组织、创建、销毁、修改优先级等操作,该对象的定义如下。
BEGIN_DEFINE_OBJECT(__KERNEL_THREAD_MANAGER) //DWORD dwCurrentIRQL; __KERNEL_THREAD_OBJECT* lpCurrentKernelThread; //Current kernel thread. __PRIORITY_QUEUE* lpRunningQueue; __PRIORITY_QUEUE* lpReadyQueue; __PRIORITY_QUEUE* lpSuspendedQueue; __PRIORITY_QUEUE* lpSleepingQueue; __PRIORITY_QUEUE* lpTerminalQueue; DWORD dwNextWakeupTick; BOOL (*Initialize)(__COMMON_OBJECT* lpThis); __KERNEL_THREAD_OBJECT* (*CreateKernelThread)( __COMMON_OBJECT* lpThis, DWORD dwStackSize, DWORD dwStatus, DWORD dwPriority, __KERNEL_THREAD_ROUTINE lpStartRoutine, LPVOID lpRoutineParam, LPVOID lpReserved); VOID (*DestroyKernelThread)(__COMMON_OBJECT* lpThis, __COMMON_OBJECT* lpKernelThread ); BOOL (*SuspendKernelThread)( __COMMON_OBJECT* lpThis, __COMMON_OBJECT* lpKernelThread ); BOOL (*ResumeKernelThread)( __COMMON_OBJECT* lpThis, __COMMON_OBJECT* lpKernelThread ); VOID (*ScheduleFromProc)( __KERNEL_THREAD_CONTEXT* lpContext ); VOID (*ScheduleFromInt)( __COMMON_OBJECT* lpThis, LPVOID lpESP ); DWORD (*SetThreadPriority)( __COMMON_OBJECT* lpKernelThread, DWORD dwNewPriority ); DWORD (*GetThreadPriority)( __COMMON_OBJECT* lpKernelThread ); DWORD (*TerminalKernelThread)( __COMMON_OBJECT* lpThis, __COMMON_OBJECT* lpKernelThread ); BOOL (*Sleep)( __COMMON_OBJECT* lpThis, //__COMMON_OBJECT* lpKernelThread, DWORD dwMilliSecond ); BOOL (*CancelSleep)( __COMMON_OBJECT* lpThis, __COMMON_OBJECT* lpKernelThread ); DWORD (*GetLastError)( __COMMON_OBJECT* lpKernelThread ); DWORD (*SetLastError)( __COMMON_OBJECT* lpKernelThread, DWORD dwNewError ); DWORD *GetThreadID)( __COMMON_OBJECT* lpKernelThread ); DWORD (*GetThreadStatus)( __COMMON_OBJECT* lpKernelThread ); DWORD (*SetThreadStatus)( __COMMON_OBJECT* lpKernelThread, DWORD dwStatus ); BOOL (*SendMessage)( __COMMON_OBJECT* lpKernelThread, __KERNEL_THREAD_MESSAGE* lpMsg ); BOOL (*GetMessage)( __COMMON_OBJECT* lpKernelThread, __KERNEL_THREAD_MESSAGE* lpMsg ); BOOL (*LockKernelThread)( __COMMON_OBJECT* lpThis, __COMMON_OBJECT* lpKernelThread); VOID (*UnlockKernelThread)( __COMMON_OBJECT* lpThis, __COMMON_OBJECT* lpKernelThread); END_DEFINE_OBJECT() //End of the kernel thread manager's definition.
这是一个比较大的对象,其中,五个优先级队列用于对系统中的线程进行管理,每个队列的用途如表4-1所示。
表4-1 Hello China的线程队列
注1:对于单CPU的情况,有且只有一个线程处于运行状态(即任何时刻,只有一个线程获得CPU资源,处于运行状态),这种情况下,该队列未被使用。但在多处理器情况下,任何一个时刻,有与系统中CPU数量相同的线程在运行,这样为了便于管理,设置此队列,用于管理多CPU情况下的运行态线程。
需要注意的是,还有一种线程状态——阻塞状态没有体现在上述队列中。因为线程的阻塞状态一般是因为该线程要请求一个共享资源,而该共享资源又不可用(被其他线程占有),这时候线程进入阻塞状态。进入阻塞状态的线程会被暂时存放在共享资源的阻塞队列中,因此没有必要专门设置一个全局队列来管理阻塞状态的线程。详细信息在介绍同步对象的时候会提到。
该对象还提供了大量的接口,用于完成对线程的操作。表4-2给出了操作动作和对应的操作函数。
表4-2 核心线程管理对象提供的接口
上述函数可以被应用程序直接调用,来完成对Hello China线程的操作。另外的几个函数,比如ScheduleFromProc、ScheduleFromInt等,用于操作系统核心完成线程切换的功能函数。这些函数被操作系统核心代码调用,一般不建议应用程序直接调用这些函数,但为了简便起见,把这些函数也纳入KernelThreadManager的管理范围。
另外,dwNextWakeupTick变量用于管理线程的睡眠功能。该变量记录了需要最早唤醒的线程和应该唤醒的时刻(tick数目)。比如,当前的时钟tick是1000,有三个线程,分别调用了Sleep函数,代码如下。
Thread1: … … Sleep(500); … … Thread2: … … Sleep(300); … … Thread3: … … Sleep(600); … …
并假设这三个线程调用Sleep函数的次序发生在同一个时间片之间,这样需要最早唤醒的线程应该是Thread2。于是,Sleep函数在执行的时候,把以毫秒(ms)为单位的参数,转换为时钟tick数,然后跟当前的系统tick数(System对象维护,详细信息请参考第8章)相加,并赋给dwNextWakeupTick变量。
这样每次时钟中断处理的时候,操作系统把dwNextWakeupTick跟当前的时钟tick数进行比较,若一致,则说明已经到达唤醒线程的时刻,于是从睡眠队列(lpSleepingQueue)中取出所有需要唤醒的线程,插入就绪队列(lpReadyQueue)。
另外需要注意的是,KernelThreadManager是一个全局对象,整个系统中只有一个这样的对象,因此,该对象没有从__COMMON_OBJECT对象派生。对于该对象提供的功能函数(比如CreateKernelThread等),为了保持面向对象的语义,其参数列表也与其他对象一样,第一个参数是lpThis(一个指向自己的指针),实际上,可以不用这个参数,而直接引用KernelThreadManager对象。