697 lines
26 KiB
Markdown
697 lines
26 KiB
Markdown
# 第1.5章 系统运行机制
|
||
|
||
> [!abstract] 学习目标
|
||
> 1. 理解多进程并发运行的场景及其对硬件支持的需求
|
||
> 2. 掌握中断机制的工作原理,理解时钟中断对操作系统的核心作用
|
||
> 3. 理解 MMU(存储管理部件)如何实现进程隔离与地址转换
|
||
> 4. 掌握 CPU 双模式工作原理(内核模式 vs 用户模式)及特权指令的划分
|
||
> 5. 理解系统调用的完整流程(从用户程序到内核服务)
|
||
> 6. 理解广义中断的分类(外部中断、异常)
|
||
> 7. 能够综合运用上述机制,解释操作系统如何实现多任务并发执行
|
||
|
||
> [!info] 前置知识
|
||
> - 计算机组成原理:CPU、内存、寄存器的基本概念
|
||
> - 二进制与地址的基本概念
|
||
> - 进程的基本概念(参见 [[06_进程控制]])
|
||
|
||
---
|
||
|
||
## 一、为什么需要系统运行机制?
|
||
|
||
### 1.1 多进程并发运行场景
|
||
|
||
在现代操作系统中,多个进程需要**并发**(交替)执行。考虑两个进程 A 和 B:
|
||
|
||
```
|
||
进程 A 的代码: 进程 B 的代码:
|
||
A:1 B:1
|
||
A:2 B:2
|
||
A:3 B:3
|
||
A:4 B:4
|
||
A:5 B:5
|
||
```
|
||
|
||
实际在 CPU 上的执行顺序可能是:
|
||
|
||
```
|
||
时间线 → A:1 B:1 A:2 B:2 A:3 B:3 B:4 A:4 A:5 B:5
|
||
```
|
||
|
||
> [!question] 核心问题
|
||
> 这种交替执行不会自然发生!CPU 只是一个顺序执行指令的"傻瓜机器",它不会自动在 A 和 B 之间切换。要实现多进程并发运行,**必须有硬件和操作系统的支持**。
|
||
|
||
实现多进程并发运行需要以下关键机制:
|
||
|
||
| 机制 | 作用 |
|
||
|------|------|
|
||
| [[#二、中断机制]] | 让 CPU 能够响应外部事件,实现进程切换的"触发器" |
|
||
| [[#三、MMU 存储管理部件]] | 实现进程间的地址隔离,防止互相干扰 |
|
||
| [[#四、CPU 工作模式]] | 区分特权操作,保护操作系统内核不被用户程序破坏 |
|
||
| [[#五、系统调用机制]] | 为用户程序提供受控的内核服务访问途径 |
|
||
|
||
---
|
||
|
||
## 二、中断机制
|
||
|
||
### 2.1 什么是中断?
|
||
|
||
**中断**(Interrupt)是指 CPU 在执行程序的过程中,收到来自外部设备或内部异常的信号,暂停当前程序的执行,转而去处理该事件,处理完毕后再返回原程序继续执行的过程。
|
||
|
||
> [!tip] 生活类比
|
||
> 中断就像你在看书时手机响了(外部中断),你先记住看到哪一页(保存断点),然后去接电话(处理中断),接完电话后回到那一页继续看(恢复执行)。
|
||
|
||
### 2.2 中断处理流程
|
||
|
||
CPU 处理一次中断的完整流程如下:
|
||
|
||
```mermaid
|
||
flowchart TD
|
||
A["CPU 正在执行用户程序"] --> B{"收到中断请求?"}
|
||
B -- "否" --> A
|
||
B -- "是" --> C["关中断(防止嵌套中断)"]
|
||
C --> D["保存断点<br>(PC、PSW 压入内核栈)"]
|
||
D --> E["保存通用寄存器<br>(保存进程现场)"]
|
||
E --> F["识别中断源<br>(查询中断向量表)"]
|
||
F --> G["跳转到中断服务程序<br>(PC ← 中断向量地址)"]
|
||
G --> H["执行中断服务程序<br>(处理具体的中断事件)"]
|
||
H --> I["恢复通用寄存器<br>(恢复进程现场)"]
|
||
I --> J["恢复断点<br>(PC、PSW 出栈)"]
|
||
J --> K["开中断"]
|
||
K --> L["继续执行用户程序"]
|
||
|
||
style A fill:#e1f5fe
|
||
style C fill:#ffcdd2
|
||
style D fill:#ffcdd2
|
||
style G fill:#fff3e0
|
||
style H fill:#fff3e0
|
||
style J fill:#e1f5fe
|
||
```
|
||
|
||
**关键步骤详解**:
|
||
|
||
1. **关中断**:CPU 响应中断后立即关闭中断信号接收,防止在处理一个中断时又被另一个中断打断(中断嵌套)
|
||
2. **保存断点**:将程序计数器(PC)和程序状态字(PSW)压入内核栈,记录"回来后从哪里继续执行"
|
||
3. **保存现场**:将通用寄存器的内容保存到进程的 PCB(进程控制块)中
|
||
4. **识别中断源**:通过中断向量表(Interrupt Vector Table)确定是哪个设备发出的中断
|
||
5. **执行中断服务程序**:跳转到对应的中断处理函数,完成实际的处理工作
|
||
6. **恢复现场和断点**:从 PCB 恢复寄存器,从栈中弹出 PC 和 PSW
|
||
7. **开中断**:重新允许 CPU 接收中断信号
|
||
|
||
### 2.3 时钟中断 — 操作系统的"心跳"
|
||
|
||
> [!important] 时钟中断的核心地位
|
||
> **时钟中断**(Timer Interrupt)是由实时时钟(RTC,Real-Time Clock)硬件周期性产生的中断,是操作系统实现进程管理的最关键机制。
|
||
|
||
时钟中断的工作原理:
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant RTC as 实时时钟 (RTC)
|
||
participant CPU
|
||
participant OS as 操作系统
|
||
participant PA as 进程 A
|
||
participant PB as 进程 B
|
||
|
||
PA->>CPU: 正在执行进程 A
|
||
RTC->>CPU: 时钟中断(每 10ms)
|
||
CPU->>OS: 保存 A 的现场,进入内核
|
||
OS->>OS: 检查:A 的时间片用完了吗?
|
||
OS->>CPU: 切换到进程 B
|
||
CPU->>PB: 恢复 B 的现场,执行进程 B
|
||
RTC->>CPU: 时钟中断(每 10ms)
|
||
CPU->>OS: 保存 B 的现场,进入内核
|
||
OS->>CPU: 切换到进程 A
|
||
CPU->>PA: 恢复 A 的现场,执行进程 A
|
||
```
|
||
|
||
**时钟中断频率**:通常为 **10ms 一次**(即每秒 100 次),也称为 **时钟滴答**(Clock Tick)。
|
||
|
||
> [!warning] 没有时钟中断会怎样?
|
||
> 如果没有时钟中断,CPU 一旦开始执行某个进程,就永远不会主动让出 CPU。操作系统将无法实现"分时",也就无法让多个进程看起来在"同时"运行。时钟中断是操作系统实现**抢占式调度**的基础。
|
||
|
||
### 2.4 中断对操作系统的意义
|
||
|
||
中断机制对操作系统具有根本性的意义:
|
||
|
||
1. **实现进程并发执行**:时钟中断迫使 CPU 周期性地将控制权交还给操作系统,操作系统可以决定切换到哪个进程
|
||
2. **实现 I/O 管理**:设备完成 I/O 操作后通过中断通知 CPU,CPU 可以将等待该 I/O 的进程唤醒
|
||
3. **实现系统调用**:用户程序通过软中断指令(如 `int $0x80`)请求内核服务
|
||
4. **异常处理**:CPU 内部发生的异常(如除零、缺页)通过中断机制通知操作系统处理
|
||
|
||
---
|
||
|
||
## 三、MMU(存储管理部件)
|
||
|
||
### 3.1 为什么需要 MMU?
|
||
|
||
> [!question] 核心问题
|
||
> 如果没有地址隔离,进程 A 可以直接读写进程 B 的内存空间,甚至可以修改操作系统内核的代码!这是绝对不允许的。
|
||
|
||
**没有 MMU 时**:程序中使用的地址 = 物理地址
|
||
|
||
```
|
||
进程 A: MOV [0x1000], 1 → 直接写物理地址 0x1000
|
||
进程 B: MOV [0x1000], 2 → 也写物理地址 0x1000 !冲突!
|
||
```
|
||
|
||
**有 MMU 时**:程序中使用的地址是**逻辑地址**(虚拟地址),经过 MMU 转换后才是**物理地址**
|
||
|
||
```
|
||
进程 A: MOV [0x1000], 1 → MMU 转换 → 物理地址 0x5000
|
||
进程 B: MOV [0x1000], 2 → MMU 转换 → 物理地址 0x8000 ✓ 不冲突
|
||
```
|
||
|
||
### 3.2 MMU 地址转换原理
|
||
|
||
MMU 通过**地址变换函数**将不同进程的逻辑地址映射到不同的物理地址区域:
|
||
|
||
```mermaid
|
||
flowchart LR
|
||
subgraph 进程A["进程 A"]
|
||
LA["逻辑地址空间<br>0x0000 ~ 0xFFFF"]
|
||
end
|
||
|
||
subgraph MMU_Block["MMU(存储管理部件)"]
|
||
TR1["地址变换函数 tr1()"]
|
||
end
|
||
|
||
subgraph PA_Phys["物理内存"]
|
||
PA_M["区域 1<br>物理地址 0x5000 ~ 0x14FFF"]
|
||
end
|
||
|
||
LA -->|"逻辑地址 0x1000"| TR1
|
||
TR1 -->|"物理地址 0x6000"| PA_M
|
||
|
||
style LA fill:#e1f5fe
|
||
style TR1 fill:#fff3e0
|
||
style PA_M fill:#e8f5e9
|
||
```
|
||
|
||
```mermaid
|
||
flowchart LR
|
||
subgraph 进程B["进程 B"]
|
||
LB["逻辑地址空间<br>0x0000 ~ 0xFFFF"]
|
||
end
|
||
|
||
subgraph MMU_Block2["MMU(存储管理部件)"]
|
||
TR2["地址变换函数 tr2()"]
|
||
end
|
||
|
||
subgraph PB_Phys["物理内存"]
|
||
PB_M["区域 2<br>物理地址 0x8000 ~ 0x17FFF"]
|
||
end
|
||
|
||
LB -->|"逻辑地址 0x1000"| TR2
|
||
TR2 -->|"物理地址 0x9000"| PB_M
|
||
|
||
style LB fill:#e1f5fe
|
||
style TR2 fill:#fff3e0
|
||
style PB_M fill:#e8f5e9
|
||
```
|
||
|
||
**关键概念**:
|
||
|
||
| 术语 | 含义 |
|
||
|------|------|
|
||
| **逻辑地址**(虚拟地址) | 程序中使用的地址,每个进程从 0 开始编址 |
|
||
| **物理地址** | 实际内存硬件上的地址,全局唯一 |
|
||
| **地址变换函数** | MMU 中的映射规则,每个进程有独立的变换函数 |
|
||
|
||
> [!tip] 进程隔离的本质
|
||
> 每个进程都有自己独立的**逻辑地址空间**,通过 MMU 映射到物理内存中互不重叠的区域。进程 A 无法访问进程 B 的物理内存区域,从而实现了**进程隔离**。
|
||
|
||
### 3.3 页表与 MMU 保护机制
|
||
|
||
在现代操作系统中,MMU 通常通过**页表**(Page Table)实现地址转换。页表中除了地址映射信息外,还包含保护位:
|
||
|
||
**页表项中的 U/S 位**:
|
||
|
||
| U/S 位值 | 含义 | 访问权限 |
|
||
|----------|------|----------|
|
||
| 0 | Supervisor(内核页) | 仅内核模式可访问 |
|
||
| 1 | User(用户页) | 内核模式和用户模式均可访问 |
|
||
|
||
**CPU 标志寄存器中的 IOPL 位**:
|
||
|
||
- `IOPL = 0` 或 `1`:用户模式(受限)
|
||
- `IOPL = 3`:内核模式(拥有全部 I/O 权限)
|
||
|
||
**保护机制的工作方式**:
|
||
|
||
```mermaid
|
||
flowchart TD
|
||
A["CPU 执行指令,访问逻辑地址"] --> B["MMU 查页表"]
|
||
B --> C{"当前 CPU 模式?"}
|
||
C -- "用户模式" --> D{"页表 U/S 位?"}
|
||
D -- "U/S = 1(用户页)" --> E["允许访问 ✓"]
|
||
D -- "U/S = 0(内核页)" --> F["触发保护异常 ✗<br>终止进程"]
|
||
C -- "内核模式" --> G["允许访问所有页面 ✓"]
|
||
|
||
style E fill:#c8e6c9
|
||
style F fill:#ffcdd2
|
||
style G fill:#c8e6c9
|
||
```
|
||
|
||
### 3.4 Linux 地址空间划分
|
||
|
||
操作系统将每个进程的逻辑地址空间划分为**用户空间**和**内核空间**:
|
||
|
||
| 操作系统 | 用户空间 | 内核空间 |
|
||
|----------|----------|----------|
|
||
| **32 位 Linux** | 低 3GB(0x00000000 ~ 0xBFFFFFFF) | 高 1GB(0xC0000000 ~ 0xFFFFFFFF) |
|
||
| **32 位 Windows** | 低 2GB(0x00000000 ~ 0x7FFFFFFF) | 高 2GB(0x80000000 ~ 0xFFFFFFFF) |
|
||
|
||
> [!info] 为什么内核空间在高地址?
|
||
> 将内核空间放在高地址是一种约定。在 Linux 中,所有进程共享同一个内核空间映射(内核只有一份),但每个进程的用户空间是独立的。当 CPU 处于用户模式时,访问内核空间会触发保护异常。
|
||
|
||
> [!tip] 相关链接
|
||
> 关于存储管理的更多内容,参见 [[13_存储管理基础]] 和 [[14_分页存储管理]]。
|
||
|
||
---
|
||
|
||
## 四、CPU 工作模式
|
||
|
||
### 4.1 双模式设计
|
||
|
||
现代 CPU 设计了两种工作模式,用于区分"谁可以执行什么指令":
|
||
|
||
| 模式 | 别名 | 权限 | 使用者 |
|
||
|------|------|------|--------|
|
||
| **内核模式** | 系统态、核心态、管态 | 可执行所有指令,访问所有内存 | 操作系统内核 |
|
||
| **用户模式** | 用户态、目态 | 只能执行非特权指令,不能访问内核空间 | 用户程序 |
|
||
|
||
> [!warning] 为什么需要两种模式?
|
||
> 如果所有程序都运行在内核模式,任何一个用户程序都可以:修改操作系统代码、直接操作硬件、访问其他进程的内存。这将导致系统完全不安全。双模式设计是操作系统**自我保护**的基础。
|
||
|
||
### 4.2 特权指令 vs 非特权指令
|
||
|
||
| 分类 | 特点 | 示例 |
|
||
|------|------|------|
|
||
| **特权指令** | 只能在内核模式下执行 | I/O 指令、停机指令(HLT)、特殊寄存器访问(如修改页表基址寄存器) |
|
||
| **非特权指令** | 在任何模式下均可执行 | 算术逻辑运算(ADD、SUB)、寄存器操作(MOV)、内存访问(普通读写) |
|
||
|
||
> [!warning] 用户模式执行特权指令会怎样?
|
||
> 如果 CPU 处于用户模式时执行了特权指令,CPU 会立即触发一个**保护异常**(Protection Exception),操作系统会终止该进程(通常表现为 "Segmentation Fault")。
|
||
|
||
### 4.3 不同架构的模式标识
|
||
|
||
CPU 通过标志寄存器中的特定位来标识当前工作模式:
|
||
|
||
**x86 架构**:使用 FLAGS 寄存器中的 **IOPL**(I/O Privilege Level)字段
|
||
|
||
```
|
||
FLAGS 寄存器:
|
||
┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐
|
||
│ ... │ IOPL│ IOPL│ ... │ IF │ ... │ ZF │ CF │
|
||
│ │ (13)│ (12)│ │ (9) │ │ (6) │ (0) │
|
||
└─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
|
||
├───IOPL───┤
|
||
00 = Ring 0(内核模式)
|
||
11 = Ring 3(用户模式)
|
||
```
|
||
|
||
**ARM 架构**:使用 CPSR(Current Program Status Register)中的 **M[4:0]** 位
|
||
|
||
```
|
||
CPSR 寄存器:
|
||
┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐
|
||
│ ... │ M4 │ M3 │ M2 │ M1 │ M0 │ ... │ │
|
||
└─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
|
||
├─── M[4:0] ───┤
|
||
10000 = User(用户模式)
|
||
10011 = Supervisor(内核模式)
|
||
10111 = Abort
|
||
11011 = Undefined
|
||
10010 = IRQ
|
||
10001 = FIQ
|
||
```
|
||
|
||
### 4.4 CPU 模式切换
|
||
|
||
CPU 模式切换发生在以下场景:
|
||
|
||
```mermaid
|
||
flowchart TD
|
||
subgraph 用户模式区["用户模式(User Mode)"]
|
||
A["执行用户程序"]
|
||
B["执行非特权指令"]
|
||
end
|
||
|
||
subgraph 内核模式区["内核模式(Kernel Mode)"]
|
||
C["处理中断/异常"]
|
||
D["执行系统调用"]
|
||
E["执行特权指令"]
|
||
end
|
||
|
||
A -->|"中断/异常发生<br>(时钟中断、I/O中断、缺页等)"| C
|
||
A -->|"系统调用<br>(int $0x80 / SWI)"| D
|
||
C -->|"中断返回<br>(IRET)"| A
|
||
D -->|"系统调用返回<br>(IRET)"| A
|
||
E -->|"设置 IOPL/M[4:0]"| A
|
||
|
||
style A fill:#e1f5fe
|
||
style B fill:#e1f5fe
|
||
style C fill:#fff3e0
|
||
style D fill:#fff3e0
|
||
style E fill:#fff3e0
|
||
```
|
||
|
||
**用户模式 → 内核模式**(两种途径):
|
||
|
||
1. **中断/异常**:硬件自动切换
|
||
- 时钟中断、I/O 中断
|
||
- 缺页异常、除零异常
|
||
- 系统调用(软中断)
|
||
|
||
2. **系统调用**:用户程序主动发起
|
||
- x86:执行 `int $0x80` 指令
|
||
- ARM:执行 `SWI` 指令
|
||
|
||
**内核模式 → 用户模式**:
|
||
|
||
- 执行中断返回指令(x86: `IRET`),恢复用户模式的状态
|
||
|
||
---
|
||
|
||
## 五、系统调用机制
|
||
|
||
### 5.1 什么是系统调用?
|
||
|
||
**系统调用**(System Call)是操作系统提供给用户程序的**唯一合法途径**,用于请求内核提供的服务。
|
||
|
||
> [!tip] 生活类比
|
||
> 系统调用就像银行柜台:你(用户程序)不能自己进入金库(内核空间)取钱,但你可以通过柜台窗口(系统调用接口)告诉工作人员(内核)你要做什么,工作人员帮你完成后再把结果交给你。
|
||
|
||
### 5.2 系统调用的必要性
|
||
|
||
用户程序**不能直接调用**内核函数,原因:
|
||
|
||
1. **地址隔离**:内核函数位于内核空间,用户模式无法访问
|
||
2. **权限保护**:内核函数可能执行特权操作(如 I/O),用户模式无权执行
|
||
3. **安全控制**:内核需要验证参数的合法性,防止恶意调用
|
||
|
||
### 5.3 系统调用的完整流程
|
||
|
||
以 `printf("Hello")` 为例,完整调用链如下:
|
||
|
||
```mermaid
|
||
flowchart TD
|
||
A["用户程序<br>printf(#quot;Hello#quot;)"] --> B["C 库函数<br>write(1, #quot;Hello#quot;, 5)"]
|
||
B --> C["系统调用包装函数<br>将参数写入寄存器<br>EAX=4 (sys_write 号)<br>EBX=1 (文件描述符)<br>ECX=缓冲区地址<br>EDX=5 (长度)"]
|
||
C --> D["软中断指令<br>int $0x80"]
|
||
D --> E["CPU 模式切换<br>用户模式 → 内核模式<br>保存断点和现场"]
|
||
E --> F["查询系统调用表<br>根据 EAX=4 找到<br>sys_write 函数地址"]
|
||
F --> G["执行内核函数<br>sys_write()<br>实际完成屏幕输出"]
|
||
G --> H["系统调用返回<br>恢复现场<br>内核模式 → 用户模式"]
|
||
H --> I["返回用户程序<br>继续执行"]
|
||
|
||
style A fill:#e1f5fe
|
||
style B fill:#e1f5fe
|
||
style C fill:#e1f5fe
|
||
style D fill:#ffcdd2
|
||
style E fill:#fff3e0
|
||
style F fill:#fff3e0
|
||
style G fill:#fff3e0
|
||
style H fill:#ffcdd2
|
||
style I fill:#e1f5fe
|
||
```
|
||
|
||
### 5.4 系统调用的参数传递方式
|
||
|
||
用户程序通过**寄存器**将参数传递给内核:
|
||
|
||
**x86 架构的系统调用参数寄存器**:
|
||
|
||
| 寄存器 | 用途 |
|
||
|--------|------|
|
||
| **EAX** | 系统调用号(标识调用哪个内核函数) |
|
||
| **EBX** | 第 1 个参数 |
|
||
| **ECX** | 第 2 个参数 |
|
||
| **EDX** | 第 3 个参数 |
|
||
| **ESI** | 第 4 个参数 |
|
||
| **EDI** | 第 5 个参数 |
|
||
| **EBP** | 第 6 个参数 |
|
||
|
||
> [!info] `int $0x80` 指令的作用
|
||
> `int $0x80` 是 x86 的**软中断指令**(Software Interrupt)。执行该指令时,CPU 会:
|
||
> 1. 自动将 FLAGS、CS、IP 压入内核栈(保存断点)
|
||
> 2. 将 CPU 模式切换为内核模式
|
||
> 3. 从中断向量表的第 0x80 号表项取出中断服务程序入口地址
|
||
> 4. 跳转到该地址开始执行(即系统调用处理函数 `system_call`)
|
||
|
||
### 5.5 系统调用的底层实现
|
||
|
||
```asm
|
||
; x86 Linux 系统调用的底层实现示意
|
||
|
||
; 用户程序部分(C 库包装)
|
||
mov eax, 4 ; 系统调用号 = 4 (sys_write)
|
||
mov ebx, 1 ; 文件描述符 = 1 (stdout)
|
||
mov ecx, msg ; 缓冲区地址
|
||
mov edx, 5 ; 字节数
|
||
int 0x80 ; 触发软中断,进入内核
|
||
|
||
; 内核部分(system_call)
|
||
system_call:
|
||
; 1. 保存所有寄存器到内核栈
|
||
push eax
|
||
push ebx
|
||
push ecx
|
||
push edx
|
||
; ...
|
||
|
||
; 2. 根据 EAX 的值查系统调用表
|
||
call [sys_call_table + eax*4]
|
||
|
||
; 3. 返回值放在 EAX 中
|
||
mov [esp + 恢复位置], eax
|
||
|
||
; 4. 恢复寄存器,返回用户模式
|
||
pop edx
|
||
pop ecx
|
||
pop ebx
|
||
pop eax
|
||
iret
|
||
```
|
||
|
||
### 5.6 常见的系统调用
|
||
|
||
| 系统调用号 | 函数名 | 功能 |
|
||
|-----------|--------|------|
|
||
| 1 | `sys_exit` | 终止进程 |
|
||
| 2 | `sys_fork` | 创建子进程 |
|
||
| 3 | `sys_read` | 读文件 |
|
||
| 4 | `sys_write` | 写文件 |
|
||
| 5 | `sys_open` | 打开文件 |
|
||
| 6 | `sys_close` | 关闭文件 |
|
||
|
||
---
|
||
|
||
## 六、异常(广义中断)
|
||
|
||
### 6.1 广义中断的分类
|
||
|
||
在操作系统中,**广义的中断**包括两大类:
|
||
|
||
```mermaid
|
||
flowchart TD
|
||
A["广义中断"] --> B["外部中断<br>(硬件中断)"]
|
||
A --> C["异常<br>(内部中断)"]
|
||
|
||
B --> B1["I/O 中断<br>键盘、鼠标、网卡等设备发出"]
|
||
B --> B2["时钟中断<br>RTC 周期性发出"]
|
||
|
||
C --> C1["系统调用<br>用户执行 int $0x80 / SWI"]
|
||
C --> C2["缺页异常<br>访问的页面不在内存中"]
|
||
C --> C3["断点指令<br>调试器设置的断点"]
|
||
C --> C4["算术溢出<br>除零错误等"]
|
||
|
||
style A fill:#fff3e0
|
||
style B fill:#e1f5fe
|
||
style C fill:#ffcdd2
|
||
```
|
||
|
||
### 6.2 外部中断 vs 异常
|
||
|
||
| 特征 | 外部中断 | 异常 |
|
||
|------|----------|------|
|
||
| **来源** | CPU 外部设备 | CPU 内部执行指令时产生 |
|
||
| **发生时机** | 与当前指令无关,随机发生 | 与当前指令直接相关 |
|
||
| **是否可屏蔽** | 可屏蔽中断(INTR)可被 IF 位屏蔽 | 不可屏蔽 |
|
||
| **典型例子** | I/O 中断、时钟中断 | 缺页、除零、系统调用 |
|
||
|
||
### 6.3 常见异常类型
|
||
|
||
| 异常类型 | 触发条件 | 处理方式 |
|
||
|----------|----------|----------|
|
||
| **系统调用** | 执行 `int $0x80` 或 `SWI` | 查系统调用表,执行对应内核函数 |
|
||
| **缺页异常** | 访问的虚拟页面不在物理内存中 | 从磁盘调入页面(参见 [[14_分页存储管理]]) |
|
||
| **断点指令** | 调试器在代码中设置断点 | 暂停程序,将控制权交给调试器 |
|
||
| **算术溢出** | 除零、溢出等运算错误 | 终止进程或通知进程处理 |
|
||
| **保护异常** | 用户模式执行特权指令或访问内核空间 | 终止进程(Segmentation Fault) |
|
||
|
||
---
|
||
|
||
## 七、多任务并发执行的综合场景
|
||
|
||
### 7.1 操作系统如何实现多任务并发
|
||
|
||
操作系统通过**中断驱动**的方式实现多任务并发执行。以下是几种典型的触发场景:
|
||
|
||
```mermaid
|
||
flowchart TD
|
||
A["CPU 正在执行进程 P1"] --> B{"发生了什么?"}
|
||
|
||
B -->|"时钟中断(每 10ms)"| C["进入内核<br>调度器决定是否切换进程"]
|
||
B -->|"系统调用"| D["进入内核<br>执行内核函数"]
|
||
B -->|"I/O 请求"| E["进程 P1 等待 I/O<br>CPU 分配给其他进程"]
|
||
B -->|"进程结束"| F["进程 P1 终止<br>CPU 分配给其他进程"]
|
||
|
||
C --> G{"调度器决策"}
|
||
G -->|"继续执行 P1"| A
|
||
G -->|"切换到 P2"| H["保存 P1 现场<br>恢复 P2 现场<br>执行进程 P2"]
|
||
|
||
D --> I{"系统调用是否阻塞?"}
|
||
I -->|"不阻塞"| A
|
||
I -->|"需要等待"| E
|
||
|
||
E --> J["P1 进入等待队列<br>CPU 执行进程 P2"]
|
||
F --> K["回收 P1 资源<br>CPU 执行就绪队列中的进程"]
|
||
|
||
style A fill:#e1f5fe
|
||
style C fill:#fff3e0
|
||
style D fill:#fff3e0
|
||
style E fill:#ffcdd2
|
||
style F fill:#ffcdd2
|
||
```
|
||
|
||
### 7.2 四种典型场景详解
|
||
|
||
**场景一:时钟中断触发进程切换**
|
||
|
||
```
|
||
时间线:
|
||
P1 执行 → [时钟中断] → OS 调度 → P2 执行 → [时钟中断] → OS 调度 → P1 执行
|
||
↑ 10ms ↑ 10ms
|
||
```
|
||
|
||
**场景二:系统调用触发内核执行**
|
||
|
||
```
|
||
P1 执行 printf → [int $0x80] → 内核执行 sys_write → [iret] → P1 继续执行
|
||
```
|
||
|
||
**场景三:I/O 等待导致 CPU 重分配**
|
||
|
||
```
|
||
P1 请求读磁盘 → P1 进入等待队列 → CPU 执行 P2 → 磁盘中断 → P1 就绪 → P1 继续执行
|
||
```
|
||
|
||
**场景四:进程结束释放 CPU**
|
||
|
||
```
|
||
P1 执行 exit() → P1 终止 → OS 回收资源 → CPU 执行就绪队列中的 P2
|
||
```
|
||
|
||
> [!important] 中断是操作系统的"发动机"
|
||
> 可以说,**没有中断就没有操作系统**。中断机制是操作系统实现进程管理、I/O 管理、内存管理等所有核心功能的基础。时钟中断让 OS 能够"定期检查"系统状态,I/O 中断让 OS 能够响应设备事件,系统调用让 OS 能够为用户程序提供服务。
|
||
|
||
---
|
||
|
||
## 八、机制之间的关系
|
||
|
||
理解了各个机制后,需要看到它们之间的协同关系:
|
||
|
||
```mermaid
|
||
flowchart TD
|
||
subgraph 硬件支持
|
||
CLK["时钟中断<br>(RTC 硬件)"]
|
||
MMU_HW["MMU 硬件<br>(地址转换)"]
|
||
CPU_MODE["CPU 双模式<br>(IOPL/M[4:0])"]
|
||
INT_HW["中断控制器<br>(PIC/APIC)"]
|
||
end
|
||
|
||
subgraph OS核心功能
|
||
PROC["进程管理<br>(调度、切换)"]
|
||
MEM["内存管理<br>(地址隔离)"]
|
||
PROT["系统保护<br>(内核安全)"]
|
||
IO["I/O 管理<br>(设备驱动)"]
|
||
end
|
||
|
||
CLK -->|"触发进程切换"| PROC
|
||
MMU_HW -->|"地址转换 + 保护"| MEM
|
||
CPU_MODE -->|"特权级隔离"| PROT
|
||
INT_HW -->|"设备事件通知"| IO
|
||
|
||
PROC -->|"选择下一个进程"| MEM
|
||
MEM -->|"提供地址空间"| PROC
|
||
PROT -->|"限制用户操作"| PROC
|
||
|
||
style CLK fill:#ffcdd2
|
||
style MMU_HW fill:#ffcdd2
|
||
style CPU_MODE fill:#ffcdd2
|
||
style INT_HW fill:#ffcdd2
|
||
style PROC fill:#e1f5fe
|
||
style MEM fill:#e1f5fe
|
||
style PROT fill:#e1f5fe
|
||
style IO fill:#e1f5fe
|
||
```
|
||
|
||
> [!tip] 核心要点
|
||
> 操作系统的四大运行机制(中断、MMU、CPU 模式、系统调用)不是孤立的,而是相互配合:
|
||
> - **中断**提供了"进入内核"的途径
|
||
> - **CPU 模式**确保了"谁能做什么"
|
||
> - **MMU** 保证了"谁的内存谁用"
|
||
> - **系统调用**提供了"受控的内核访问"
|
||
|
||
---
|
||
|
||
## 思考与练习
|
||
|
||
以下是 PPT 中给出的复习思考题:
|
||
|
||
1. **什么是逻辑地址、物理地址?什么是内核空间、用户空间?**
|
||
- 提示:逻辑地址是程序中使用的地址,物理地址是内存硬件的实际地址;内核空间是操作系统运行的区域,用户空间是应用程序运行的区域
|
||
|
||
2. **MMU 的功能是什么,如何实现进程隔离?**
|
||
- 提示:MMU 负责逻辑地址到物理地址的转换;每个进程有独立的地址变换函数,映射到不同的物理区域
|
||
|
||
3. **特权指令与非特权指令的特点?**
|
||
- 提示:特权指令只能在内核模式执行(如 I/O 指令、停机指令),非特权指令在任何模式都可执行(如算术运算)
|
||
|
||
4. **两种 CPU 工作模式的特点?**
|
||
- 提示:内核模式可执行所有指令、访问所有内存;用户模式只能执行非特权指令、不能访问内核空间
|
||
|
||
5. **如何利用 MMU 与工作模式防止用户程序破坏 OS?**
|
||
- 提示:MMU 通过页表 U/S 位将内核页标记为仅内核模式可访问;CPU 在用户模式执行特权指令会触发保护异常
|
||
|
||
6. **CPU 模式切换方法?**
|
||
- 提示:用户→内核:中断/异常/系统调用(硬件自动切换);内核→用户:执行 IRET 指令
|
||
|
||
7. **系统调用的作用、原理和实现过程?**
|
||
- 提示:作用是让安全地访问内核服务;原理是通过软中断指令触发模式切换;实现过程是参数通过寄存器传递,查系统调用表找到对应函数执行
|
||
|
||
8. **时钟中断对操作系统的作用?**
|
||
- 提示:时钟中断是操作系统实现进程并发执行的基础,它迫使 CPU 周期性地将控制权交还给 OS,使 OS 能够进行进程调度
|
||
|
||
9. **中断对操作系统的意义?**
|
||
- 提示:中断是操作系统的"发动机",是实现进程管理、I/O 管理、系统调用等所有核心功能的基础
|
||
|
||
---
|
||
|
||
## 相关笔记
|
||
|
||
- [[02_Linux基础]] — Linux 系统的基本使用
|
||
- [[06_进程控制]] — 进程的创建、终止与管理
|
||
- [[11_处理机调度]] — 进程调度算法(时钟中断的实际应用)
|
||
- [[13_存储管理基础]] — 存储管理的基本概念
|
||
- [[14_分页存储管理]] — 分页存储与 MMU 的详细实现
|
||
|
||
---
|
||
|
||
## 参考资料
|
||
|
||
- 《操作系统概念》(Operating System Concepts)第1章、第2章
|
||
- 《深入理解计算机系统》(CSAPP)第1章、第9章
|
||
- 《现代操作系统》(Modern Operating Systems)第2章
|