共计 6251 个字符,预计需要花费 16 分钟才能阅读完成。
熟肉视频地址:
- CS162 操作系统课程第二课 - 4 个外围 OS 概念(上)
- CS162 操作系统课程第二课 - 4 个外围 OS 概念(下)
咱们探讨了操作系统如何表演 裁判 , 魔术师 和粘合剂 的角色,裁判 是指对于资源爱护的治理;魔术师是指咱们要让它看起来像咱们有一套十分洁净易用的资源的形象,而不是应用理论的没有对立接口的物理资源。粘合剂是一组通用服务,它们使在操作系统上编写程序变得更容易,例如文件系统服务、网络服务等等。
明天咱们要讲操作系统四个基本概念:
咱们将从什么是线程(Thread)开始探讨,线程会残缺地形容一个程序状态或者一个线性执行上下文,它会有一个程序计数器,寄存器,执行标记,线程栈等等。而后是地址空间(Address Space),这是程序可拜访的一组内存地址。而后咱们会介绍什么是过程(Process),过程包含地址空间以及一个或者多个线程。最初,咱们将探讨在这门课的后期特地重要的硬件机制也就是双模式操作(dual mode operation),即一个典型的处理器至多有两种不同的模式,咱们能够松散地称之为内核模式(Kernel mode)和用户模式(User mode)。
咱们要学习如何编写和编译程序成为可执行文件,而后从文件系统中取出这些可执行文件后,建设运行的过程,文件被加载到内存中,咱们会具体探讨那个过程运行须要提供的栈和堆。而后是转移管制(Transfer Control),意思是处理器的程序计数器将会指向该过程的用户代码中的指令,而后程序就会开始执行。操作系统须要给这些过程提供各种零碎服务,同时操作系统须要爱护操作系统和过程不受其余过程和其余用户的影响。
咱们来回顾下 CS61B + CS61C 课程的内容:处理器一开始有一个程序计数器(PC,Program Counter),还有一个能够读取的内存(Memory),内存外面有一组指令(Instruction)。程序计数器会指向内存中的指令,让处理器读取下一条指令。咱们把指令从内存中拉进去,解码,而后把它输出到执行流水线中。咱们在 CS61C 中常常议论的是一种危险类型的处理器(risk style processor),有五个执行流水线。解码后,它们会输出一组寄存器和 ALU 来进行实际操作,而后递增程序计数器并持续下一条指令。
咱们的第一个操作系统概念是一个控制线程(Thread of Control),线程实际上是一个惟一的执行上下文,它有程序计数器,寄存器,执行标记,线程栈,内存状态。线程就像上一张幻灯片中你看到的处理器的虚构版本。一个线程正在处理器或外围上执行,顺便说一下,我当初混用处理器和外围这两个概念,前面咱们就能更分明地了解这两个概念的区别。但 它是在处理器寄存器中常驻(Resident)时才会执行的。寄存器中有线程的上下文(Context)或根状态(Root State),有些货色在寄存器中,剩下的在内存中:
- 包含一个程序计数器,以后正在执行的指令,程序计数器指向内存中的下一条指令,所有的指令都存储在内存中
- 包含用于进行计算的两头值
- 有一个栈指针,它有一个指向内存中栈的顶部的指针,线程的其余部分都在内存中。
当线程的状态不在寄存器中即不是常驻的时候,线程就被挂起(suspended)或不再执行。被挂起的线程实际上是在内存中,还没有执行或者基本没有执行,有其余线程正在执行。
这是另一个对于执行过程的视图(冯·诺依曼体系)
这是一组地址,咱们稍后会把它叫做 地址空间,从 0 到 2^32 -1。外面有一组将要执行的指令,粉色的是你的处理器。过程有一个与之相干的爱护状态,处理器包含寄存器和流水线的汇合,咱们的执行序列(Execution Sequence)包含:在程序计数器上获取一条指令,解码它,执行它,写回寄存器,获取下一条指令,并反复这个执行循环。
那么咱们是如何产生多处理器的错觉的呢?咱们上次讲过在你的笔记本电脑上做一个 ps -aux 或者其余的操作唤起工作管理器,如果你认真看,你会发现有数百个过程在运行,大部分在休眠状态。但它们都能够在你以后的处理器上应用,那么它是如何工作的呢?
让咱们假如只有一个物理处理器上只有一个核,在任何给定的工夫在硬件上只有一个执行线程。但当初咱们想要的是有多个 cpu,或者说是多个 cpu 同时运行的假象,所以咱们能够有多个线程同时运行。从程序员的视图来看是,我有一堆货色都在运行,它们都共享内存。
每个线程是一个虚构外围,咱们这里有三个线程,品红色线程,青色线程以及黄色线程,造成他们同时运行的错觉的形式是通过咱们多反复用硬件,咱们运行一段时间的品红色线程,而后运行一会青色线程,而后运行一会黄色线程,以此循环。
线程的内容包含什么?显然,每个线程都须要一个程序计数器和一个堆指针,还有所有寄存器状态。如果线程在运行中,寄存器状态就在处理器的寄存器中;如果不在运行中,这些状态就在内存中,被称为线程管制块(TCB,Thread Control Block)
咱们举一个例子:
在 T1 的时候 vCPU1 在运行,在 T2 的时候 vCPU2 在运行,在 T1 和 T2 时,产生了上下文切换(Context Switch),在底层是一个事件,咱们把所有的程序计数器,栈指针,所有其余寄存器都保留在内存中的对应线程管制块中。咱们从 vcpu2 加载程序计数器、堆栈指针等,而后运行 vcpu2。
一些 Q&A:
- 每个线程是否都有本人独占的 CPU 缓存?答案是否定的,个别每个核有一个高速缓存,线程都共享同一个缓存,你能够想到如果切换太快,就没有线程能利用好缓存了。原始处理器中的缓存或 TLB 必须在切换时被刷新,更高级的处理器则不会。缓存自身通常在物理空间中,你从一个线程切换到另一个,你只是扭转了页表,不须要清空缓存。
- 线程上下文切换这须要多长时间?这大略须要几微秒的工夫,须要确保转换的工夫不要太长,不要把大部分工夫都花在转换上。
而后是 什么触发了上下文切换?全局倒计时器工夫到了,或被迫让出 CPU 等这样的事件触发的。比方某个线程要做一些 I/O,那么操作系统就会调度其余线程。
咱们可能有一堆内存(蓝色的代表内存),咱们能够设想这些虚构过程中的每一线程都有本人的栈、堆、数据和代码,它们都以某种形式散布在内存中,咱们要做的就是以某种形式记录所有货色的地位。线程管制块是所有货色的所在,当咱们从绿色切换到黄色,咱们要做的第一件事是将所有的绿色线程的寄存器保留到它的线程管制块中,顺便说一下,那是内核内存的一部分。
咱们明天要讲的第二个操作系统概念是 地址空间 :
它是一组可拜访地址和与之相干的状态,这只是处理器对可用地址的视图。对于 32 位处理器,能够拜访的地址空间是 2 的 32 次方大概是 40 亿,对于 64 位的处理器,2 的 64 次方是 18 万亿。然而这并不是说所有的空间里都有对应的物理 DRAM 内存能够应用,只有一部分有对应的物理 DRAM。
当你读或写一个地址空间中的地址时,兴许它像一般内存一样运行,或者齐全疏忽写入操作;或者可能是零碎导致一个 I / O 操作产生,这被称为内存映射 I/O;或者它可能会导致异样,如果你试图在我这里展现的栈和堆之间的某个中央读或写是能够的,然而如果没有物理内存调配给这个过程,就会呈现 页面谬误(Page fault);或者写入内存的行为是为了与另一个程序通信。
- 程序计数器(PC,Program Counter)指向某个地址,意味着处理器能执行在那个地址的指令。
- 栈指针(SP,Stack Pointer)指向某个地址,通常是栈的底部(图外面栈向下增长,所以最初一个元素是栈底)
- 栈(stack):栈就是当你递归调用一个函数时,前一个函数的变量会被推到栈上,而后栈指针向下挪动,当函数返回时,你把它们从栈中取出,栈指针向上挪动。
- 堆(heap):你用 malloc 调配货色等的时候,通常就会放在堆上。堆的初始物理内存也比程序最终须要的要少,随着程序的增长,这会在堆上调配货色,你会遇到页面谬误,而后调配理论的物理内存。
- 代码段(Code Segment):保留着要执行的代码。
- 静态数据(Static Data):动态变量,全局变量等等
操作系统必须爱护本人不受用户程序的影响,这样做有很多起因:
- 可靠性:危及操作系统通常会导致系统解体
- 安全性:你想要限度恶意软件的作用范畴
- 隐衷:限度每个线程拜访它应该拜访的数据,不心愿我的明码或者机密被泄露
- 公平性:我不心愿这样一个线程,例如它计算 PI 的最初一位,忽然就能占用所有的 cpu,以就义其余所有的线程为代价。
操作系统必须爱护用户程序之间的平安,避免一个用户领有的线程影响另一个用户领有的线程。那么硬件能做些什么来帮忙操作系统爱护本人免受程序的侵害呢,这里有一个非常简单的想法,事实上非常简单,以至于渺小的物联网设施能够用很少的晶体管做到这一点,这个概念我称之为根底和界线(B&B,base and bound):
咱们要做的是咱们有两个寄存器,一个 base 寄存器和一个 bound 寄存器,这两个寄存器记录黄色线程容许拜访内存的哪一部分。程序运行的时候,磁盘上的文件被加载并挪动到内存的这一部分。所以当初当程序开始执行的时候,它与程序计数器一起工作,比方在 1010 范畴内,这就是代码所在的中央。硬件会做一个疾速比拟看看这个程序的计数器是否大于 base,以及它是否小于 bound。
这种形式实现很简略,然而拜访外面的每一块内容,都要记录一个长地址 。
然而对于这个过程调配的内存,在这个模型下是很难扭转的 。因为有多个过程在共享这个内存,所以当你想扩大内存的时候,可能 须要将以后黄色的局部复制到内存中其余残余空间更大的一块中央 。
为了优化这些问题,咱们个别不间接拜访物理内存,而是加上地址空间翻译机制(Address Space Translation):
其中一种翻译设计是退出一个硬件加法器:
地址实际上是在动静转换的,所有的地址其实是一个偏移量,操作系统记录每个过程的内存基址(Base Address),之后加上这个偏移量就是理论的物理内存地址。
另一种办法是利用分段,在 x86 硬件中,咱们有代码段,堆栈段等等各种段,每段有不同的基址与长度,即不同的 base 和 bound,即硬件寄存器,有 base 和 bound 硬编码在该段。代码段是有物理终点(即 base)和长度(即 bound)的,而理论运行的指令指针是段内的偏移量。
最初一种是咱们理论中更罕用的,咱们要做的是,咱们要把地址空间,也就是所有的 DRAM,分成一堆大小相等的页(Page)。硬件会应用页表(Page Table)进行从虚拟内存地址到硬件 DRAM 内存地址的转换。
咱们明天要讲的第三个操作系统概念是过程:
过程实际上是一个权力受限的执行环境,咱们讲过,简略的虚构线程有这样的问题,每个线程都能拜访每个线程的内存,内存翻译机制能够爱护咱们能够拜访的内存,即一个受爱护的内存块。它被操作系统中的一个实体独占,这个实体叫做过程。它包含一个受限的地址空间和一个或多个线程,它领有一些文件描述符和文件系统上下文。
过程提供了内存保护形象,在爱护和效率之间有一个根本的衡量,如果你在同一个过程中有一堆线程,它们之间能够很容易地通信,因为它们共享雷同的内存,它们能够通过一个写入内存,另一个读取内存来通信,然而它们之间可能会相互笼罩导致并发平安问题。有时候你想要高性能,进步并行性,你会想要在一个过程中有很多线程。然而当你想要爱护时,你想要限度过程之间的通信,所以过程之间的通信成心变得更加艰难,这就是咱们失去爱护的形式。
这是一个单线程的过程还有一个多线程的过程。对于单线程的过程,只有一组寄存器还有栈内存。对于多线程的过程,代码段,数据,文件是共享的,然而每个线程有独立的寄存器还有栈。当咱们从一个线程切换到另一个线程时,为了给人一种多解决的错觉,咱们须要从第一个线程切换出寄存器,这样咱们就能从第二个线程把它们加载回来。
线程封装了并发性,为什么过程要用多线程?
- 一个是并行性(Parallelism)。如果你有多个核,通过在同一个过程中有多个线程,能够让许多工作同时解决。
- 另一个是为了并发(Concurrency)。并发性就是大多数线程大部分工夫都在休眠的状况,比方某个线程须要做一些 I/O,开始 I/O 时进入睡眠,而后在 I/O 实现时醒来,那么 CPU 不用期待 I/O 实现,而是能够做其余的事件。这就是多线程的益处。
那么为什么咱们须要过程来保障可靠性、安全性和隐衷性呢?
- 对于可靠性:Bug 只会笼罩一个过程的内存,歹意的或者被毁坏的过程不能烦扰其余过程。
- 对于平安还有隐衷性:过程不能批改其余过程的内存
- 公平性:共享磁盘,CPU 等资源。
这个爱护次要通过翻译机制实现的,每个过程地址空间通过翻译机制映射到物理内存,这个翻译是过程自身不可控的。那么操作系统是如何保障过程无奈批改页表从而影响翻译呢?这就引入了下一个话题:双模式操作(Dual-mode Operation)
硬件至多提供了两种模式:内核模式(Kernel mode,或管理模式 Supervisor mode)和用户模式(User mode)。当你在用户模式下运行时某些操作会被禁止,比方当你在用户模式时你不能扭转你应用的页表,这只有在内核模式下的操作系统能力做到。在用户模式下还不能禁止中断,这样,一个如果想计算 PI 最初一位的过程就不能阻止其余过程在计时器完结时取得 CPU 工夫。在用户模式下你也被阻止间接与硬件交互等等,因而不能毁坏磁盘上的文件。
咱们小心管制的用户模式和内核模式之间的转换的是什么?包含零碎调用(System call),中断,异样等等。如上图中的流程所示,咱们有用户过程,它们对内核进行零碎调用,从用户模式切换到内核模式执行零碎调用中对应的操作,实现后退出内核模式,零碎调用返回。
这是一个典型的 Unix 系统结构中各个模式别离蕴含什么的表格:
- 用户模式 蕴含你所有的程序和库等等。
- 零碎调用:示意能够平安拜访各种资源的代码。
- 内核模式 蕴含:信号处理,I/O 零碎,文件系统,块替换 I/O 零碎,磁盘驱动,CPU 调度,页替换,虚拟内存治理等等。内核通过接口拜访并管制硬件。
举个例子,咱们有硬件有内核模式和用户模式。硬件可能会 exec 创立一个新过程。用户模式下的零碎调用会进入内核模式,执行完操作后返回用户模式。中断可能导致用户模式进入内核,而后可能查看硬件比方 I/O 就绪,最终从中断中返回。在你除以零或者产生一个页面谬误,会产生一个异样,导致进入内核模式,而后最终返回。
总共有三种可能触发用户模式到内核模式转换的操作类型:
零碎调用:
- 调用一个零碎服务,例如 exit 退出过程
- 函数调用,然而波及拜访过程外的一些资源
- 目前没有零碎函数须要的内存地址
- RPC 近程函数调用
- Marshall 寄存器中的零碎调用 id 和参数并执行零碎调用
中断:
- 内部异步事件触发上下文切换,例如,定时器,I/ O 设施
Trap 或者异样:
- 外部同步事件触发上下文切换
- 例如,违反爱护(segmentation fault),除以零,…
如果你留神到这里有两个过程,一个绿色的,一个黄色的。灰色的代表的是操作系统的内存。
微信搜寻“干货满满张哈希”关注公众号,加作者微信,每日一刷,轻松晋升技术,斩获各种 offer:
我会常常发一些很好的各种框架的官网社区的新闻视频材料并加上集体翻译字幕到如下地址(也包含下面的公众号),欢送关注:
- 知乎:https://www.zhihu.com/people/…
- B 站:https://space.bilibili.com/31…