# Four Fundamental OS Concepts ## Complexity 复杂性 操作系统的硬件非常复杂。如果没有操作系统,用户必须直接与硬件交互,这将非常困难。操作系统通过提供抽象来隐藏硬件的复杂性,使用户能够更容易地使用计算机。 这也就意味着,如果处理不当: - 复杂性会“泄露”出来。例如一些第三方驱动程序导致系统崩溃,或者某些功能无法正常工作。 - 安全漏洞。例如2017年,Intel的CPU存在一个名为Meltdown的漏洞。攻击者甚至可以访问kernel模式的内存! - 不同版本的库会导致应用程序可能出问题。(现在我们有了Docker) 一般来说,操作系统进行的"抽象"包括: - Processor -> Thread - Memory -> Address Space - Disks, SSDs, ... -> Files - Networks -> Sockets - Machines -> Processes ## Thread of Control Thread 是一个独一无二的context, 其包括程序计数器、寄存器、堆栈、CPU标志位、内存等等。 ### Resident / Running 驻留(运行态) 当一个线程处于Resident状态时,表示该线程正在被CPU执行。 Resident指: 寄存器当前保存了该线程的state (content)。 - 包括程序计数器(PC, program counter)、当前执行的指令的地址 - PC指向内存中下一条指令的地址 - 包括程序当前正在计算的数据 - 栈指针 - 剩下的、内存里的数据 ### Suspended / Ready 挂起(就绪态) 当一个线程处于Suspended状态时,表示该线程已经准备好运行,但由于某些原因(如等待资源、等待I/O操作完成等)而暂时未执行。 与Resident状态具有很多相反的状态: - CPU的state不再是该线程的content,而是另一个线程的content - PC指向另一个线程的指令地址 - 大多数情况下,线程的content被保存在内存中,而不是寄存器中 ### Content Switch 上下文切换 #### 使用场景 现代的操作系统通常支持多线程。电脑上可以同时运行很多线程,但单个CPU核心一次只能执行一个线程。当操作系统需要切换正在运行的线程时,就会发生上下文切换。 以图中为例: ![CPU上下文切换](/UCB-CS162/images/L02-content-switch.png) - T1时刻,vCPU1在CPU核心上,vCPU2在内存中 - T2时刻,vCPU1在内存中,vCPU2在CPU核心上 在T1和T2之间,操作系统进行了一次**context switch**,也就是上下文切换: - 将vCPU1的状态保存到**Thread Control Block** (TCB, 位于内存) 中。 - 将vCPU2的状态从内存加载到CPU核心上 - 其他操作..... 上下文切换不是毫无开销的。一般来讲,它会耗费几微秒的时间。因此,操作系统会尽量减少上下文切换的次数,并使用各种手段减少上下文切换的开销,以提高系统性能。 有多种条件可以出发一次Content Switch: 计时器、I/O事件、系统调用、线程优先级变化等等。 #### Thread Control Block (TCB) TCB是一些数据,保存了线程的状态信息,包括寄存器值、堆栈指针、程序计数器、PC等。 通常来讲,TCB被存储在内存的kernel空间中。 #### Address Space 地址空间 ![简化的地址空间](/UCB-CS162/images/L02-Simplified-Address-Space.png) ## Reliability 可靠性 简单的上下文切换不提供操作系统保护,而我们显然不会希望一个user program崩掉整个系统!因此,操作系统需要提供一些保护机制。 - 可靠性:破坏操作系统通常会导致其崩溃 - 安全性:限制线程的操作范围 - 隐私性:限制每个线程仅能访问其被允许访问的数据 - 公平性:每个线程应限制在其应得的系统资源份额内(CPU时间、内存、I/O等) 仅靠软件是不足以更好的保护系统的,因此我们有了一些硬件层面的保护机制: ### Base and Bound (B&B) 基址寄存器、边界寄存器 ![Base & Bound image](/UCB-CS162/images/L02-Base-and-Bound.png) 如图所示,程序地址“看起来”位于 0 ~ 100 之间;但,当它被加载到内存内时,它被重新定位到 1000 ~ 1100 之间。 这是一种基于编译器、加载器的保护机制。它把操作系统和用户程序隔离,保护操作系统。 我们也可以通过添加一个加法器来实现更高效的B&B机制: ![B&B Hardware Assistant 1](/UCB-CS162/images/L02-B&B-Hardware-Assistant-1.png) 这是一种硬件辅助的内存重定向。 B&B机制有很多缺点。最显著的缺点之一是,它无法简单地处理用户程序allocate或者free内存的情况。由此,我们有了一个更复杂的机制: ### Address Space Translation 地址空间转换 我们可以通过增添一个特定的“小盒子”,来实现更高效的地址空间转换: ![Address Space Translation](/UCB-CS162/images/L02-Address-Space-Translation-Overview.png) 这个“小盒子”被称为**Memory Management Unit** (MMU),它可以将虚拟地址转换为物理地址。 这个解决方案很好地解决了B&B机制的缺点。用户程序可以allocate或者free内存,而不需要担心地址空间的重叠问题。其中,有一个至今仍然在使用的机制: **Paging** (分页机制)。 ### Paging 分页机制 分页机制将内存划分为固定大小的页(通常是4KB)。每个页都有一个对应的页表项,记录了该页在物理内存中的位置。当程序访问一个虚拟地址时,MMU会查找对应的页表项,将虚拟地址转换为物理地址。 ![Paged Memory](/UCB-CS162/images/L02-Paged-Memory.png) 我们还可以实现一点更“酷”的东西。为了节约内存,我们甚至可以让一些Page实际上不在内存中!对这些page的访问会触发一个**page fault**,程序中断后,操作系统将所需的page从磁盘加载到内存中,然后继续执行程序。 ## Processes 进程 进程是具有受限权限的执行环境。 - (受保护的)具有一个或多个线程的地址空间 - 拥有内存(地址空间) - 拥有文件描述符、文件系统上下文... - 封装一个或多个共享进程资源的线程 复杂的应用程序可以 fork/exec(创建/执行)子进程。 某种意义上来说,进程是一些“城市”,而操作系统是管理他们的“政府”。 - 每个城市都有自己的居民(线程),有自己的资源(内存、文件描述符等)。程序假设它们是独立的,不会相互干扰。 - 政府负责管理这些城市,确保城市A出问题不会波及到城市B、审查城市AB之间的通信、分配硬件资源给城市, etc. ### 为什么需要进程? - 它让不同程序、组件之间相互隔离,不会一个崩溃连带其他崩溃。 - 操作系统免受它们的影响 - 提供内存保护 ### 保护与效率之间的基本权衡 - 进程**内**通信更容易。 - 线程之间可以直接访问共享内存 - 进程**间**通信更难 - 需要使用IPC机制(如管道、消息队列、共享内存等)来进行通信 ### 单线程与多线程 ![Single Thread vs Multi Thread](/UCB-CS162/images/L02-Single-Thread-vs-Multi-Thread.png) 可以从途中看到,各个线程**共享资源**(同一个地址空间、文件描述符等),但每个线程都有**自己的程序计数器、寄存器、堆栈**等。 ## privilege levels 权限级别 上边我们提到了页表。那如果,应用A想要把页的映射地址改到一个它不应该访问的地址上怎么办?我们有了权限级别来解决这个问题。 ### Dual Mode Operation 双模式操作 CPU至少会提供以下两个模式: - User mode (Ring 3) 用户模式:应用程序运行在这个模式下,权限受限,无法直接访问硬件资源。 - Kernel mode (Ring 0) 内核模式:操作系统运行在这个模式下,拥有完全的权限。 由此,我们解决了问题:当操作系统让其他应用程序运行时,它会将CPU切换到User mode。这样子,应用A就不能直接修改页表了。 如果应用程序需要从硬盘读取数据,它会发出一个系统调用(system call),请求操作系统执行这个操作。操作系统会将CPU切换到Kernel mode,执行相应的操作,然后再切换回User mode。 由此,我们得出了经典的UNIX系统架构: ![Simplified Unix System Architecture](/UCB-CS162/images/L02-Simplified-Unix-System-Architecture.png) ### How to switch? 如何切换当前模式? 从用户态到内核态: - ```System call``` 系统调用 - 定义: 进程请求系统服务,例如exit - 系统调用类似于函数调用 - 没有要调用的系统函数的地址 - 类似于远程过程调用 (RPC) - 将系统调用 ID 和参数整理到寄存器中,并执行系统调用 - ```Interrupt``` 中断 - 某些原因触发上下文切换 - 例如:Timer 、I/O 设备 - ```Trap``` 陷阱或```Exception``` 异常 - 进程内部同步事件触发上下文切换 - 例如:```Protection Violation - Segmentation Fault```(段错误)、除以零, etc. 在一次,例如```Interrupt```发生后,CPU会把当前正在运行的程序保存在```Thread Control Block (TCB)```中。TCB中保存了程序计数器、寄存器值、堆栈指针等,并存储在Kernal memory中。