# 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中。