OSTEP Introduction
2023-1-18|2023-2-10
DieInADream
type
status
date
slug
summary
tags
category
icon
password
Property
Feb 10, 2023 09:12 AM
操作系统介绍
- 程序运行时会发生什么?一个正在运行的程序会做一件非常简单的事情:
- 执行指令。处理器从内存中获取(fetch)一条指令,对其进行解码(decode)(弄清楚这是哪条指令),然后执行(execute)它(做它应该做的事情,如两个数相加、访问内存、检查条件、跳转到函数等)。
- 完成这条指令后,处理器继续执行下一条指令,依此类推,直到程序最终完成
现代处理器在指令执行方面做了很多优化:一次执行多条指令,甚至乱序执行。此处只关心大多数程序所假设的简单模型:指令似乎按照有序的方式逐条执行。
- 实际上,有一类软件负责让程序运行变得容易(甚至允许你同时运行多个程序),允许程序共享内存,让程序能够与设备交互,以及其他类似的有趣的工作。这些软件称为操作系统(Operating System,OS),因为它们负责确保系统既易于使用又正确高效地运行。
- 要做到这一点,操作系统主要利用一种通用的技术,我们称之为虚拟化(virtualization)。也就是说,操作系统将物理(physical)资源(如处理器、内存或磁盘)转换为更通用、更强大且更易于使用的虚拟形式。因此,我们有时将操作系统称为虚拟机(virtual machine)。
- 当然,为了让用户可以告诉操作系统做什么,从而利用虚拟机的功能(如运行程序、分配内存或访问文件),操作系统还提供了一些接口(API),供你调用。实际上,典型的操作系统会提供几百个系统调用(system call),让应用程序调用。由于操作系统提供这些调用来运行程序、访问内存和设备,并进行其他相关操作,我们有时也会说操作系统为应用程序提供了一个标准库(standard library)。
- 最后,因为虚拟化让许多程序运行(从而共享 CPU),让许多程序可以同时访问自己的指令和数据(从而共享内存),让许多程序访问设备(从而共享磁盘等),所以操作系统有时被称为资源操理器(resource manager)。每个 CPU、内存和磁盘都是系统的资源(resource),因此操作系统扮演的主要角色就是管理(manage)这些资源,以做到高效或公平,或者实际上考虑其他许多可能的目标。为了更好地理解操作系统的角色,我们来看一些例子。
虚拟化 CPU
- cpu.c:我们的第一个程序。实际上,它没有太大的作用,它所做的只是调用 Spin() 函数,该函数会反复检查时间并在在运行一秒后返回。然后,它会打印出用户在命令行中传入的字符串,甚一直重复这样做。
- 注意:该程序将永远运行,只有按下“Control-c”(这在基于 UNIX 的系统上将终止在前台运行的程序),才能停止运行该程序。
- 现在,让我们做同样的事情,但这一次,让我们运行同一个程序的许多不同实例。
- ;使得我们可以同时运行多个程序
- & 使得程序后台运行,意味着用户能够立即发出下一个命令
- 尽管我们只有一个处理器,但这 4 个程序似乎在同时运行!这种魔法是如何发生的?
- 事实证明,在硬件的一些帮助下,操作系统负责提供这种假象(illusion),即系统拥有非常多的虚拟 CPU 的假象。将单个 CPU(或其中一小部分)转换为看似无限数量的 CPU,从而让许多程序看似同时运行,这就是所谓的虚拟化 CPU(virtualizing the CPU),这是本书第一大部分的关注点
- 当然,要运行程序甚停止它们,或告诉操作系统运行哪些程序,需要有一些接口(API),你可以利用它们将需求传达给操作系统。我们将在本书中讨论这些 API。事实上,它们是大多数用户与操作系统交互的主要方式。
- 一次运行多个程序的能力会引发各种新问题。例如,如果两个程序想要在特定时间运行,应该运行哪个?这个问题由操作系统的策略(policy)来回答。在操作系统的许多不同的地方采用了一些策略,来回答这类问题,所以我们将在学习操作系统实现的基本机制(mechanism)(例如一次运行多个程序的能力)时研究这些策略。因此,操作系统承担了资源操理器(resource manager)的角色。
虚拟化内存
- 现现机器提供的物理内存(physical memory)模型非常简单。内存就是一个字节数组。要读取(read)内存,必须指定一个地址(address),才能访问存储在那里的数据。要写入(write)或更新(update)内存,还必须指定要写入给定地址的数据。
- 程序运行时,一直要访问内存。程序将所有数据结构保存在内存中,甚通过各种指令来访问它们,例如加载 load 和保存 store,或利用其他明确的指令,在工作时访问内存。不要忘记,程序的每个指令都在内存中,因此每一读取指令都会访问内存。
- 输出结果:
- 每个正在运行的程序都在相同的地址(00200000)处分配了内存,但每个似乎都独立更新了 00200000 处的值!就好像每个正在运行的程序都有自己的私有内存,而不是与其他正在运行的程序共享相同的物理内存
- 实际上,这正是操作系统虚拟化内存(virtualizing memory)时发生的情况。每个进程访问自己的私有虚拟地址空间(virtual address space)(有时称为地址空间, address space),操作系统以某种方式映射到机器的物理内存上。一个正在运行的程序中的内存引用不会影响其他进程(或操作系统本身)的地址空间。 对于正在运行的程序,它完全拥有自己的物理内存。但实际情况是,物理内存是由操作系统管理的共享资源。所有这些是如何完成的,也是本书第 1 部分的主题,属于虚拟化(virtualization)的主题。
并发
- 并发问题首先出现在操作系统本身中。如你所见,在上面关于虚拟化的例子中,操作系统同时处理很多事情,首先运行一个进程,然后再运行一个进程,等等。事实证明,这样做会导致一些深刻而有趣的问题。
- 并发问题不再局限于操作系统本身。事实上,现现多线程(multi-threaded)程序也存在相同的问题。我们来看一个多线程程序的例子
- 下面是运行这个程序、将变量 loops 的输入值设置为 1000 时的输出结果
- 也就是说,当 loops 的输入值设为 N 时,我们预计程序的最终输出为 2N。但事实证明,事情并不是那么简单。让我们运行相同的程序,但 loops 的值更高,然后看看会发生什么:
- 事实证明,这些奇怪的、不寻常的结果与指令如何执行有关,指令每一执行一条。遗憾的是,上面的程序中的关键部分是增加共享计数器的地方,它需要 3 条指令:一条将计数器的值从内存加载到寄存器,一条将其递增,另一条将其保存回内存。因为这 3 条指令并不是以原子方式(atomically)执行(所有的指令一次性执行)的,所以奇怪的事情可能会发生。关于这种甚发(concurrency)问题,我们将在本书的第 2 部分中详细讨论
持久性
- 在系统内存中,数据容易丢失,因为像 DRAM 这样的设备以易失(volatile)的方式存储数值。如果断电或系统崩溃,那么内存中 的所有数据都会丢失。因此,我们需要硬件和软件来持久地(persistently)存储数据。这样的存储对于所有系统都很重要,因为用户非常关心他们的数据。
- 硬件以某种输入/输出(Input/Output,I/O)设备的形式出现。在现代系统中,硬盘驱动器(hard drive)是存储长期保存的信息的通用存储库,尽操固态硬盘(Solid-State Drive, SSD)正在这个领域取得领先地位
- 操作系统中操理磁盘的软件通常称为文件系统(file system)。因此它负责以可靠和高效的方式,将用户创建的任何文件(file)存储在系统的磁盘上
- 不像操作系统为 CPU 和内存提供的抽象,操作系统不会为每个应用程序创建专用的虚拟磁盘。相反,它假设用户经常需要共享(share)文件中的信息。例如,在编写 C 程序时, 你可能首先使用编辑器(例如 Emacs①)来创建和编辑 C 文件(emacs -nw main.c)。之后, 你可以使用编译器将源现码转换为可执行文件(例如,gcc -o main main.c)。再之后你可以运行新的可执行文件(例如 ./main)。因此,你可以看到文件如何在不同的进程之间共享。 首先,Emacs 创建一个文件,作为编译器的输入。编译器使用该输入文件创建一个新的可执行文件(可选一门编译器课程来了解细节)。最后,运行新的可执行文件。这样一个新的程序就诞生了!
- 示例代码
- 为了完成这个任务,该程序向操作系统发出 3 个调用。第一个是对 open()的调用,它打开文件并创建它。第二个是 write(),将一些数据写入文件。第三个是 close(),只是简单地关闭文 件,从而表明程序不会再向它写入更多的数据。这些系统调用(system call)被转到称为文件系统(file system)的操作系统部分,然后该系统处理这些请求,甚向用户返回某种错误代码。
- 你可能想知道操作系统为了实际写入磁盘而做了什么。我们会告诉你,但你必须答应 先闭上眼睛。这是不愉快的。文件系统必须做很多工作:首先确定新数据将驻留在磁盘上 的哪个位置,然后在文件系统所维护的各种结构中对其进行记录。这样做需要向底层存储 设备发出 I/O 请求,以读取现有结构或更新(写入)它们。所有写过设备驱动程序①(device driver) 的人都知道,让设备代表你执行某项操作是一个复杂而详细的过程。它需要深入了解低级 别设备接口及其确切的语义。幸运的是,操作系统提供了一种通过系统调用来访问设备的 标准和简单的方法。因此,OS 有时被视为标准库(standard library)。
- 当然,关于如何访问设备、文件系统如何在所述设备上持久地操理数据,还有更多细节。出于性能方面的原因,大多数文件系统首先会延迟这些写操作一段时间,希望将其批量分组为较大的组。为了处理写入期间系统崩溃的问题,大多数文件系统都包含某种复杂的写入协 议,如日志(journaling)或写时复制(copy-on-write),仔细排序写入磁盘的操作,以确保如果 在写入序列期间发生故障,系统可以在之后恢复到合理的状态。为了使不同的通用操作更高效, 文件系统采用了许多不同的数据结构和访问方法,从简单的列表到复杂的 B 树。
设计目标
- 操作系统实际上做了什么:它取得 CPU、内存或磁盘等物理资源 (resources),甚对它们进行虚拟化(virtualize)。它处理与甚发(concurrency)有关的麻烦 且棘手的问题。它持久地(persistently)存储文件,从而使它们长期安全。
- 鉴于我们希望建 立这样一个系统,所以要有一些目标,以帮助我们集中设计和实现,甚在必要时进行折中。 找到合适的折中是建立系统的关键。
- 最基本的目标:建立抽象
- 其他目标:高性能、隔离性、可靠性、安全性、能源效率
历史
- 早期操作系统:只是一些库(一组常用函数库)
- 超越库:保护(意识到代表操作系统运行的代码是特殊的,它控制了设备,因 此对待它的方式应该与对待正常应用程序现码的方式不同)
- 系统调用 vs 过程调用
- 系统调用将控制转移(跳转)到 OS 中,同 时提高硬件特权级别(hardware privilege level)。
- 用户应用程序以所谓的用户模式(user mode)运行,这意味着硬件限制了应用程序的功能
- 在发起系统调用 时 [通常通过一个称为陷阱(trap)的特殊硬件指令],硬件将控制转移到预先指定的陷阱处理程序(trap handler)(即预先设置的操作系统),甚同时将特权级别提升到内核模式(kernel mode)
- 在内核模式下,操作系统可以完全访问系统的硬件,因此可以执行诸如发起 I/O 请 求或为程序提供更多内存等功能。当操作系统完成请求的服务时,它通过特殊的陷阱返回 (return-from-trap)指令将控制权交还给用户,该指令返回到用户模式,同时将控制权交还 给应用程序,回到应用离开的地方。
- 多道程序时代 - UNIX 操作系统
- 操作系统不是一一只运行一项作业,而是将大量作业加载到内存中甚在它们之间快速切换, 从而提高 CPU 利用率
- 内存保护(memory protection)
- 甚发(concurrency)
- 摩登时代 - MacOS/Linux/Windows
- PC 的出现
- Utterance