1.直接执行的下一个问题是在进程之间实现切换
直接执行的下一个问题是在进程之间实现切换。在进程之间切换应该很简单,对吗?操作系统应该决定停止一个进程,启动另一个进程。有什么大不了的?但是它实际上有点棘手:具体来说,如果一个进程在CPU上运行,那么根据定义,这意味着操作系统不会运行。如果操作系统没有运行,它怎么能做任何事情呢?(提示:它不能)虽然这听起来很有哲理,但这是一个真正的问题:如果操作系统没有在CPU上运行,操作系统显然没有办法采取行动。因此,我们得出了问题的症结所在。如何重新控制CPU ?操作系统如何重新控制CPU,以便在进程之间切换?
合作方式:等待系统调用。
一些系统在过去采用的一种方法(例如,Macintosh操作系统的早期版本[M11],或旧的施乐Alto系统[A79])被称为合作方法。
- 这种方式操作系统信任系统的进程,使其行为合理。运行时间过长的进程假定会周期性地放弃CPU,以便操作系统能够决定运行其他任务。因此,您可能会问,一个友好的过程是如何在这个乌托邦世界中放弃CPU的?大多数进程,如它所证明的那样,通过使系统调用(例如,打开一个文件并随后读取它)或向另一台机器发送消息,或创建一个新进程,来频繁地将CPU的控制转移到操作系统。像这样的系统通常包括一个显式的yield系统调用,它除了将控制转移到操作系统之外什么都不做,这样它就可以运行其他进程。应用程序也将控制转移到操作系统上,当他们做一些非法的事情时。例如,如果一个应用程序划分为零,或者试图访问它不应该访问的内存,它将会给操作系统生成一个陷阱。然后,操作系统将再次控制CPU(并可能终止违规的进程)。因此,在合作调度系统中,操作系统通过等待系统调用或某种类型的非法操作来重新控制CPU。你可能也在想:这种被动的方法是不是不够理想?例如,如果一个进程(不管是恶意的,还是充满bug的)在一个无限循环中结束,并且从来没有进行系统调用,会发生什么情况呢?那么操作系统能做什么呢?
-
- 不合作的方法:操作系统控制
如果没有硬件的额外帮助,当一个进程拒绝进行系统调用(或错误)并将控制权返回给操作系统时,操作系统就不能做很多事情。事实上,在合作的方法中,当进程陷入无限循环时,唯一的方法就是求助于解决计算机系统中所有问题的古老解决方案:重新启动机器。因此,我们再次来到了一个子问题,我们的总体任务是获得对CPU的控制。如何在不合作的情况下获得控制权?即使进程不合作,操作系统如何能控制CPU ?操作系统能做些什么来确保一个流氓程序不接管机器?
使用定时器中断重获控制。添加一个计时器中断使操作系统能够在CPU上再次运行,即使进程以不合作的方式运行。因此,这种硬件特性对于帮助操作系统维护机器的控制至关重要。答案其实很简单,很多年前就有很多人在计算机系统中发现了这个问题:计时器中断[M+63]。一个计时器装置可以被设定为每隔几毫秒就触发一次中断;在提出中断时,当前正在运行的进程被停止,操作系统中一个预先配置的中断处理程序运行。此时,操作系统已经重新控制了CPU,因此可以做它想做的事情:停止当前进程,启动另一个进程。正如我们在系统调用之前讨论过的,操作系统必须通知硬件,当计时器中断发生时,要运行哪些代码;因此,在引导时,操作系统正是这样做的。其次,在引导序列中,OS必须启动计时器,这当然是一个特权操作。一旦计时器开始,操作系统就会在控制中感到安全,最终会返回给它,这样操作系统就可以自由运行用户程序了。计时器也可以关闭(也是一个特权操作),当我们更详细地了解并发时,我们将讨论这个问题。请注意,当中断发生时,硬件有一些责任,特别是当中断发生时运行的程序的状态保存足够多,这样,随后的refromtrap指令将能够正确地恢复运行程序。这组操作与在一个显式的系统调用陷阱中对内核的行为非常类似,它使用不同的寄存器来保存(例如,在内核堆栈上),从而可以很容易地通过rereturn -from-trap指令恢复。
保存和恢复环境
现在操作系统已经重新获得了控制,无论是通过系统调用进行合作,还是通过计时器中断进行更有力的调用,都必须做出一个决定:是否继续运行当前运行的进程,或者切换到另一个进程。这个决策是由称为调度器的操作系统的一部分做出的;我们将在接下来的几章中详细讨论调度策略。如果决定切换,操作系统就会执行一个低级的代码段,我们称之为上下文切换。上下文切换在概念上很简单:所有操作系统都要做的是为当前执行的进程保存一些寄存器值(例如,在它的内核堆栈上),并为即将执行的进程(从内核堆栈中)恢复一些值。:通过这样做,操作系统就可以确保当重新设置陷阱时,指令最终被执行,而不是返回正在运行的进程,系统继续执行另一个进程的执行。
为了保存当前运行过程的上下文,操作系统将执行一些低级的汇编代码来保存通用寄存器、PC以及当前运行的进程的内核堆栈指针,然后恢复表示寄存器、PC,并切换到即将执行的进程的内核堆栈。通过切换堆栈,内核在一个进程(被中断的进程)的上下文中输入转换代码的调用,并在另一个进程的上下文中返回(即将执行的一个)。当操作系统最后执行一个返回-陷阱指令时,即将执行的流程将成为当前运行的流程。这样,上下文切换就完成了。整个流程的时间表如图6.3所示。在本例中,进程A正在运行,然后被计时器中断打断。硬件将寄存器保存(在内核堆栈上)并进入内核(切换到内核模式)。在定时器中断处理程序中,操作系统决定从运行进程A切换到进程B。在这一点上,它调用switch()例程,它仔细地保存当前寄存器值(进入A的流程结构),重新存储过程B的寄存器(从它的流程结构条目),然后切换上下文。特别是通过改变,堆栈指针使用B的内核堆栈(而不是s)。最后,操作系统的returnsfrom-trap,它恢复了B的寄存器并开始运行它。注意,在此协议中有两种类型的寄存器保存/恢复。注意,在此协议中有两种类型的寄存器保存/恢复。第一个是当计时器中断发生时;在这种情况下,使用该进程的内核堆栈,硬件将隐式地保存正在运行的进程的用户寄存器。第二个是操作系统决定从A切换到B;在这种情况下,内核寄存器是由软件显式地保存的(也就是。,但这一次进入内存的过程结构的过程。后一种操作将系统从运行中移动,就好像它只是被困在内核中一样。为了让您更好地了解这种转换是如何实现的,图6.4显示了xv6的上下文切换代码。看看您是否能理解它(您将需要了解一些x86,以及一些xv6)。新旧工艺流程结构中发现了新旧过程结构。
为了让您更好地了解这种转换是如何实现的,图6.4显示了xv6的上下文切换代码。看看您是否能理解它(您将需要了解一些x86,以及一些xv6)。新旧工艺流程结构中发现了新旧过程结构。你们中的一些人,作为细心体贴的读者,现在可能会想:嗯……在一个系统调用中,当一个计时器运行时,会发生什么?当你处理一个中断,另一个中断时会发生什么?在内核中这很难处理吗?好问题,我们真的对你有一些希望。答案是肯定的,操作系统确实需要关注在中断或陷阱处理过程中发生的另一个中断。事实上,这是这本书的第二部分,关于并发性的确切主题;我们将把详细的讨论推迟到那时候。了满足您的需求,我们将简要介绍操作系统如何处理这些棘手的情况。操作系统可能做的一件简单的事情是在中断处理过程中禁用中断;这样做可以确保在处理一个中断时,没有其他的中断被交付给CPU。当然,操作系统必须要小心;中断太长时间的中断会导致中断,这是(在技术方面)是坏的。操作系统还开发了许多复杂的锁定方案,以保护对内部数据结构的并发访问。这使多个活动能够同时在内核中运行,在多处理器上尤其有用。但是,正如我们在下一篇关于并发性的书中所看到的那样,这种锁定可能会很复杂,并导致各种有趣而难以发现的错误。
6.5总结
我们已经描述了实现CPU虚拟化的一些关键的底层机制,这是一组我们统称为有限直接执行的技术。基本的想法很简单:只要运行你想在CPU上运行的程序,但是首先要确保设置硬件,以限制在没有OS帮助的情况下进程可以做什么。
在现实生活中也有这种普遍的方法。例如,那些有孩子的人,或者至少听说过孩子的人,可能对婴儿打样的概念很熟悉:锁住装有危险物品的柜子和覆盖电源插座。当房间如此之大时,你可以让你的宝宝自由自在地漫游,让你知道房间里最危险的部分已经被限制了。在类似的方式中,OS婴儿对CPU进行验证,首先(在引导期间)设置陷阱处理程序并启动一个中断计时器,然后只在受限模式下运行进程。通过这样做,操作系统可以确信流程可以高效运行,只需要操作系统干预来执行特权操作,或者当它们占用CPU时间太长,因此需要关闭。因此,我们有了对CPU进行虚拟化的基本机制。但是一个主要的问题是没有回答的:我们应该在给定的时间运行哪个进程?这个问题是调度程序必须回答的问题,因此是我们研究的下一个主题。