操作系统机制之受限直接执行

受限直接执行(limited direct execution)是操作系统的关键底层机制之一,其目的就是让用户想运行的程序在CPU上运行之前,首先确保设置好硬件,以便在没有操作系统帮助的情况下限制进程可以执行的操作。

 

直接执行

操作系统会以时间片轮转的方式让多个进程共享CPU,来实现虚拟化。但是,在执行这个机制时存在一些问题。第一个是性能:如何在不增加系统开销的情况下实现进程间的切换?第二个是如何在运行其他进程的同时保留自身对CPU的控制权?控制权对于操作系统尤为重要,因为操作系统要负责资源的管理,如果没有控制权,那么也就没办法按照预想的策略控制多个进程,只需要一个进程可以简单地无限制运行并接管CPU。因此,在保持控制权的同时获得高性能,这是构建操作系统的主要挑战之一。

在这个挑战的前提下,受限直接执行(limited direct execution)就产生了。这个概念中的“直接执行”部分很简单:就是字面上的进程直接执行的意思。也就是当操作系统希望启动程序时,其会在进程列表中创建一个进程条目,为进程分配一些资源,将程序代码从磁盘加载到内存中,找到入口点(main()函数或类似的),跳转到那里,并开始运行用户的代码。

 

但是,单纯的直接执行程序会产生一些问题。一:如果只运行一个程序,该如何能确保程序不做任何操作系统不希望它做的事,例如不要访问到分配给它的以外的内存,并且同时仍然高效地运行它?二:当操作系统分配给一个进程的时间片耗光时,操作系统该如何将其中断并切换到另一个进程?

这两个问题的解决方案就是让进程直接运行时受限。

 

受限制的操作

操作系统要允许一个进程能够执行I/O和其他一些应该受限制的操作,但又不能让这个进程跳过操作系统可以完全的控制CPU。因此,开发者们为操作系统引入了一种新的处理器模式,称为用户模式(user mode)。在用户模式下运行的代码会受到限制。例如,在用户模式下运行时,进程不能执行某些受限制的指令,否则会导致处理器引发异常,操作系统可能会终止该进程。与用户模式相对应的是内核模式(kernel mode),在此模式下,运行的代码可以做它喜欢的事,包括一些特权操作,操作系统(或内核)就以这种模式运行。
这其中的问题是如果某一时刻进程希望执行某种被限制的操作(如从磁盘读取),该如何实现?为了实现这一点,几乎所有的现代硬件都提供了用户程序执行系统调用的能力。要执行系统调用,程序必须执行特殊的指令,可以成为陷阱(trap)指令。该指令将跳入内核并将特权级别提升到内核模式,之后系统就可以执行任何需要的特权操作(如果允许),从而为调用进程执行所需的工作。完成后,操作系统调用一个特殊的从陷阱返回(return-from-trap)指令,该指令返回到发起调用的用户程序中,同时将特权级别降低,回到用户模式。

硬件通过提供不同的执行模式来协助操作系统。在用户模式下,应用程序不能完全访问硬件资源。在内核模式下,操作系统可以访问机器的全部资源。还提供了陷入内核和从陷阱返回到用户模式程序的特别说明,以及一些指令,让操作系统告诉硬件陷阱表(trap table)在内存中的位置。执行陷阱时,硬件需要小心,因为它必须确保存储足够的调用者寄存器,以便在操作系统发出从陷阱返回指令时能够正确返回。例如,在x86上,处理器会将程序计数器、标志和其他一些寄存器推送到每个进程的内核栈(kernel stack)上。从返回陷阱将从栈弹出这些值,并恢复执行用户模式程序。

内核通过在启动时设置陷阱表(trap table)来实现。当机器启动时,它在特权(内核)模式下执行,因此可以根据需要自由配置机器硬件。操作系统做的第一件事,就是告诉硬件在发生某些异常事件时要运行哪些代码。例如,当发生硬盘中断,发生键盘中断或程序进行系统调用时,应该运行哪些代码?操作系统通常通过某种特殊的指令,通知硬件这些陷阱处理程序的位置。一旦硬件被通知,它就会记住这些处理程序的位置,直到下一次重新启动机器,并且硬件知道在发生系统调用和其他异常事件时要跳转到哪段代码。

LDE协议有两个阶段。第一个阶段(在系统引导时),内核初始化陷阱表,并且CPU记住它的位置以供随后使用。内核通过特权指令来执行此操作(所有特权指令均以粗体突出显示)。第二个阶段(运行进程时),在使用从陷阱返回指令开始执行进程之前,内核设置了一些内容(例如,在进程列表中分配一个节点,分配内存)。这会将CPU切换到用户模式并开始运行该进程。当进程希望发出系统调用时,它会重新陷入操作系统,然后再次通过从陷阱返回,将控制权还给进程。该进程然后完成它的工作,并从main()返回。这通常会返回到一些存根代码,它将正确退出该程序(例如,通过调用exit()系统调用,这将陷入OS中)。此时,OS清理干净,任务完成了。

 

进程间切换

下一个问题是,操作系统如何重新获得CPU的控制权,来达到切换进程的目的?

协作方式:等待系统调用

过去某些系统采用协作方式。在这种风格下,操作系统相信系统的进程会合理运行。运行时间过长的进程被假定会定期放弃CPU,以便操作系统可以决定运行其他任务。但是实际上,大多进程通过进行系统调用将CPU的控制权转交给操作系统;他们会在进行的某些操作的后续中,调价一个显式的调用,来讲操作权交还给系统。

还有进程执行了某些非法操作时,也会将控制权交还给系统。例如进程以0为除数,或者尝试访问系统分配给它的以外的内存。此时操作系统会重新获取CPU控制权,并有可能终止进程。

因此,协作调度系统中,操作系统通过等待程序调用或程序非法行为发生,来得到重新获取控制权的机会。然而,如果当进程陷入无限循环不会再进行系统调用的话,那么此时唯一的解决办法就是——重启计算机。

非协作方式:操作系统进行控制

对于协作方式可以看到,当进程不协作也就是不主动进行系统调用也不出错的情况下,操作系统是无法重新获得CPU的控制权的。那么这时操作系统该如何保证自己的地位?答案就是通过硬件的帮助。在许多年前构建计算机系统的人们发现了时钟中断(timer interrupt)。时钟设备可以编程为每隔几毫秒产生一次中断。产生中断时,当前正在运行的进程停止,操作系统中预先配置的中断处理程序(interrupt handler)运行。此时,操作系统重新获得CPU的控制权,就可以做它该做的事,例如停止当前进程,并启动另一个进程。

那么又得到了一个问题,操作系统在启动时必须通知硬件哪些代码在发生时钟中断时运行。在启动过程中,操作系统也必须启动时钟,这是一项特权操作。一旦时钟开始运行,操作系统就感到安全了,因为控制权最终会归还给它,因此操作系统可以自由运行用户程序。时钟也可以关闭(也是特权操作)。硬件在发生中断时有一定的责任,尤其是在中断发生时,要为正在运行的程序保存足够的状态,以便随后从陷阱返回指令能够正确恢复正在运行的程序。这一组操作与硬件在显式系统调用陷入内核时的行为非常相似,其中各种寄存器因此被保存(进入内核栈),因此从陷阱返回指令可以容易地恢复。

保存和恢复上下文

操作系统已经重新获得CPU控制权后,必须决定是继续运行当前正在运行的进程,还是切换到另一个进程。这个决定是由调度程序(scheduler)做出的,它是操作系统的一部分。如果决定进行切换,OS就会执行一些底层代码,即所谓的上下文切换(context switch)。上下文切换在概念上很简单:操作系统要做的就是为当前正在执行的进程保存一些寄存器的值,从而确保下一次切换到当前的进程时可以继续正确运行;当切换到下一个进程时,根据上一次切换时保存的进程寄存器有关值,来回复进程的执行指令。

为了保存当前正在运行的进程的上下文,操作系统会执行一些底层汇编代码,来保存通用寄存器、程序计数器,以及当前正在运行的进程的内核栈指针,然后恢复寄存器、程序计数器,并切换内核栈,供即将运行的进程使用。通过切换栈,内核在进入切换代码调用时,是一个进程(被中断的进程)的上下文,在返回时,是另一进程(即将执行的进程)的上下文。当操作系统最终执行从陷阱返回指令时,即将执行的进程变成了当前运行的进程。在这个过程中,有两种类型的寄存器保存/恢复。第一种是发生时钟中断的时候,此时运行进程的用户寄存器由硬件隐式保存,使用该进程的内核栈。第二种是当操作系统决定从A切换到B,内核寄存器将被操作系统明确地保存,但这次被存储在该进程的进程结构的内存中。后一个操作让系统从好像刚刚由A陷入内核,变成好像刚刚由B陷入内核。

当然,时钟中断期间是禁止中断的(disable interrupt),这样做可以确保在处理一个中断时,不会将其他中断交给CPU,当然,操作系统必须及其小心的这样做,因为禁用中断时间过长或过短都可能造成问题。

©️2020 CSDN 皮肤主题: 技术黑板 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值