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”的处理机制一样,请参考上面一节获取详细信息。