处理器执行模式+进程切换
大多数处理器至少支持两种执行模式。某些指令只能在特权模式下执行,包括读取或者改变诸如程序状态字之类控制寄存器的指令、原始IO指令和内存管理相关的指令。另外,有一部分内存区域仅在特权下可以被访问到。
非特权态常被称为用户态,这是因为用户程序通常在该模式下执行;特权态可称作系统态、控制态或者内核态,内核态指的是操作系统的内核,这是操作系统中包含重要系统功能的部分。
这样产生了两个问题:处理器如何知道它正在什么模式下执行以及如何改变这一模式。对第一个问题,程序状态字中有一个位表示执行模式,这一位应某些事件的要求而改变。在典型情况下,当用户调用一个操作系统服务或者中断触发系统例程的执行时,执行模式被设置成内核态,当从系统服务返回到用户进程时,执行模式被设置成内核态,当从系统服务返回到用户态是,执行模式被设置成为用户态。例如IA64的PSR,CPL级别0为特权级别。对于intel Itanium,应用程序使用系统调用是通过以下方式实现的:把系统调用标示符和参数放在一个预定义的区域,然后执行一个特殊的指令中断用户态程序的执行,并把控制权交给内核。
一旦操作系统决定创建一个新进程,它就可以按以下步骤进行:
1)给新进程分配一个唯一的进程标示符。此时,在主进程表中增加一个新表项,表中的每个新表项对应着一个进程。
2)给进程分配空间。这包括进程映像中的所有元素。因此,操作系统必须知道私有用户地址空间(程序和数据)和用户空间栈需要多少空间。可以根据进程的类型使用默认值,也可以在作业创建时根据用户请求设置。如果一个进程是由另一个进程创建的,则用户进程可以把所需的值作为进程创建请求的一部分传递给操作系统。如果任何现有的地址空间被这个新进程共享,则必需建立正确的连接。最后,必须给进程控制块分配空间。
3)初始化进程控制块。进程标识符部分包括进程ID和其他相关的ID,如父进程的ID等;处理器状态信息部分的大多数项目通常初始化成0,除了程序计数器(被置为程序入口点)和系统栈指针(用来定义进程栈边界);进程控制信息部分的初始化基于标准默认值和为该进程所请求的属性。例如,进程状态在典型的情况下被初始化成就绪/挂起;除非显式地请求更高的优先级,否则优先级的默认值为最低优先级;除非显式地请求或者从父进程继承,否则进程最初不拥有任何资源(IO设备、文件)。
4)设置正确的连接。例如,如果操作系统把每个调度队列都保存成链表,则新进程必须放在就绪或者就绪挂起链表中。
5)创建或者扩充其他数据结构。例如,操作系统可能为每个进程保存一个记账文件,可用于编制账单和进行性能评估。
进程切换
从表面看,进程切换的功能是很简单的。在某一时刻,一个正在进行的进程被中断,操作系统指定另一个进程为运行态,并把控制权交给这个进程。但是这会引发若干问题。首先,什么事件触发进程的切换?另一个问题是必须认识到模式切换和进程切换之间的区别。最后,为实现进程切换,操作系统必须对它控制的各种数据结构做些什么?
何时切换进程
进程切换可以在操作系统从当前正在运行的进程中获得控制权的任何时刻发生。首先考虑系统中断。实际上,大多数操作系统区分两种类型的系统中断。一种称为中断,另一种称为陷阱。前者与当前正在运行的进程无关的某种类型的外部事件有关,如完成一次IO操作;后者与当前正在运行的进程所产生的错误或者异常条件有关,如非法的文件访问。对于普通中断,控制首先转移给中断处理器,它做一些基本的辅助工作,然后转到与已经发生的特定类型的中断相关的操作系统例程。参见以下例子:
时钟中断:操作系统确定当前正在运行的进程的执行时间是否已经超过了最大允许的时间段(时间片,即进程在被中断前可以执行的最大时间段),如果超过了,进程必须切换到就绪态,调用另一个进程。
IO中断:操作系统确定是否发生了IO活动。如果IO活动是一个或者多个进程正在等待的事件,操作系统就把所有相应的阻塞态进程转换到就绪态,阻塞挂起转到就绪挂起,操作系统必须决定是继续执行当前处于运行态的进程,还是让具有高优先级的就绪态进程抢占这个进程。
内存失效:处理器访问一个虚地址,且此地址单元不在内存中时,操作系统必须从外存中把包含这个引用的内存块(段或页)调入内存中。在发出调入内存块的IO请求之后,操作系统可能会执行一个进程切换,以恢复另一个进程的执行,发生内存失效的进程被置为阻塞态,当想要的块调入内存中时,该进程被置为就绪态。
对于陷阱,操作系统确定错误或者异常条件是否是致命伤。如果是,当前正在运行的进程被转换到退出态,并发生进程切换;如果不是,操作系统的动作取决于错误的种类和操作系统的设计,其行为可以是试图恢复或通知用户,操作系统可能会进行一次进程切换或者继续执行当前正在运行的进程。
最后,操作系统可能会被来自正在执行的程序的系统调用激活。例如,一个用户进程正在运行并且正在执行一条请求IO操作的指令,如打开文件,这个操作导致转移到作为操作系统代码一部分的一个例程上执行。通常,使用系统调用会导致把用户进程置位阻塞态。
模式切换
如果存在一个未处理的中断,处理器需要做以下工作:
1)把程序计数器置成中断处理程序的开始地址。
2)从用户态切换到内核态,使得中断处理代码可以包含特权的指令。
处理器现在继续取指阶段,并取中断处理程序的第一条指令,他将给中断提供服务。此时,被中断的进程上下文保存在被中断程序的PCB中。
问题是,保存的上下文环境包括什么?答案是它必须包括所有中断处理可能改变的信息和恢复被中断程序时所需要的信息。因此,必须保存称作处理器状态信息的进程控制块部分,这包括程序计数器、其他处理器寄存器和栈信息。
还需要做其他工作吗?这取决于下一步会发生什么。中断处理程序通常是执行一些与中断相关的基本任务的小程序。例如,它重置表示出现中断的标志或指示器。可能给产生中断的实体如IO模块发送应答。它还做一些与产生中断的事件结果相关的基本辅助工作。例如,如果中断与IO事件有感,中断处理程序将检查错误条件;如果发生了错误,中断处理程序给最初请求IO操作的进程发一个信号。如果是时钟中断,处理程序将控制移交给调度器,当分配给当前正在运行的时间片用尽时,调度器将控制转移给另一个进程。
进程控制块中的其他信息如何处理?如果中断之后是切换到另一个应用程序,则需要做一些工作。但是,在大多数操作系统中,中断的发生并不是伴随着进程切换的。可能是中断处理执行之后,当前正在运行的进程继续执行。在这种情况下,所需要做的是当中断发生时保存处理器状态信息。,当控制权返回给这个程序时回复这些信息。在典型情况下,保存和恢复功能由硬件实现。
进程状态的变化
显然,模式切换与进程切换是不同的。发生模式切换可以不改变正处于运行态的进程状态,在这种情况下,保存上下文环境和以后恢复上下文环境只需要很少的开销。但是,如果当前正在运行的进程被切换到另一个状态(就绪、阻塞等),则操作系统必须使其环境产生实质性的变化,完整的进程切换步骤如下:
1)保存处理器上下文,包括程序计数器和其他寄存器。
2)更新当前处于运行态的进程的进程控制块,包括将进程的状态改变到另一状态(就绪态、阻塞态、就绪/挂起态或退出态)。还必须更新其他相关域,包括离开运行态的原因和记账信息。
3)将进程的进程控制块移到相应的队列(就绪、在事件i处阻塞、就绪/挂起)。
4)选择另一个进程执行。
5)更新所选择进程的进程控制块,包括将进程的状态转变为运行态。
6)更新内存管理的数据结构,这取决于如何管理地址转换。
7)恢复处理器在被选择的进程最后一次切换出运行状态的上下文环境,者可通过载入程序计数器和其他寄存器以前的值来实现。
进程切换涉及状态变化,因而比模块切换需要做更多的工作。