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

4.5.3 线程的调度——程序上下文

在Hello China V1.0的实现中,在程序上下文中完成线程调度,是通过调用ScheduleFromProc函数来完成的,在V1.5的实现中,继续保留了该函数,但对于该函数的实现,做了一些变更,下面是在V1.5中实现的ScheduleFromProc函数:

  static VOID ScheduleFromProc(__KERNEL_THREAD_CONTEXT* lpContext)
  {
    __KERNEL_THREAD_OBJECT* lpCurrent=NULL;
    __KERNEL_THREAD_OBJECT* lpNew   =NULL;
    DWORD                    dwFlags;
    __ENTER_CRITICAL_SECTION(NULL,dwFlags);
    lpCurrent=KernelThreadManager.lpCurrentKernelThread;
    switch(lpCurrent->dwThreadStatus)
    {
    case KERNEL_THREAD_STATUS_RUNNING:
    {
          lpNew=KernelThreadManager.GetScheduleKernelThread(
            (__COMMON_OBJECT*)&KernelThreadManager,
            lpCurrent->dwThreadPriority);  //Try to get a new one.
          if(NULL==lpNew)  //Current one is the most priority.
          {
            lpCurrent->dwTotalRunTime+=SYSTEM_TIME_SLICE;
            __LEAVE_CRITICAL_SECTION(NULL,dwFlags);
            return;  //Allow current thread to continue to run.
          }
    //If got a new kernel thread successfully,then should
          //schedule the new one to run.
          else
          {
            lpCurrent->dwThreadStatus=KERNEL_THREAD_STATUS_READY;
            KernelThreadManager.AddReadyKernelThread(
                (__COMMON_OBJECT*)&KernelThreadManager,
                lpCurrent);  //Add to ready queue.
            lpNew->dwThreadStatus=KERNEL_THREAD_STATUS_RUNNING;
            lpNew->dwTotalRunTime+=SYSTEM_TIME_SLICE;
            KernelThreadManager.lpCurrentKernelThread=lpNew;
            __SaveAndSwitch(&lpCurrent->lpKernelThreadContext,
                &lpNew->lpKernelThreadContext);
                          //Switch to new one.
            __LEAVE_CRITICAL_SECTION(NULL,dwFlags);
            return;
          }
    }
    case KERNEL_THREAD_STATUS_READY:
    {
          lpNew=KernelThreadManager.GetScheduleKernelThread(
            (__COMMON_OBJECT*)&KernelThreadManager,
            lpCurrent->dwThreadPriority);
          if(NULL==lpNew)  //Should not occur.
{
              BUG();
              __LEAVE_CRITICAL_SECTION(NULL,dwFlags);
              return;
}
if(lpNew==lpCurrent)  //The same one.
{
              lpCurrent->dwTotalRunTime+=SYSTEM_TIME_SLICE;
              lpCurrent->dwThreadStatus=KERNEL_THREAD_RUNNING;
              __LEAVE_CRITICAL_SECTION(NULL,dwFlags);
              return;  //Allow current continue to run.
}
else
{
              lpNew->dwThreadStatus=KERNEL_THREAD_STATUS_RUNNING;
              lpNew->dwTotalRunTime+=SYSTEM_TIME_SLICE;
              KernelThreadManager.lpCurrentKernelThread=lpNew;
              __SaveAndSwitch(&lpCurrent->lpKernelThreadContext,
              &lpNew->lpKernelThreadContext);
              __LEAVE_CRITICAL_SECTION(NULL,dwFlags);
              return;
}
}
case KERNEL_THREAD_STATUS_BLOCKED:
case KERNEL_THREAD_STATUS_SLEEPING:
case KERNEL_THREAD_STATUS_TERMINAL:
{
          lpNew=KernelThreadManager.GetScheduleKernelThread(
              (__COMMON_OBJECT*)&KernelThreadManager,
              0);  //Current thread must be swapped out.
          if(NULL==lpNew)  //Should not occur.
          {
    BUG();
    __LEAVE_CRITICAL_SECTION(NULL,dwFlags);
    return;
          }
          lpNew->dwThreadStatus=KERNEL_THREAD_STATUS_RUNNING;
          lpNew->dwTotalRunTime+=SYSTEM_TIME_SLICE;
          KernelThreadManager.lpCurrentKernelThread=lpNew;
          __SaveAndSwitch(&lpCurrent->lpKernelThreadContext,
    &lpNew->lpKernelThreadContext);
            __LEAVE_CRITICAL_SECTION(NULL,dwFlags);
            return;
    }
    default:  //Should not occur.
    {
          BUG();
          __LEAVE_CRITICAL_SECTION(NULL,dwFlags);
          return;
    }
    }
}

该函数可以在任何非中断上下文中被调用,用于完成核心线程的重调度。这样,该函数必须判断当前线程的状态,以确定进一步的动作。需要注意的是,当前线程的状态,不一定是RUNNING,而很多情况下,都是非RUNNING的“临时”状态,比如,当前线程等待一个共享对象,而该共享对象又是不可使用的,于是当前线程就需要把自己插入共享对象的等待队列,然后把状态设置为BLOCKED,并调用ScheduleFromProc重新调度,这样就出现了当前线程状态是BLOCKED状态的情况。

下列对各种临时状态进行解释,包括其发生的条件,以及采取的动作等。

● KERNEL_THREAD_STATUS_RUNNING

在当前核心线程调用WaitForThisObject等系统调用的时候,若试图等待的共享资源可用,则当前线程的状态不会被修改。但Hello China V1.5采用的是抢占式的调度方式,因此在任何系统调用中,会重新检查系统就绪队列,看是否存在比当前优先级更高的核心线程,即执行一个核心线程调度过程。

这种情况下,在调用ScheduleFromProc的时候,就会出现当前核心线程是RUNNING的情况。对于这种情况,ScheduleFromProc做如下处理:

(1)调用GetScheduleKernelThread函数,试图从就绪队列中选择一个可调度线程。在调用该函数的时候,会以当前核心线程的优先级作为参数,遮掩GetScheduleKernel Thread会返回比当前核心线程优先级更高的核心线程,若没有,则返回NULL。

(2)若返回NULL,说明当前就绪队列中没有核心线程比当前线程优先级更高,于是直接返回,这样可导致当前核心线程继续运行。

(3)若能够找到一个比当前核心线程优先级更高的线程,则把当前核心线程状态修改为READY,并放入就绪队列。然后增加刚刚获取的核心线程的运行时间片信息,修改其状态为KERNEL_THREAD_STATUS_RUNNING,并修改当前核心线程指针指向该线程,调用__SaveAndSwitch函数,切换到该线程。这样当前核心线程就会被打断,从而“让路”给更高优先级的核心线程。

这种调度方式,可确保任何比当前核心线程优先级高的线程,能够在最快的时间内得到调度,从而提升系统的整体实时性。

● KERNEL_THREAD_STATUS_READY

在操作系统刚刚完成初始化,还没有选择任何核心线程运行的时候,当前核心线程会被设置为这种状态。在系统初始化的过程中,会创建Shell、IDLE等系统核心线程。在初始化完成后,会把当前核心线程设置为创建的任何一个核心线程,不论设置为哪个核心线程,其状态都是KERNEL_THREAD_STATUS_READY。

系统初始化完成之后,会调用ScheduleFromProc函数,以切换到一个优先级最高的线程。实际上,系统初始化过程,是不属于任何核心线程的,但也可以看做是一个初始化核心线程。一旦初始化完成,切换到其他的核心线程,则这个“初始化核心线程”也就运行结束了。

这样初始化完成,调用ScheduleFromProc的时候,当前核心线程就是READY状态。针对这种状态,调度程序做如下处理:

①调用GetScheduleKernelThread函数,从就绪队列中提取一个核心线程。在调用该函数的时候,会以当前核心线程的优先级为参数,这样就约束了GetScheduleKernelThread函数,只能返回大于或等于当前核心线程优先级的就绪线程。

② 若GetScheduleKernelThread返回NULL,说明系统发生问题了。因为当前核心线程被创建的时候,一定是加入到就绪队列的,GetScheduleKernelThread函数至少应该返回当前核心线程。若返回NULL,则打印出调试信息(BUG()函数),并返回。

③ 若返回的核心线程对象,与当前核心线程是同一个,则说明当前核心线程就是系统中优先级最高的,于是增加当前核心线程的时间片计数,并修改其状态为RUNNING,直接返回。这样可导致当前核心线程继续执行。

④ 若返回的核心线程对象不是当前核心线程对象,则增加新核心线程的时间片计数,修改其状态,并切换到该线程开始执行。

● KERNEL_THREAD_STATUS_SUSPENDED

若当前核心线程对象的状态为KERNEL_THREAD_STATUS_SUSPENDED,则说明当前核心线程对象调用了SuspendKernelThread函数,试图挂起自己。SuspendKernelThread函数在把当前核心线程设置为SUSPENDED状态之后,会把当前核心线程插入挂起队列,并调用ScheduleFromProc函数,重新调度线程。

若当前核心线程处于该状态,则调度程序执行下列动作:

①调用GetScheduleKernelThread函数,试图从当前就绪队列中选择一个状态为就绪的核心线程。需要注意的是,这时候调用GetScheduleKernelThread函数,是以参数0作为第二个参数的,这样可导致该函数返回就绪队列中任何优先级大于或等于0的核心线程,即只要就绪队列中有核心线程对象存在,就会返回一个核心线程对象;

② 若上述函数返回NULL,说明系统出现了问题。因为就绪队列中肯定会有核心线程存在,至少有IDLE线程存在;

③ 若上述调用返回了一个合法的核心线程对象,则修改返回的核心线程状态信息,增加其运行时间片计数,调用__SaveAndSwitch函数,保存当前核心线程的上下文信息,并切换到新的核心线程开始运行。

● KERNEL_THREAD_STATUS_SLEEPING

当前核心线程调用Sleep函数,试图睡眠的时候,会发生当前核心线程状态是SLEEPING的情况。因为Sleep函数首先把当前核心线程设置为KERNEL_THREAD_STATUS_SLEEPING状态,并插入睡眠队列,然后调用ScheduleFromProc函数。对于这种状态的核心线程,ScheduleFromProc的处理机制,与当前核心线程状态为KERNEL_THREAD_STATUS_SUSPENDED的处理机制一样,请参考上面“KERNEL_THREAD_STATUS_SUSPENDED”一节获取详细信息。

● KERNEL_THREAD_STATUS_TERMINAL

线程运行结束的时候,会首先设置自己的状态为TERMINAL,并调用ScheduleFromProc函数。ScheduleFromProc函数对当前线程是该状态的处理动作,与上面“KERNEL_THREAD_STATUS_SUSPENDED”的处理机制一样,请参考上面一节获取详细信息。