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

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对象。