1. Intro
1.1 Recall :The complexity of CPU

上图是CPU连通各个硬件模块的简要概述图,从此图就可以看出来底层的硬件组成有多么的复杂,所以我们更需要OS发挥它扮演的illusionist(幻术师)的角色,将底层复杂的硬件变为一个与硬件无关的抽象,方便我们编程。
1.2 The risks of OS/OS’s plugins
第三方设备驱动程序是操作系统(OS)中最不可靠的方面之一:
- 驱动程序可能由非利益相关者编写,质量不高
 - 讽刺的是,试图提供干净的抽象可能导致系统崩溃!
 - 50%甚至是60%的OS出现的问题都是由设备驱动程序引起的。
 
操作系统安全模型中的漏洞或操作系统本身的缺陷可能导致系统不稳定和隐私泄露:
- 一个很好的例子是 Meltdown(2017 年)
    
- 从受保护的内核空间中提取数据!
 
 
库版本中的不一致可能导致应用程序执行问题:
- 库之间的版本冲突可能导致依赖不兼容,进而影响应用程序的功能。所以我们需要docker。
 
数据泄露、分布式拒绝服务(DDoS)攻击、计时信道等:
- Heartbleed(SSL):影响到 SSL 加密通信的著名漏洞,攻击者可以通过该漏洞获取服务器的私钥等关键信息,进而窃取受保护的数据。
 
操作系统中的这些不稳定和安全漏洞以及第三方组件的低质量问题可能对系统造成影响,降低性能,并导致安全风险。这就需要开发人员和管理者更加关注操作系统的更新和安全修补,并且密切关注相关漏洞的发现和修复。同时,选择和整合第三方设备驱动程序和库时,需要考虑质量和兼容性,以确保操作系统的稳定运行和安全。
1.3 OS抽象底层硬件
操作系统将底层硬件抽象出来,有助于应对复杂性。

可以看到,OS抽象了硬件驱动程序提供的硬件机器接口,进而为应用程序提供了与底层硬件无关的抽象编程接口,使得应用程序的演变可以与底层硬件的演变分离开来,方便了程序员编写程序。
- 处理器(Processor)→ 线程(Thread)
 - 内存(Memory)→ 地址空间(Address Space)
 - 磁盘、固态硬盘(SSD)等→ 文件(File)
 - 网络(Networks)→ 套接字(Socket)
 - 计算机(Machines)→ 进程(Process)
 
上述是操作系统中的常见抽象概念,将底层的硬件资源映射为更高级别的概念,使得应用程序和用户能够更方便地使用这些资源。
- 
    
处理器(Processor)→ 线程(Thread):处理器是计算机的中央处理器,它负责执行指令和计算任务。操作系统将处理器抽象为线程,一个处理器可以同时运行多个线程(多核),并通过时间片轮转等调度算法来切换执行不同线程的任务。
 - 
    
内存(Memory)→ 地址空间(Address Space):内存是用于存储程序和数据的物理空间。操作系统将内存抽象为每个进程的独立地址空间,每个进程有自己的虚拟地址空间,使进程之间的内存访问不会相互干扰。
 - 
    
磁盘、固态硬盘(SSD)等→ 文件(File):磁盘和固态硬盘是计算机上的存储设备,操作系统将这些物理存储设备抽象为文件的逻辑单位。文件系统提供了对文件的管理和访问接口,使得应用程序可以通过文件操作来读写数据。
 - 
    
网络(Networks)→ 套接字(Socket):网络是用于计算机之间通信的连接,操作系统将网络抽象为套接字的通信接口。应用程序可以通过套接字进行网络通信,发送和接收数据。
 - 
    
计算机(Machines)→ 进程(Process):计算机是由操作系统管理的物理设备,操作系统将计算机抽象为进程的执行环境。每个进程拥有独立的运行空间和资源,使得它们可以并发运行。
 
通过这些抽象,操作系统提供了一组高级别的接口和概念,使得应用程序和用户可以更方便地使用底层硬件资源,而无需关心底层硬件的细节。这样可以降低开发难度,提高系统的可用性和性能。同时,这些抽象也使得操作系统能够更好地管理和分配硬件资源,提高系统的利用率和效率。
1.4 OS as illusionist(幻术师)
因此,本节课重点介绍OS作为illusionist(幻术师)的角色:
操作系统是一个illusionist(幻术师):
- 消除软件/硬件的quirks(应对复杂性)
 - 优化便利性、利用率、可靠性等(帮助程序员)
 
对于任何操作系统领域(例如文件系统、虚拟内存、网络、调度):
- 处理什么样的硬件接口?(物理现实)
 - 提供什么样的软件接口?(更友好的抽象)
 
正是因为OS有了如下四个基本概念,才让其具备了一个illusionist(幻术师)的能力:
1.5 OS Bottom Line:Run Programs

由上图可知,栈的扩张是栈指针不断地向0地址靠近的过程,而堆的扩张是堆指针不断的向最大的地址值靠近的过程。
操作系统启动和运行程序的过程通常包括以下步骤:
- 写程序并进行编译:首先,程序员编写程序代码,并使用编译器将程序源代码转换为可执行文件。
 - 将指令和数据段加载到内存:当程序被启动时,操作系统将程序的可执行文件加载到内存中。这个文件包含了程序的指令和数据段。
 - 创建栈和堆:操作系统为程序创建一个栈空间和堆空间。栈用于存储局部变量和函数调用的信息,而堆用于动态分配内存。
 - “将控制权转移到程序”:操作系统在启动时将控制权转移给程序的入口点,使得程序开始执行。
 - 为程序提供服务:在程序执行过程中,操作系统提供各种服务和资源,如文件访问、内存管理、网络通信等。
 - 保护操作系统和程序:操作系统使用双模式操作(特权模式和用户模式)来隔离操作系统和程序的执行权限,确保操作系统的稳定性和安全性。操作系统会监控程序的行为,防止程序访问未授权的资源或执行危险操作。
 
1.6 Recall (61C): Instruction Fetch/Decode/Execute

这是CS61C(计算机组成原理的内容),描述了计算机如何执行指令,总的来说,CPU中的program counter指针会指向一条指令,拿到它后发送给解码器,解析完后交给registers、ALU等单元去执行,执行部分在执行过程中同时会需要去内存交互。
注意PC寄存器始终指向待执行的下一条指令。
1.7 Today:Four Fundamental OS Concepts
Thread: Execution Context
- 完整描述程序状态
 - 包括程序计数器、寄存器、执行标志、栈等信息
 
Address space (有或没有translation)
- 程序可访问的一组内存地址(用于读取或写入)
 - 可能与物理机器的内存空间不同(在这种情况下,程序在虚拟地址空间中运行)
 
Process: an instance of a running program
- 受保护的地址空间 + 一个或多个线程
 
Dual mode operation / Protection(双模式操作)
- 一个典型的处理器至少有两种不同的模式:内核模式和用户模式。
 - 仅”系统”具有访问某些资源的能力
 - Combined with translation,将程序与其他程序以及操作系统相互隔离
 
2. First OS Concept:Thread of Control
2.1 basic concepts
线程(Thread):单一唯一的执行上下文,一般由如下内容组成:
- 程序计数器(Program Counter Register)、寄存器、执行标志(记录执行结果的状态,如0标志、负数标志等等的一些状态标志位)、栈、内存状态
 - 一个执行线程就是经过虚拟化后的处理器的一个core。
 
当线程在处理器(核心)中运行时,它的状态(上下文)是常驻于(resident)在处理器的寄存器中
“Resident”指的是寄存器中保存了线程的根状态(即上下文):
- 包括程序计数器(PC)寄存器和当前正在执行的指令
    
- PC指向内存中的下一条指令
 - 指令存储在内存中
 
 - 包括正在进行的计算的中间值
    
- 可以包含实际值(如整数)或指向内存中值的指针
 
 - 栈指针保存着栈顶的地址(位于内存中)
 - 其他部分位于”内存”中
 
当线程的状态没有加载(resident)到处理器时,线程被挂起(不执行):
- 处理器的状态指向其他线程
 - 程序计数器寄存器不指向此线程的下一条指令
 - 通常情况下:将每个寄存器的最后一个值的副本存储在内存中
 - 线程不执行时就被放在内存中,不占据处理器(core)线程
 
2.2 CS61C: What happens during program execution

上图是一个程序时的本质,在此期间,CPU、CPU的寄存器、内存,三者需要密切的交互。冯诺依曼结构。
2.3 不同ISA:RISC-V和x86

现在大部分机器使用的都是x86架构,x86架构相对于RISC-V有着更少的寄存器,我们在CS162中将要重点学习的是X86指令集架构,X86更加复杂,同时也更加通用。
2.4 Illusion of Multi-Processors

在单处理器(核心)情况下,操作系统可以通过“时间分片”(Time Multiplexing)的方式来提供我们拥有多个处理器的幻觉。
线程可以看作是 virtual cores。

我们分配给不同的线程任务不同的时间片,这样他们可以轮流占用CPU的core进行运行,从而给了我们一个多个线程任务在同时运行的假象。
虚拟核心(线程)的内容包括:
- 
    
程序计数器(Program Counter):指向当前正在执行的指令的地址,用于记录线程执行的位置。
 - 
    
栈指针(Stack Pointer):指向栈顶的地址,用于维护线程的局部变量和函数调用的信息。
 - 
    
寄存器(Registers):保存线程的执行状态和中间结果,包括通用寄存器、状态寄存器等。
 
virtual cores(线程)可以存在于两种位置:
- 
    
实际的CPU core上:当线程正在CPU core上执行时,其内容被保存在该CPU core的寄存器和缓存中。这是线程在执行过程中的活动状态。
 - 
    
线程控制块(
Thread Control Block,TCB)-在内存中:当线程不在CPU core上执行时,它的内容会被保存在内存中的线程控制块中。线程控制块是一个数据结构,用于保存线程的状态信息,包括程序计数器、寄存器内容、栈信息等。 
当线程需要重新调度执行时,操作系统从线程控制块中恢复线程的状态,然后将其加载到CPU core上继续执行。
线程控制块的使用允许操作系统在不同的CPU core之间进行线程切换和调度,从而实现多线程并发执行。当一个线程被挂起或需要暂时让出处理器时,它的内容会被保存在线程控制块中,以便在适当的时候重新加载并继续执行。这样,操作系统可以灵活地管理线程的执行,提高系统的效率和资源利用率。

在上述情况下,发生了线程切换。假设在时间T1,虚拟核心vCPU1正在实际CPU core上执行,而虚拟核心vCPU2的内容被保存在内存中的线程控制块中。在时间T2,操作系统决定切换执行,将虚拟核心vCPU2加载到实际CPU core上,同时将虚拟核心vCPU1的内容保存在线程控制块中。
线程切换的过程如下:
- 
    
操作系统决定执行线程切换,可以是由于定时器触发的时间片用完、线程主动放弃执行(voluntary yield)、等待I/O等原因。
 - 
    
在T1时刻,操作系统保存了虚拟核心vCPU1的执行状态,包括程序计数器(PC)、栈指针(SP)、寄存器等,将这些内容保存在vCPU1的线程控制块中。
 - 
    
在T2时刻,操作系统从vCPU2的线程控制块中恢复保存的执行状态,包括PC、SP、寄存器等。然后,操作系统将实际切换到vCPU2上,将vCPU2的内容加载到该CPU core上。
 - 
    
然后,操作系统跳转到vCPU2的PC指向的位置,从此刻开始,虚拟核心vCPU2开始在实际CPU core上执行。
 
通过线程切换,操作系统可以实现多线程的并发执行,充分利用处理器的计算能力,提高系统的响应性和效率。切换过程中,操作系统负责保存和恢复线程的执行状态,确保线程切换的正确性和稳定性。
2.5 Multi-programing-Multi-Threads of Control

线程控制块(Thread Control Block,TCB)是一个数据结构,用于保存线程的状态信息,特别是当线程不在运行时,保存了线程的执行状态。除了寄存器的内容外,TCB还可以包含以下其他信息:
- 
    
线程状态:保存线程的运行状态,如就绪、运行、挂起等。
 - 
    
程序计数器(PC):指向下一条要执行的指令的地址。
 - 
    
栈指针(SP):指向线程的栈顶地址,用于维护线程的局部变量和函数调用信息。
 - 
    
线程优先级:保存线程的优先级信息,用于调度算法决定线程的执行顺序。
 - 
    
线程ID:唯一标识线程的标识符。
 - 
    
线程的待处理任务列表:保存线程等待执行的任务列表。
 
后面我们会讲到用户级线程和内核级线程,自然就会有用户内存和内核内存,所以自然就有了用户空间(User Space)和内核空间(Kernel Space),这块概念在后面也会进行具体讲解。
留一个疑问:位于用户空间的用户级内存和位于内核空间的内核级内存有什么区别?是如何管理的?如何区分?
3. Second OS Concept:Address Space

当您在计算机系统中读取或写入某个地址时,根据内存地址和正在执行的操作类型,可能会发生不同的情况。以下是一些可能性:
- 像常规内存一样:在大多数情况下,读取或写入地址涉及访问常规系统内存(RAM)。读取数据会检索存储在指定内存地址的值,而写入数据会用新数据覆盖地址中的当前值。
 - 忽略写入:在某些情况下,内存地址可能是只读的。尝试写入这些地址可能会被静默忽略,地址中存储的数据保持不变。
 - 导致I/O操作(内存映射I/O):内存映射I/O涉及与硬件设备或外围设备通信。在这种情况下,读写某个内存地址会向硬件设备(如显示器、网卡或存储设备)发送信号,触发I/O操作
 - 导致异常(故障):在某些情况下,读写某个特定的内存地址可能是无效的或被禁止的,从而导致异常或故障。当试图访问受保护的内存、分配给其他过程的内存或尚未分配的内存时,可能会发生这种情况。
 - 与另一个程序通信:在进程间通信(IPC)的上下文中,内存地址可能有助于两个或多个正在运行的程序之间的通信。例如,共享内存是一种IPC形式,多个进程将数据读写到一个公共内存地址。
 - 其他:根据内存地址的性质,可能会发生各种其他结果,包括触发特定功能、发出事件信号或管理程序执行。 了解访问内存地址时的这些不同行为对于编程和计算机系统操作至关重要。
 
地址空间实际上是processor对内存的视图,即内存在processor眼中的样子;但是这并不意味着所有的空间里都有实际的物理DRAM内存可以使用,这只是processor对可用的地址、即代表了processor可以访问到的地方,部分地址空间是可以映射到实际的物理DRAM上的。
3.1 Address Space:In a picture

上图展示了Processor中的寄存器是如何与内存进行交互的,如PC寄存器指向内存中的要处理的下一条指令;SP是栈指针,通常指向栈顶。当然其余的寄存器也可能会指向堆中的一些地址。
注意栈的顺序是由上到下递增的,堆则是由下到上进行递增的。堆和栈的大小一般都是动态增长的,随着用户程序的需要不断地给他分配内存,直到无法再分配,就会发生stack overflow或heap overflow。(后续会进行详细的)
- 
    
代码段(Code Segment)中包含什么? 代码段又称为文本段,通常用于存储程序的可执行指令。这部分内存通常是只读的,以防止程序在运行过程中意外地修改自己的代码。
 - 
    
静态数据段(Static Data Segment)中包含什么? 静态数据段用于存储全局变量和静态变量。这部分内存包含程序运行期间的常量和在编译时已知其值的变量。静态数据段可以进一步划分为已初始化数据段和未初始化数据段(BSS,Block Started by Symbol)。这些内容在进程创建后,程序第一次加载时就被加载了。
 - 
    
栈段(Stack Segment)中包含什么? 栈段主要用于存储函数调用所需的局部变量、返回地址以及实际参数。栈是后进先出(LIFO)结构,这意味着当新的数据推入(push)到栈顶时,最后一个进入的数据会先被弹出(pop)。
 - 
    
栈段如何分配?大小如何? 栈的分配通常是由操作系统自动管理的。当进程启动或函数被调用时,操作系统为栈分配内存空间。栈的大小通常是有限的。栈大小依赖于操作系统、编译器和硬件平台。如果栈内存耗尽或超出限制,会产生栈溢出(stack overflow)错误。
 - 
    
堆段(Heap Segment)中包含什么? 堆段主要用于存储程序在运行时动态分配的内存。例如,当使用C语言中的malloc或C++中的new操作符时,分配的内存空间位于堆段。
 - 
    
堆段如何分配?大小如何? 堆内存的分配是由程序员显式控制的,当需要分配内存时需要显式地调用内存分配函数,比如C语言中的malloc,C++中的new。相应地,在不再需要这些内存空间时,需要显式地释放它们,比如C语言中的free,C++中的delete。堆的大小通常比栈大,但其实际大小取决于操作系统、可用系统内存以及相关设置。
 
3.2 Very Simple Multiprogramming
我们之前讨论的线程运行模型是非常简单地多线程模型,他们除了寄存器等CPU资源是独占的意外,所有的非CPU资源都是共享的:
所有的虚拟CPU-线程 (vCPU)共享非CPU资源
- 如内存、I/O设备
 
每个线程都可以读/写内存
- 也许是其他线程的数据
 - 甚至有可能覆盖操作系统的数据
 
这种方式确实存在风险,因为它可能会导致竞争条件、数据错误和程序崩溃。不过,在某些情况下,这种方式仍然是可行且有用的。
所以OS必须保护自己不受用户程序的影响。同时OS也要保护用户程序之间不会相互影响。
3.2.1 Simple Multiplexing has no protection
操作系统必须保护自身免受用户程序的侵害
- 可靠性:操作系统受到损害通常会导致崩溃
 - 安全性:限制线程可以执行的操作范围
 - 隐私:限制每个线程只能访问其被允许访问的数据
 - 公平性:每个线程应受限于其在系统资源(CPU时间、内存、I/O等)中的适当份额,即每个线程都应该有适当的被运行机会
 
操作系统必须保护用户程序不受彼此影响
- 防止一个用户拥有的线程影响另一个用户拥有的线程
 - 示例:防止一个用户从另一个用户处窃取机密信息
 
3.3 What can the hardware do?(段内存管理)
关于OS保护自身免受应用程序影响,硬件可以提供段内存管理的方式,但是这种方式本身比较笨重,在运行过程中会在内存空间中造成大量的内存碎片。
3.3.1 Simple Protection:Base and Bound(B&B)
我们给每个线程分配了一个Base和一个Bound寄存器,对于该线程而言,他可以看到的地址空间仍然是整个进程的地址空间,但是它只被允许去访问的地址空间是[base, bound)。如果该线程去访问别的地址空间,则会触发错误,即只有那个界限中的地址空间是可以被该线程使用的。

左边是这段程序在磁盘中的样子。当程序需要被加载到内存时,这时会发生地址重定位(即磁盘中的地址会被经过某种翻译机制),使之被加载到指定的内存中位置,这里是program loaded后就马上翻译。

这是另一种方式:只不过是只有在程序真的运行的时候才会进行转换,本质上是推后了地址翻译这一步操作:上一种机制是program loaded后就马上翻译,这里是只有program on-the-fly的时候地址才会被翻译。
注意这只是一个基于硬件的检查,而不是软件。
3.3.2 X86-segments and stacks

这是一种变相的基于base and bound的方法:处理器中的EIP和ESP寄存器会分别保存当前正在执行程序在内存中的起始地址和终止地址,外还有一些元数据信息。如相关寄存器的访问权限等等。
3.4 Address Space Translation(基于页的虚拟内存管理系统)
之前讲的两种地址转换方式,只是基于磁盘上的地址+base寄存器的值,只是简单地线性转换。而这种基于转换器的转换可以用任意复杂的规则,需要用到虚拟内存管理机制。

程序在一个与机器物理内存空间不同的地址空间中运行。这意味着程序使用的地址与实际物理内存中的地址是分离的。这种方式通常在虚拟内存管理中使用,它允许程序在不考虑物理内存的实际布局或限制的情况下正常运行。
虚拟内存管理有以下好处:
- 程序隔离:每个程序在独立的虚拟地址空间中运行,防止程序直接访问其他程序或操作系统的内存。这种隔离提高了系统的安全性和稳定性。
 - 更大的可用内存:通过将虚拟地址空间映射到物理内存,程序可以使用比物理内存实际容量更大的内存空间。操作系统可以根据需要将虚拟内存的页面映射到物理内存或交换区中(例如,在磁盘上),从而实现内存的有效管理。
 - 内存保护:虚拟内存机制允许操作系统控制内存页面的访问权限。例如,程序的代码部分可以设置为只读,以防止意外修改。
 
为实现虚拟地址空间与物理内存空间之间的映射,操作系统和硬件通常使用页表(page tables)等数据结构。内存管理单元(MMU)位于CPU中,负责在运行时将虚拟地址转换为相关的物理地址。通过这种方式,程序在各自的虚拟地址空间中运行,而无需关心底层物理内存的实际分布。
3.5 Paged Virtual Address Space
如果我们将整个虚拟地址空间分成相等大小的块(即页),并为每个页设定一个基址,会带来以下优点:
- 内存管理简化:由于所有页面都具有相同的大小,因此将每个页面置于内存中变得更容易。内存空间以大小相等的帧(frames)进行分割,可以在任意帧放置任意页面。
 - 硬件地址转换:通过使用页表,硬件(MMU)可以处理地址转换。每个页面都有一个单独的基址(base),而
bound则是页面大小。一个特殊的硬件寄存器用于存储指向页表的指针。 - 内存利用率提高:将虚拟地址空间分成大小相等的页面有助于更有效地利用物理内存,因为任何一页都可以放入任何帧中。这有助于降低内存碎片问题,提高整体性能。(上面讲的段内存方式会有严重的内存碎片化问题,需要定期整理)
 - 易于实现虚拟内存:将虚拟地址空间拆分为相等大小的页面,将节省内存的同时实现部分数据存储在磁盘或其他辅助存储设备上。当内存不足时,操作系统可以将不常用的页面交换出内存到磁盘交换区,从而提供可用物理内存。
 - 内存保护:由于每个页面都有自己的基址,操作系统可以轻松地为每个页面分配访问权限,如只读、只写或执行权限。这为应用和操作系统提供了内存保护,增强了系统安全性。
 
使用这种页面式内存管理方法,CPU的内存管理单元(MMU)负责在运行时将虚拟地址转换为相应的物理地址。虚拟内存管理不仅提高了内存利用率,还优化了应用程序性能,并在操作系统中提供了内存隔离和保护功能。
3.6 MMU(内存管理单元)
MMU(内存管理单元)是硬件的一部分。它通常位于CPU中,负责处理虚拟地址到物理地址的转换。MMU在地址转换过程中使用页表,页表由操作系统在内存中维护。因此,MMU作为硬件组件,扮演着虚拟内存管理和硬件之间联系的关键角色。
3.7 比较
相较于基址-界限(Base and Bound)方法,使用分页式虚拟地址空间的优势如下:
- 更高的内存利用率:分页式虚拟地址空间将内存分成大小相等的块,使各种大小的内存需求能够得到满足,从而降低内存碎片。在基址-界限系统中,存在内存碎片问题,因为分配给进程的内存块可能不是完美匹配所需大小的。
 - 更灵活的内存管理:分页式虚拟地址空间允许任何一页映射到物理内存的任何帧。这为操作系统提供了更灵活的内存管理方案,可以根据需求将页面交换到磁盘或重新分配内存资源。
 - 支持虚拟内存:通过使用分页式虚拟地址空间,操作系统可以在内存不足时将不常用的页面置换到其他存储设备(如磁盘)。这允许程序使用超过物理内存大小的地址空间,从而为开发人员提供了更大的可用内存。
 - 简化地址转换过程:使用分页系统,虚拟地址到物理地址的转换过程相对简单,因为其转换关系相对固定。在基址-界限系统中,地址转换过程可能涉及不同的界限检查和地址计算。
 - 更好的内存保护:分页式虚拟地址空间允许操作系统为每个页面设置独立的访问权限(如读、写或执行权限)。这提供了更细粒度的内存保护,有利于提高整个系统的安全性和稳定性。
 
总之,分页式虚拟地址空间由于其较高的内存利用率、灵活的内存管理能力、虚拟内存支持以及更好的内存保护而优于基址-界限方法。然而,需要注意的是,分页系统增加了内存管理的复杂性并可能引入一定的性能开销,如页表查找和更频繁的地址转换。但在实际应用中,分页式内存管理相较于基址-界限方法所带来的收益通常超过其开销。
3.8 总结
在基于页的虚拟内存管理系统中,以下特点是典型的:
- 指令操作虚拟地址:程序中的指令访问虚拟地址而不是物理地址。这包括指令本身的地址和加载/存储数据的目标地址。
 - 通过硬件中的页表将虚拟地址转换为物理地址:内存管理单元(MMU)负责通过页表将虚拟地址转换为相应的物理地址。
 - 地址空间中的任意一页可以在内存中的任意(与页大小相同的)帧里:虚拟地址空间中的每一页可以映射到物理内存的任意帧,从而使内存管理更加灵活高效。
 - 或者不在内存中(访问会引发页面错误):当程序试图访问不存在与物理内存中的页面时,将引发页面错误。这种情况下,操作系统将加载缺失页到内存并更新页表条目。
 - 特殊寄存器保存页表基址:一个特殊的硬件寄存器(通常称为页表基址寄存器,PTBR)存储了当前进程所使用的页表的基地址。MMU使用该寄存器来引用和访问页表。
 
这不像段内存管理,段内存管理要求一个程序的所有内容都必须在物理内存空间中连续,
4. Third OS Concept:Process
4.1 Definition of process
定义:具有受限权限的执行环境
- (受保护的)具有一个或多个线程的地址空间
 - 拥有内存(地址空间)
 - 拥有文件描述符、文件系统上下文等
 - 封装了共享进程资源的一个或多个线程
 
应用程序以进程的形式执行
- 复杂应用程序可以创建/执行子进程 [稍后说明!]
 
为什么需要进程?
- 进程之间相互保护
 - 操作系统受到保护
 - 进程提供内存保护
 
在保护与效率之间的基本权衡
- 同一进程内的通信更容易实现
 - 不同进程之间的通信较为困难
 
将应用程序执行环境定义为具有受限权限的进程有诸多好处。进程提供了一定程度的安全性与隔离,帮助保护操作系统和其他进程的资源。通过使用受保护的地址空间,进程可以拥有独立的内存、文件描述符和文件系统上下文等资源。
然而,在进程之间进行通信比在同一进程内部通信要复杂得多。为了实现不同进程之间的通信,需使用进程间通信(IPC)机制,例如套接字、管道、共享内存等。尽管进程带来了保护和隔离,但其可能降低通信效率。
4.2 single and multi-threaded process

线程封装并发性:
- “主动的”组件
 
地址空间封装保护:
- “被动的”组件
 - 防止含有错误的程序导致系统崩溃
 
为什么要在每个地址空间中使用多个线程?
- 并行性:利用实际硬件并行性(例如多核处理器)
 - 并发性:简化对 I/O 和其他同时发生的事件的处理
 
线程是程序代码内实现并发性的关键组件。通过使用多个线程,程序可以实现在计算和 I/O 操作之间进行切换,从而更高效地运行任务。在多核处理器环境中,多线程编程还可以发挥硬件的并行性。
与之不同的是,地址空间提供程序之间保护的封装。它们作为“被动”组件,确保错误或恶意的程序不会对操作系统和其他进程造成破坏。每个进程在独立的地址空间内运行,彼此隔离。
多线程允许在同一个地址空间中同时运行多个任务。这样,各个线程可以共享进程资源(如内存空间和文件描述符),从而提高程序在处理多个任务时的并发性和性能。同时,由于线程共享相同的地址空间,它们之间的通信和数据交换变得更加简单。总之,通过在单一地址空间中使用多线程,程序设计人员可以在确保保护的前提下,实现并发性、并行性以及高效的事件处理。
4.3 Protection and isolation
我们为什么需要进程?
- 可靠性:程序错误只会覆盖所在进程的内存,不会影响其他进程。
 - 安全性和隐私保护:恶意或受到攻击的进程无法读取或写入其他进程的数据。
 - (一定程度上的)公平性:强制执行对磁盘和CPU的共享。
 
实现进程的机制:
- 地址转换:地址空间仅包含进程自身的数据。通过地址转换能够实现保护,防止一个进程直接访问另一个进程的内存空间。
 - 但是,为什么进程不能更改页表指针?
    
- 或者使用I/O指令绕过系统?
 
 - **硬件必须支持特权级别**。特权级别确保只有在特定权限下的代码(如操作系统内核)可以执行敏感操作,例如修改页表指针和执行I/O指令。这样可防止普通进程执行恶意操作或篡改操作系统数据。
 
通过使用地址转换和硬件特权级别,操作系统可以在保护进程的同时实现可靠性、安全性和公平性。这为程序设计师提供了一个受保护的运行环境,可以更安全地开发和运行应用程序。
5. Fourth OS Concept:Dual Mode Operation
5.1 basic two modes
硬件至少提供两种模式(至少有一个模式位):
- 内核模式(或
supervisor模式) - 用户模式
 
在用户模式下执行时,某些操作是被禁止的:
- 更改页表指针、禁用中断、直接与硬件交互、向内核内存写入数据。
 
在用户模式和内核模式之间进行谨慎控制的切换:
- 系统调用、中断、异常
 
将硬件操作分为内核模式和用户模式是一种保护机制。在内核模式下,操作系统可以执行关键任务,比如修改页表指针、处理硬件中断等。在用户模式下,应用程序有限制地执行,防止它们执行影响系统稳定的操作。
用户和内核模式之间的切换是经过严格控制的。例如,当程序发出系统调用请求,它将从用户模式切换到内核模式。操作系统以内核模式身份处理该请求,然后将结果返回给用户程序并切换回用户模式。通过维护这两种模式并在适当时刻切换,可以在保持系统安全的同时,允许应用程序与操作系统互动。

5.2 user memory & kernel memory
内核内存和用户内存的管理是统一的,但它们在内存中被区分开来,具有不同的访问权限和用途。
内核内存:
- 这部分内存专用于操作系统内核,用于存储内核代码、内核数据结构(如进程表、文件系统缓存、页表等)以及设备驱动程序。
 - 内核内存具有较高的特权级别,只能由内核模式下的代码访问。
 - 这有助于保护操作系统核心部分免受恶意或错误应用程序的影响。
 
用户内存:
- 这部分内存用于用户运行的应用程序。
 - 每个应用程序都在单独的地址空间内运行,地址空间包含了程序代码、数据、堆栈等。
 - 用户内存在用户模式下运行,这意味着应用程序没有权限执行操纵内核内存的敏感操作,从而确保了操作系统的安全性。
 
尽管内核内存和用户内存在概念上和访问权限上区分开来,但它们在内存管理方面依赖于统一的管理策略,如基于分页的虚拟内存系统。操作系统通过页表和内存管理单元(MMU)来处理内核空间和用户空间之间的地址转换,确保它们之间的隔离。
总之,内核内存和用户内存在内存管理上是统一的,但它们被保护在独立的地址空间中,具有不同的访问权限和用途。这有助于确保操作系统和应用程序在资源保护和可靠性方面的需求得到满足。
此外,内核内存和用户内存都最终映射到物理硬件,这里要表达的意思是他们的底层物理资源是相同的,只是管理和用途上有所不同。
虽然它们的逻辑地址空间保持分离且有不同的访问权限,但它们都依赖于底层的物理内存资源。 在基于分页的虚拟内存系统中,操作系统使用页表将虚拟地址映射到物理地址。
页表将用户内存空间(用户程序)和内核内存空间(操作系统内核)中的虚拟地址关联到相应的物理地址。这意味着您的计算机上运行的所有进程和操作系统在物理层面上实际上共用相同的物理资源。
操作系统可以在确保概念分离和访问保护的同时,实现高效的内存管理。在虚拟内存系统中,这种映射关系在运行时可以动态调整,从而提供灵活的内存分配和管理策略。
5.3 Example:UNIX System Structure


上图很好的诠释了OS如何在日常的运行中自然地在用户模式和内核模式之间进行切换,注意用户态只有受限的硬件访问权限,而内核态则拥有所有的硬件访问权限。
5.4 Additional Layers of Protection for Modern Systems

通过虚拟机或容器实现额外的保护层:
- 在虚拟机中运行完整的操作系统:虚拟机(VM)提供了一种将硬件资源抽象成多个独立虚拟环境的方法。每个虚拟机可以运行一个独立的操作系统,无论宿主机上的操作系统是什么。这使用户可以在同一台物理计算机上运行多个不同的操作系统,并在它们之间实现更严格的隔离。
 - 将与应用程序相关的所有库打包到一个容器中进行执行:容器技术通过将应用程序及其依赖项打包到一个独立、可移植的单元中来实现资源隔离。容器与宿主系统共享相同的内核,但拥有独立的文件系统、进程空间、网络接口等。容器可以更轻量、更迅速地部署和执行应用程序,同时也提供了一定程度的隔离和保护。
 
在课程的后期,将进一步探讨这些概念及其实现方法。虚拟机和容器技术在某种程度上可以看作对操作系统内核和用户内存保护的补充,使我们在部署复杂应用程序和执行任务时同时实现资源隔离和保护。这对于云计算、服务化架构和微服务等现代软件开发和运行环境尤为重要。
5.5 Example:The switch of kernel and user mode
本节PPT第11页开始,讲述了处理器上的核是如何进行内核态和用户态的切换的:
- 刚开始处理器在内核态,sysmode=1,此时Base和Bound寄存器分别是00,FF,代表在内核模式下可以访问整个地址空间。
 - 之后通过Privileged Instruction(特权指令),去设置下一个即将要运行的程序的相关信息,如:设置uPC寄存器,指向即将要运行的程序(已经加载到内存中)的指令代码的地址,将其他的寄存器设置为即将要运行的程序PCB(TCB)中的状态变量。此时已经完成准备工作。
 - OS进行状态切换,用户程序开始执行。每当我们该进程即将被切换时,我们需要提前进入内核态,在内核态下完成当前进程上下文的保存,并预加载下一个要执行进程的相关状态信息,之后内核态直接返回即可开始下一个进程的执行。
 
5.6 The way of user->kernel mode
用户模式转换到内核模式的3种类型:
系统调用(Syscall)
- 进程请求系统服务,例如:exit
 - 类似于功能调用,但位于进程“之外”
 - 不具有要调用的系统函数的地址
 - 类似于远程过程调用(RPC) - 之后介绍
 - 将系统调用id和参数封装在寄存器中,并执行系统调用
 
中断(Interrupt)
- 外部异步事件触发上下文切换
 - 例如:定时器、I/O设备
 - 与用户进程独立
 
陷阱或异常(Trap or Exception)
- 进程内部同步事件触发上下文切换
 - 例如:保护违例(段错误)、除以零等
 
这3种类型皆为非编程控制传输(UNPROGRAMMED CONTROL TRANSFER)
- 转移到何处?
 
How do we get the system target address of the “unprogrammed control transfer?”
- 这个问题到后面会进行详细说明,大概意思就是每个相关调用会返回一个interrupt number,然后在内核态我们有一个中断向量表,会根据上述返回的number去找到对应的向量,从而获取具体的处理逻辑。
 
当用户模式和内核模式之间的转换发生时,控制流会跳转到操作系统预定义的处理程序。处理程序根据事件类型(系统调用、中断或异常)来负责处理请求或错误,并在处理完毕后将控制权返回给用户程序(对于中断和异常)或者将控制权移交给其他进程(在进程调度的情况下)。这种机制可以确保操作系统保持对硬件资源和程序执行的控制,并为潜在错误提供隔离和错误处理。
6. some questions
6.1 Running Many Programs ???
我们已经具备基本机制来:
- 在用户进程和内核之间进行切换
 - 内核可以在用户进程之间进行切换
 - 保护操作系统免受用户进程影响,保护进程之间互相不受影响
 
问题:
- 我们如何决定运行哪个用户进程? 答:操作系统使用进程调度算法来决定哪个用户进程应该运行。常见的进程调度算法包括先进先出(FIFO)、优先级调度、短进程优先(SPF)和时间片轮转调度等。
 - 我们如何在操作系统中表示用户进程? 答:操作系统使用进程控制块(PCB)来表示和管理进程。PCB 包含进程的状态信息(例如,进程 ID、优先级、程序计数器值、寄存器值和内存页表信息)。
 - 我们如何打包进程并将其搁置? 答:当操作系统将一个进程暂停以运行另一个进程时,它会将当前进程的执行上下文(如寄存器值、程序计数器值等)保存到相应的 PCB。当之后重新运行该进程时,操作系统从 PCB 中恢复上下文并恢复执行。
 - 我们如何为内核获得堆栈和堆? 答:内核独立于用户进程管理其Own内存。它具有专用堆栈和堆,用于处理内核操作。内核内存不同于用户进程内存,并提供更高的访问权限。
 - 我们不是在浪费大量内存吗? 答:虽然对于内核和用户进程的隔离和保护可能导致某些内存浪费,但操作系统采用各种内存管理策略来优化资源使用,例如分页、分段和虚拟内存技术。
 
以上回答涵盖了操作系统如何管理进程、调度执行和优化资源使用的基本概念。操作系统作为硬件和应用程序之间的抽象层,需要确保计算资源的有效分配和保护。虽然这可能引入一定程度的性能开销,但权衡之下,资源保护和稳定性提升值得这种设计。
6.2 Process Control Block(PCB)
进程控制块(Process Control Block)
内核将每个进程表示为一个进程控制块(PCB),其中包括:
- 状态(运行、就绪、阻塞等)
 - 寄存器状态(当进程不处于就绪(ready)状态时)
 - 进程 ID(PID)、用户、可执行文件、优先级等
 - 执行时间等
 - 内存空间、地址转换等
 
内核调度器维护一个包含 PCB 的数据结构。调度算法从中选择要运行的下一个进程。
当操作系统需要执行进程调度时,它会遍历包含 PCB 的数据结构,并运用调度算法来确定下一个要运行的进程。此算法可以根据进程优先级、到达时间、估计运行时间等因素来选择进程。
7. Conclusion:4 fundamental OS Concepts
Thread: Execution Context
- 完全描述程序状态
 - 程序计数器、寄存器、执行标志、栈。( Program Counter, Registers, Execution Flags, Stack)
 
Address space (with or without translation)
- 程序可访问(读或写)的内存地址集合
 - 可能与物理机的内存空间不同(在这种情况下,程序运行在虚拟地址空间中),即虚拟地址空间与物理内存地址空间是完全独立的,但是可以用某种翻译器让二者以某种方式形成某种映射关系。
 
Process: an instance of a running program
- 受保护的地址空间 + 一个或多个线程
 
Dual mode operation / Protection
- 只有“系统”具有访问某些资源的能力
 - 结合地址转换,将程序与彼此隔离,也将操作系统与程序隔离。
 
在这里,我们定义了线程、地址空间和进程之间的关系。
线程是程序执行的上下文,包括程序计数器、寄存器、执行标志和堆栈等。
地址空间(可能为虚拟地址空间)为程序提供了可访问的内存区域。
进程是一个正在运行的程序实例,包含一个受保护的地址空间和一个或多个线程。
操作系统通过双模操作和保护机制,确保只有内核具有访问特殊资源的能力。
这种方法可以防止不同进程之间的相互干扰,并确保操作系统免受潜在的恶意或意外操作的影响。同时,地址转换与保护机制相结合,将程序与彼此隔离。这种设计可以确保系统的稳定性和安全性。