CS162操作系统课程第二课-4个核心OS概念

2022-06-02  本文已影响0人  干货满满张哈希

熟肉视频地址:

[图片上传失败...(image-e70087-1654131471474)]

我们讨论了操作系统如何扮演裁判魔术师粘合剂的角色,裁判是指对于资源保护的管理;魔术师是指我们要让它看起来像我们有一套非常干净易用的资源的抽象,而不是使用实际的没有统一接口的物理资源。粘合剂是一组通用服务,它们使在操作系统上编写程序变得更容易,例如文件系统服务、网络服务等等。

今天我们要讲操作系统四个基本概念:
[图片上传失败...(image-a3a035-1654131471474)]
我们将从什么是线程(Thread)开始讨论,线程会完整地描述一个程序状态或者一个线性执行上下文,它会有一个程序计数器,寄存器,执行标志,线程栈等等。然后是地址空间(Address Space),这是程序可访问的一组内存地址。然后我们会介绍什么是进程(Process),进程包括地址空间以及一个或者多个线程。最后,我们将讨论在这门课的前期特别重要的硬件机制也就是双模式操作(dual mode operation),即一个典型的处理器至少有两种不同的模式,我们可以松散地称之为内核模式(Kernel mode)和用户模式(User mode)。
[图片上传失败...(image-e2e94f-1654131471474)]
我们要学习如何编写和编译程序成为可执行文件,然后从文件系统中取出这些可执行文件后,建立运行的进程,文件被加载到内存中,我们会详细讨论那个进程运行需要提供的栈和堆。然后是转移控制(Transfer Control),意思是处理器的程序计数器将会指向该进程的用户代码中的指令,然后程序就会开始执行。操作系统需要给这些进程提供各种系统服务,同时操作系统需要保护操作系统和进程不受其他进程和其他用户的影响。
[图片上传失败...(image-9c035f-1654131471474)]
我们来回忆下 CS61B + CS61C 课程的内容: 处理器一开始有一个程序计数器(PC,Program Counter),还有一个可以读取的内存(Memory),内存里面有一组指令(Instruction)。程序计数器会指向内存中的指令,让处理器读取下一条指令。我们把指令从内存中拉出来,解码,然后把它输入到执行流水线中。我们在 CS61C 中经常谈论的是一种风险类型的处理器(risk style processor),有五个执行流水线。解码后,它们会输入一组寄存器和 ALU 来进行实际操作,然后递增程序计数器并继续下一条指令。

[图片上传失败...(image-e599b7-1654131471474)]
我们的第一个操作系统概念是一个控制线程(Thread of Control),线程实际上是一个唯一的执行上下文,它有程序计数器,寄存器,执行标志,线程栈,内存状态。线程就像上一张幻灯片中你看到的处理器的虚拟版本。一个线程正在处理器或核心上执行,顺便说一下,我现在混用处理器和核心这两个概念,后面我们就能更清楚地理解这两个概念的区别。但它是在处理器寄存器中常驻(Resident)时才会执行的。寄存器中有线程的上下文(Context)或根状态(Root State),有些东西在寄存器中,剩下的在内存中:

这是另一个关于执行过程的视图(冯·诺依曼体系)
[图片上传失败...(image-93df21-1654131471474)]
这是一组地址,我们稍后会把它叫做地址空间,从 0 到 2^32 -1。里面有一组将要执行的指令,粉色的是你的处理器。进程有一个与之相关的保护状态,处理器包括寄存器和流水线的集合,我们的执行序列(Execution Sequence)包括:在程序计数器上获取一条指令,解码它,执行它,写回寄存器,获取下一条指令,并重复这个执行循环。

那么我们是如何产生多处理器的错觉的呢?我们上次讲过在你的笔记本电脑上做一个ps -aux或者其他的操作唤起任务管理器,如果你仔细看,你会发现有数百个进程在运行,大部分在休眠状态。但它们都可以在你当前的处理器上使用,那么它是如何工作的呢?
[图片上传失败...(image-bf555d-1654131471474)]
让我们假设只有一个物理处理器上只有一个核,在任何给定的时间在硬件上只有一个执行线程。但现在我们想要的是有多个cpu,或者说是多个cpu同时运行的假象,所以我们可以有多个线程同时运行。从程序员的视图来看是,我有一堆东西都在运行,它们都共享内存。
每个线程是一个虚拟核心,我们这里有三个线程,品红色线程,青色线程以及黄色线程,造成他们同时运行的错觉的方式是通过我们多重复用硬件,我们运行一段时间的品红色线程,然后运行一会青色线程,然后运行一会黄色线程,以此循环。
线程的内容包括什么?显然,每个线程都需要一个程序计数器和一个堆指针,还有所有寄存器状态。如果线程在运行中,寄存器状态就在处理器的寄存器中;如果不在运行中,这些状态就在内存中,被称为线程控制块(TCB,Thread Control Block)

我们举一个例子:
[图片上传失败...(image-87880f-1654131471474)]
在 T1 的时候 vCPU1 在运行,在 T2 的时候 vCPU2 在运行,在 T1 和 T2 时,发生了上下文切换(Context Switch),在底层是一个事件,我们把所有的程序计数器,栈指针,所有其他寄存器都保存在内存中的对应线程控制块中。我们从 vcpu2 加载程序计数器、堆栈指针等,然后运行 vcpu2。

一些 Q&A:

然后是什么触发了上下文切换?全局倒计时器时间到了,或自愿让出CPU等这样的事情触发的。比如某个线程要做一些 I/O,那么操作系统就会调度其他线程。

[图片上传失败...(image-6212f8-1654131471474)]

我们可能有一堆内存(蓝色的代表内存),我们可以想象这些虚拟进程中的每一线程都有自己的栈、堆、数据和代码,它们都以某种方式分布在内存中,我们要做的就是以某种方式记录所有东西的位置。线程控制块是所有东西的所在,当我们从绿色切换到黄色,我们要做的第一件事是将所有的绿色线程的寄存器保存到它的线程控制块中,顺便说一下,那是内核内存的一部分。

我们今天要讲的第二个操作系统概念是地址空间
[图片上传失败...(image-9c0504-1654131471474)]
它是一组可访问地址和与之相关的状态,这只是处理器对可用地址的视图。对于 32 位处理器,可以访问的地址空间是 2 的 32 次方大约是 40 亿,对于 64 位的处理器,2 的 64 次方是 18 万亿。但是这并不是说所有的空间里都有对应的物理 DRAM 内存可以使用,只有一部分有对应的物理 DRAM。
当你读或写一个地址空间中的地址时,也许它像普通内存一样运行,或者完全忽略写入操作;或者可能是系统导致一个I/O操作发生,这被称为内存映射 I/O;或者它可能会导致异常,如果你试图在我这里展示的栈和堆之间的某个地方读或写是可以的,但是如果没有物理内存分配给这个进程,就会出现页面错误(Page fault);或者写入内存的行为是为了与另一个程序通信。
[图片上传失败...(image-b5ca8e-1654131471474)]

[图片上传失败...(image-4ce9e8-1654131471474)]
操作系统必须保护自己不受用户程序的影响,这样做有很多原因:

操作系统必须保护用户程序之间的安全,防止一个用户拥有的线程影响另一个用户拥有的线程。那么硬件能做些什么来帮助操作系统保护自己免受程序的侵害呢,这里有一个非常简单的想法,事实上非常简单,以至于微小的物联网设备可以用很少的晶体管做到这一点,这个概念我称之为基础和界限(B&B,base and bound):
[图片上传失败...(image-3ecc2b-1654131471474)]

我们要做的是我们有两个寄存器,一个base寄存器和一个bound寄存器,这两个寄存器记录黄色线程允许访问内存的哪一部分。程序运行的时候,磁盘上的文件被加载并移动到内存的这一部分。所以现在当程序开始执行的时候,它与程序计数器一起工作,比如在1010范围内,这就是代码所在的地方。硬件会做一个快速比较看看这个程序的计数器是否大于 base,以及它是否小于 bound。

这种方式实现很简单,但是访问里面的每一块内容,都要记录一个长地址
但是对于这个进程分配的内存,在这个模型下是很难改变的。因为有多个进程在共享这个内存,所以当你想扩展内存的时候,可能需要将当前黄色的部分复制到内存中其他剩余空间更大的一块地方
为了优化这些问题,我们一般不直接访问物理内存,而是加上地址空间翻译机制(Address Space Translation):
[图片上传失败...(image-6756ae-1654131471474)]

其中一种翻译设计是加入一个硬件加法器:
[图片上传失败...(image-a581c5-1654131471474)]
地址实际上是在动态转换的,所有的地址其实是一个偏移量,操作系统记录每个进程的内存基址(Base Address),之后加上这个偏移量就是实际的物理内存地址。

[图片上传失败...(image-ae3d6e-1654131471474)]

另一种方法是利用分段,在x86硬件中,我们有代码段,堆栈段等等各种段,每段有不同的基址与长度,即不同的 base 和 bound,即硬件寄存器,有 base 和 bound 硬编码在该段。代码段是有物理起点(即 base)和长度(即 bound)的,而实际运行的指令指针是段内的偏移量。

[图片上传失败...(image-6b4fbe-1654131471474)]

最后一种是我们实际中更常用的,我们要做的是,我们要把地址空间,也就是所有的DRAM,分成一堆大小相等的页(Page)。硬件会使用页表(Page Table)进行从虚拟内存地址到硬件 DRAM 内存地址的转换。

[图片上传失败...(image-2da55f-1654131471474)]

我们今天要讲的第三个操作系统概念是进程:
[图片上传失败...(image-fb064f-1654131471474)]
进程实际上是一个权利受限的执行环境,我们讲过,简单的虚拟线程有这样的问题,每个线程都能访问每个线程的内存,内存翻译机制可以保护我们可以访问的内存,即一个受保护的内存块。它被操作系统中的一个实体独占,这个实体叫做进程。它包括一个受限的地址空间和一个或多个线程,它拥有一些文件描述符和文件系统上下文。

进程提供了内存保护抽象,在保护和效率之间有一个基本的权衡,如果你在同一个进程中有一堆线程,它们之间可以很容易地通信,因为它们共享相同的内存,它们可以通过一个写入内存,另一个读取内存来通信,但是它们之间可能会互相覆盖导致并发安全问题。有时候你想要高性能,提高并行性,你会想要在一个进程中有很多线程。但是当你想要保护时,你想要限制进程之间的通信,所以进程之间的通信故意变得更加困难,这就是我们得到保护的方式。

[图片上传失败...(image-5d0a23-1654131471474)]
这是一个单线程的进程还有一个多线程的进程。对于单线程的进程,只有一组寄存器还有栈内存。对于多线程的进程,代码段,数据,文件是共享的,但是每个线程有独立的寄存器还有栈。当我们从一个线程切换到另一个线程时,为了给人一种多处理的错觉,我们需要从第一个线程切换出寄存器,这样我们就能从第二个线程把它们加载回来。

线程封装了并发性,为什么进程要用多线程?

那么为什么我们需要进程来保证可靠性、安全性和隐私性呢?
[图片上传失败...(image-70a1b1-1654131471474)]

这个保护主要通过翻译机制实现的,每个进程地址空间通过翻译机制映射到物理内存,这个翻译是进程本身不可控的。那么操作系统是如何保证进程无法修改页表从而影响翻译呢?这就引入了下一个话题:双模式操作(Dual-mode Operation)

[图片上传失败...(image-739a38-1654131471474)]
[图片上传失败...(image-cc2219-1654131471474)]

硬件至少提供了两种模式:内核模式(Kernel mode,或管理模式 Supervisor mode)和用户模式(User mode)。当你在用户模式下运行时某些操作会被禁止,比如当你在用户模式时你不能改变你使用的页表,这只有在内核模式下的操作系统才能做到。在用户模式下还不能禁止中断,这样,一个如果想计算PI最后一位的进程就不能阻止其他进程在计时器结束时获得CPU时间。在用户模式下你也被阻止直接与硬件交互等等,因此不能破坏磁盘上的文件。

我们小心控制的用户模式和内核模式之间的转换的是什么?包括系统调用(System call),中断,异常等等。如上图中的流程所示,我们有用户进程,它们对内核进行系统调用,从用户模式切换到内核模式执行系统调用中对应的操作,完成后退出内核模式,系统调用返回。

[图片上传失败...(image-ccb63-1654131471474)]
这是一个典型的Unix系统结构中各个模式分别包含什么的表格:

[图片上传失败...(image-eb6d9-1654131471474)]
举个例子,我们有硬件有内核模式和用户模式。硬件可能会 exec 创建一个新进程。用户模式下的系统调用会进入内核模式,执行完操作后返回用户模式。中断可能导致用户模式进入内核,然后可能检查硬件比如 I/O 就绪,最终从中断中返回。在你除以零或者发生一个页面错误,会发生一个异常,导致进入内核模式,然后最终返回。

总共有三种可能触发用户模式到内核模式转换的操作类型:

[图片上传失败...(image-c135cf-1654131471474)]

如果你注意到这里有两个进程,一个绿色的,一个黄色的。灰色的代表的是操作系统的内存。

上一篇下一篇

猜你喜欢

热点阅读