写在前面:
学习操作系统是一个漫长且容易迷茫的过程。这本书在我的学习过程中给予了很大的帮助。本文将尽量精简内容,仅保留关键部分,并对学习中遇到的难点进行注释和解释。希望这能为初学者提供一些帮助和指引。
本文所有涉及的图片及内容皆引用自:Operating Systems: Three Easy Pieces
作者:
Remzi H. Arpaci-Dusseau and Andrea C. Arpaci-Dusseau (University of Wisconsin-Madison)
原版请访问官网:Operating Systems: Three Easy Pieces
4.1 The Abstraction: A Process
非正式地说,进程的定义非常简单:它是一个正在运行的程序。程序本身只是存储在磁盘上的一堆指令,操作系统将其字节加载并运行,让程序变得有用。通常,人们希望同时运行多个程序。而一个典型的系统可能同时运行上百个进程。这样就可以让用户不需要担心 CPU 是否可用,只需运行程序即可。
这里就抛出核心问题:物理 CPU 数量有限,但操作系统如何提供多 CPU 的假象?
答:操作系统通过虚拟化 CPU 来创造这种假象。它通过运行一个进程,然后暂停它并运行另一个进程,如此循环,来营造许多虚拟 CPU 同时存在的假象。这种基本技术称为 CPU 的时间共享。允许用户运行任意数量的并发进程。代价:多个进程共享 CPU 时,每个进程的运行速度会变慢。
Mechanisms and Policies机制与策略
为了实现并完善 CPU 的虚拟化,操作系统需要一些底层机制(machinery)和高层策略。机制是实现特定功能的底层方法或协议( low-level methods or protocols )。例如上下文切换, 其使操作系统能够暂停运行一个程序,并在给定的 CPU 上启动另一个程序。
机制之上是策略(Policies)。策略是操作系统做出某种决策的算法。例如,面对多个可在 CPU 上运行的程序,操作系统应该运行哪个?
Time Sharing and Space Sharing时间共享与空间共享
时间共享允许一个实体使用一段时间的资源,然后切换给另一个实体,如此循环,使得相关资源(如 CPU 或网络连接)能够被多个实体共享。其对应技术是空间共享,它将资源在空间上划分给需要使用的实体。例如,磁盘空间本质上是空间共享资源;一旦一个块被分配给某个文件,它通常不会再被分配给其他文件,直到用户删除原始文件。
Policy and Mechanism Separation 策略与机制分离
操作系统将高层策略与底层机制分离,这样可以只更改策略而不重新设计机制。一种通用的模块化的软件设计原则。
4.2 Process API 进程 API
- 创建进程(Create):shell 中输入命令/双击应用程序图标:操作系统会被调用以创建一个新进程来运行指定的程序。
- 销毁进程(Destroy):许多进程会在完成后自行退出;但如果没有退出,用户可用这个结果终止进程。
- 等待进程(Wait):等待一个进程停止运行
- 其他控制(Miscellaneous Control):例如挂起进程(暂时停止其运行)
- 状态(Status):获取进程状态信息。例如它已经运行了多长时间,或当前所处的状态。
4.3 Process Creation: A Little More Detail 进程创建:更详细的解析
程序是如何转变为进程的?
第一步:加载代码和静态数据
将程序代码和静态数据(例如已初始化的变量)加载到内存中,即进程的地址空间中。程序最初以某种可执行格式存储在磁盘上,然后操作系统从磁盘读取这些字节并将其放置到内存中的某个位置。早期操作系统在运行程序之前会一次性完成加载,而 现代操作系统只在程序执行过程中需要某段代码或数据时才加载它们(lazy loading)。
第二步:分配栈和堆
1.Allocate the Stack:为程序的运行时栈(简称栈)分配一些内存。C 程序使用栈存储局部变量、函数参数和返回地址。操作系统分配这些内存并将其提供给进程。
2.Initialize Stack Arguments:操作系统通常用参数初始化栈,具体是填充 main()
函数的参数,即 argc
和 argv
数组。
3.Allocate the Heap:堆用于显式请求的动态分配数据(通过 malloc()
和 free()
实现)。堆通常用于存储链表、哈希表、树等数据结构。初始堆很小;随着程序运行并请求更多内存,操作系统会分配更多空间以满足这些需求。
第三步:I/O 初始化
操作系统还会完成与输入/输出(I/O)相关的任务。例如,在 UNIX 系统中,每个进程默认有三个打开的文件描述符:标准输入 (stdin),标准输出 (stdout),标准错误 (stderr)。这些描述符让程序可以方便从终端读取输入并将输出打印到屏幕。
第四步:启动程序
从程序的入口点(即 main()
函数)启动程序运行。通过跳转到 main()
例程(routine),操作系统将 CPU 的控制权交给新创建的进程,从而程序开始执行。
4.4 Process States进程状态
接下来讨论进程在任意时刻可能处于的不同状态。
简化来看,进程可以处于三种状态之一:
- Running运行状态:进程正在处理器上运行。它正在执行指令。
- Ready就绪状态:进程已经准备好运行,但由于某种原因,操作系统选择在当前时刻不运行它。
- Blocked阻塞状态:进程执行了一些使得它在某些事件发生之前无法运行的操作。例:当进程向磁盘发起 I/O 请求时,它会进入阻塞状态,因此其他进程可以使用处理器。
注:什么是I/O 请求:当一个进程需要访问磁盘(例如读取文件或写入数据)时,它会向操作系统发起一个 I/O 请求。磁盘 I/O 的速度通常比 CPU 慢得多,需要一定的时间才能完成。在发起 I/O 请求后,进程会进入阻塞状态,意味着它暂时不能继续运行,必须等待 I/O 操作完成。此时,操作系统会将该进程从运行状态切换到等待状态,并把 CPU 的控制权释放出来。操作系统调度器会选择另一个处于就绪状态(Ready State)的进程来使用 CPU。这种机制确保了 CPU 不会因为某个进程等待 I/O 而空闲。
State Transitions状态转换
如果我们将上述状态映射到图形中,可以得到所示状态图:
- From Ready to Running:当操作系统调度一个进程时,它会从就绪状态变为运行状态。
- From Running to Ready:当进程被取消调度时,它会从运行状态返回到就绪状态。
- From Running to Blocked:当进程进入阻塞状态(例如发起 I/O 操作)后,操作系统会将其保持为阻塞状态,直到某个事件发生(例如 I/O 完成)。
- From Blocked to Ready:当阻塞事件完成时,进程会返回到就绪状态(如果操作系统决定运行它,也可能直接进入运行状态)。
示例:两个进程的状态转换
情况1:No I/O
两个进程正在运行,它们都只使用 CPU(没有 I/O 操作)。在这种情况下,每个进程的状态在运行和就绪之间交替变化。如下图:
情况2:With I/O
假设第一个进程在运行一段时间后发起了 I/O 请求。此时,第一个进程进入阻塞状态,第二个进程得以运行。如下图:
上图中,进程 0 发起 I/O 操作并进入阻塞状态,等待 I/O 完成。操作系统发现进程 0 没有使用 CPU,于是开始运行进程 1。当进程 1 运行时,进程 0 的 I/O 完成,进程 0 转为就绪状态。最后,进程 1 运行结束,进程 0 恢复运行直到完成执行。
即使在这个简单的示例中,操作系统也需要做出多个重要决策:
- 操作系统选择在进程 0 等待 I/O 时运行进程 1,从而提高了 CPU 的利用率。
- 操作系统决定在进程 0 的 I/O 完成后,不立即切换回进程 0。
决策调度器(scheduler)做出,在后续章节讨论。
4.5 Data Structures 数据结构
操作系统本质上是一个程序,与其他程序一样,它需要用一些关键的数据结构来追踪各种相关信息。例如,为了追踪每个进程的状态,操作系统通常会维护一个 进程列表,用于记录所有处于就绪状态的进程,并保存其他信息以追踪当前正在运行的进程。操作系统还需要以某种方式追踪阻塞的进程;当 I/O 事件完成时,操作系统应确保唤醒正确的进程并将其重新置于就绪状态。
下展示了操作系统在 xv6 内核 中需要追踪的每个进程的相关信息。类似的数据结构在实际操作系统中(如 Linux、macOS 或 Windows)也存在。
根据图中的描述,以下是操作系统追踪的几个关键组成部分:
1.Register Context:用于存储停止运行的进程的寄存器内容。当进程停止时,其寄存器内容会被保存到该内存位置。通过稍后恢复这些寄存器内容,操作系统可以继续运行该进程。这种机制被称为 上下文切换,后续章节详细讨论。
2.Process States:除了基本的运行、就绪和阻塞状态外,进程还可能处于:
- Initial State 初始状态:进程刚被创建时的状态。
- Final State 最终状态:进程已退出但尚未被清理的状态。(在UNIX类系统中称为zombie state僵尸状态)。该状态状态允许其他进程(通常是父进程)检查该进程的返回码。返回码用于指示进程是否成功执行。例如,在 UNIX 系统中,当程序成功完成任务时返回 0,否则返回非零值。进行运行完成后,父进程进行最后一次调用(例如
wait()
)来等待子进程的完成。这也表明操作系统可以清理与已终止进程相关的数据结构。
4.6 Summary 总结
理解进程是操作系统的基础。从它们的创建和管理,到状态之间的转换,进程体现了操作系统如何高效地虚拟化和调度任务。接下来是探讨上下文切换等机制和调度等策略。
ASIDE
附注1:The Process List进程列表
操作系统使用 进程列表(process list )(或 任务列表(task list))来追踪系统中所有运行的程序。它是操作系统中支持多任务运行的简单但重要的数据结构之一。任何能够同时运行多个程序的操作系统都需要这样的结构。进程列表中的每个条目称为 进程控制块(PCB) 或 进程描述符。本质上是一个 C 语言结构体,用于存储与进程相关的所有信息。
附注2:Key Process Terms关键进程术语
- 进程:进程是操作系统对运行中程序的主要抽象。在任意时刻,进程都可以通过以下状态来描述:地址空间中内存的内容。CPU 寄存器的内容(包括程序计数器、栈指针等)。I/O 的信息(例如可以读写的打开文件)。
- 进程 API :进程 API 包括程序可用的与进程相关的调用。包括创建,销毁,其他用于控制或状态检查的调用。
- 进程状态:运行,就绪,阻塞。不同的事件会导致进程从一种状态转换到另一种状态,例如,被调度或取消调度,或等待 I/O 完成。
- 进程列表:进程列表包含系统中所有进程的信息,其每个条目存储在一个被称为 进程控制块 (PCB) 的结构中。PCB 包含与特定进程相关的所有信息。