Lazy loaded image
操作系统
🌶️受限直接执行
Words 3429Read Time 9 min
2025-4-4
2025-4-8
type
status
date
slug
summary
tags
category
icon
password
类型
标签
状态
为了实现虚拟化,操作系统需要找到一种方式让许多任务来共享CPU,让它们看起来像是同时运行。即运行一个进程一段时间,然后运行另一个进程,如此轮换。通过这种方式时分共享(time sharing)CPU,就实现了虚拟化。
为了实现虚拟化,往往会有以下挑战:
  1. 性能:如何在不断增加系统开销的情况下实现虚拟化
  1. 控制权:如何有效地运行进程,同时保留对CPU的控制
总之,如何在保持控制权的同时获得高性能,这是构建操作系统的主要挑战之一。

受限直接执行(limited direct execution)

为了使程序尽可能快地运行,只需要直接在CPU上运行程序即可。当OS希望启动程序运行时,它会在进程列表中为其创建一个进程条目,为其分配一些内存,将程序代码从磁盘加载到内存中,找到入口点(main()函数等),跳转到那里,并开始运行用户的代码。
通过以下这个基本的直接执行协议,使用正常的调用并返回跳转到程序的main(),并在稍后回到内核。
notion image
但是这种受限制直接执行的方法在虚拟化CPU时会产生一些问题:
  1. 如果我们只运行一个程序,操作系统怎么能确保程序不做任何我们不希 望它做的事,同时仍然高效地运行它?
  1. 当我们运行一个进程时,操作系统如 何让它停下来并切换到另一个进程,从而实现虚拟化 CPU 所需的时分共享?
如果对运行程序没有任何的限制,那么操作系统将无法控制任何事,它仅仅是一个库。

受限制的操作

直接执行的明显优势就是快速。程序直接在CPU上运行,执行速度于预期的一样快。但是如果希望执行某种受限操作,如向磁盘I/O请求或获得更多系统资源,该怎么办?
如何执行受限操作? 一个进程必须能够执行 I/O 和其他一些受限制的操作,但又不能让进程完全控制系统。操作系统和硬件如何协作实现这一点?
💡
采用受保护的控制器转移
硬件通过提供不同的执行模式来协助操作系统。在用户模式(user mode)下,应用程序不能完全访问硬件资源。
在内核模式(kernel mode)下,操作系统可以访问机器的全部资源。还提供了陷入(trap)内核和从陷阱返回(return-from-trap)到用户模式程序的特别说明,以及一些指令,让操作系统告诉硬件陷阱表(trap table)在内存中的位置。
为了避免所有得到保护都失效,引入一种新的处理器模式,称为用户模式(user mode)。在用户模式下运行的代码会受到限制。而于用户模式不同的内核模式(kernel mode),操作系统就以这种模式运行。在此模式下,运行的代码可以做它喜欢的事,包括特权操作,如发出I/O请求和执行所有类似的受限指令。
如果用户希望执行某种特权操作,现代硬件也都提供了用户程序执行系统调用的能力。系统调用是在 Atlas [K+61,L78]等古老机器上开创的,它允许内核小心地向用户程序暴露某些关键功能,例如访问文件系统、创建和销毁进程、与其他进程通信,以及分配更多内存。
要执行系统调用,程序必须执行特殊的陷阱(trap)指令。该指令同时跳入内核并将特权级别提升到内核模式。一旦进入内核,系统就可以执行任何需要的特权操作(如果允许),从而为调用进程执行所需的工作。完成后,操作系统调用一个特殊的从陷阱返回(return-from-trap)指令,该指令返回到发起调用的用户程序中,同时将特权级别降低,回到用户模式。
为了让陷阱知道在OS运行哪些代码,但是发起调用的过程不能指定要跳转到的地址(让程序跳转到内核中的任意位置显然不可取),于是可以通过在启动时设置陷阱表来实现。当机器启动时,,它在特权(内核)模式下执行,因此可以根据需要自由配置机器硬件。操作系统做的第一件事,就是告诉硬件在发生某些异常事件时要运行哪些代码。操作系统通常通过某种特殊的指令,通知硬件这些陷阱处理程序的位置。一旦硬件被通知,它就会记住这些处理程序的位置,直到下一次重新启动机器, 并且硬件知道在发生系统调用和其他异常事件时要做什么(即跳转到哪段代码)。
下表总结了一份受限直接运行协议,假设每个进程都有一个内核栈,在进入和离开内核栈时,寄存器分别被保持和恢复。
notion image
这份LED协议分为两个阶段:
  1. 内核初始化陷阱,并且CPU记住它的位置以供随后使用。内核通过特权指令来执行此操作。
  1. 在使用从陷阱返回指令直线进程之前,内核设置一些内容。这会将CPU切换到用户模式并开始运行该进程。当进程希望发出系统调用时,它会重新陷入操作系统,然后再次通过从陷阱返回,将控制权还给进程。该进程然后完成它的工作,并从 main()返回。这通常会返回到一些存根代码,它将正确退出该程序

在进程之间切换

直接执行的下一个问题就是如何实现进程之间的切换。如果OS没有在CPU上运行,那么操作系统显然没有办法采取行动。
如何重获CPU的控制器 操作系统如何重新获得 CPU 的控制权(regain control),以便它可以在进程之间切换?

协助方式:等待系统调用

在这种方式下,操作系统相信系统的进程会合理运行。运行时间过长的进程被假定会定期放弃 CPU,以便操作系统可以决定运行其他任务。
💡
一个友好的进程如何放弃 CPU? 大多数进程通过进行系统调用,将 CPU 的控制权转移给操作系统,例如打开文件并随后读取文件,或者向另一台机器发送消息或创建新进程。像这样的系统通常包括一个显式的 yield 系统调用,它什么都不干,只是将控制权交给操作系统,以便系统可以运行其他进程。
如果应用程序执行了某些非法操作,也会将控制转移给操作系统。(如遭遇陷阱陷入操作系统时)。
即在协助操作系统中,OS通过等待系统调用,或某种非法操作发生,从而重新获得 CPU 的控制权。

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

没有硬件的额外帮助,如果进程拒绝进行系统调用,从而将控制权交给操作系统,那么OS将无法做任何事。在协作方式中,当进程陷入无限循环时,唯一的办法就是使用古老的解决方案来解决计算机系统中的所有问题——重新启动计算机。
如何在没有协助的情况下获得控制权 即使进程不协作,操作系统如何获得 CPU 的控制权?操作系统可以做什么来确保流氓进程不会占用机器?
于是就有了利用时钟中断(timer interrupt)重新获得控制权的方法,时钟设备可以编程为每隔几毫秒产生一次中断。产生中断时,当前正在运行的进程停止,操作系统中预先配置的中断处理程序(interrupt handler)会运行。此时,操作系统重新获得 CPU 的控制权,因此可以做它想做的事:停止当前进程,并启动另一个进程。
操作系统必须通知硬件哪些代码在发生时钟中断时运行。在启动时,操作系统必须启动时钟(特权操作),一旦时钟开始运行,OS就感到安全了,因为控制器最终会归还给它,因此操作系统可以自由运行用户程序。
硬件在发生中断时有一定的责任,尤其是在中断发生时,要为正在运行的程序保存足够的状态,以便随后从陷阱返回指令能够正确恢复正在运行的程序。这一组操作与硬件在显式系统调用陷入内核时的行为非常相似,其中各种寄存器因此被保存(进入内核栈),因此从陷阱返回指令可以容易地恢复。

保持和恢复上下文

当操作系统重新获得了控制权,都必须决定:继续运行当前正在运行的进程,还是切换到另一个进程。这个决定是由调度程序决定的,它是操作系统的一部分。
如果决定进行切换,OS 就会执行一些底层代码,即所谓的上下文切换(context switch):OS要做的激素为当前正在执行的进程保持一些寄存器的值,并为即将执行的进程恢复一些寄存器的值。这样一来,操作系统就可以确保最后执行从陷阱返回指令时,不是返回到之前运行的进程,而是继续执行另一个进程。
为了保存当前正在运行的进程的上下文,操作系统会执行一些底层汇编代码,保存通用寄存器、程序计数器,以及当前正在运行的进程的内核栈指针,然后恢复寄存器、程序计数器,并切换内核栈,供即将运行的进程使用。
通过切换栈,内核在进入切换代码调用时,是一个进程(被中断的进程)的上下文,在返回时,是另一进程(即将执行的进程)的上下文。当操作系统最终执行从陷阱返回指令时,即将执行的进程变成了当前运行的进程。至此上下文切换完成。
以下表为时钟中断控制的受限直接执行协议:
notion image
在此协议中,有两种类型的寄存器保护/恢复。第一种是发生时钟中断的时候。第二种是当操作系统决定从 A 切换到 B。

处理并发

操作系统可能简单地决定,在中断处理期间禁止中断(disable interrupt)。这样做可以确保在处理一个中断时,不会将其他中断交给 CPU。当然,操作系统这样做必须小心。
操作系统还开发了许多复杂的加锁(locking)方案,以保护对内部数据结构的并发访 问。这使得多个活动可以同时在内核中进行,特别适用于多处理器。

小结

为了实现受限直接执行,就让想运行的程序在 CPU 上运行,但首先确保设置好硬件,以便在没有操作系统帮助的情况下限制进程可以执行的操作。
通过类似的方式,OS 首先(在启动时)设置陷阱处理程序并启动时钟中断,然后仅在受限模式下运行进程,这样做,操作系统能确信进程可以高效运行,只在执行特权操作,或者当它们独占 CPU 时间过长并因此需要切换时,才需要操作系统干预。
 
上一篇
日志结构文件目录
下一篇
屏障