304 lines
11 KiB
Markdown
304 lines
11 KiB
Markdown
# 13. 存储管理基础
|
||
|
||
> **课程**: 操作系统 - 存储器管理
|
||
> **核心内容**: 存储器层次结构、存储管理功能、程序编译链接与装入、地址空间
|
||
|
||
---
|
||
|
||
## 一、存储器层次结构
|
||
|
||
存储器按照速度和容量形成层次结构,越靠近CPU速度越快但容量越小、价格越高。
|
||
|
||
```
|
||
速度递增 ↑ 容量递减 ↑ 价格递增 ↑
|
||
┌─────────────────────────┐
|
||
│ 寄存器(Register) │ ← 最快,纳秒级,几十~几百字节
|
||
├─────────────────────────┤
|
||
│ 高速缓存(Cache) │ ← L1/L2/L3,几MB
|
||
├─────────────────────────┤
|
||
│ 主存(内存/DRAM) │ ← 几GB~几百GB,百纳秒级
|
||
├─────────────────────────┤
|
||
│ Flash/SSD │ ← 固态存储
|
||
├─────────────────────────┤
|
||
│ 磁盘缓存(Disk Cache) │
|
||
├─────────────────────────┤
|
||
│ 固定磁盘(HDD/SSD) │ ← 几百GB~几TB,毫秒级
|
||
├─────────────────────────┤
|
||
│ 可移动存储(U盘/光盘/磁带) │ ← 最慢,容量可很大
|
||
└─────────────────────────┘
|
||
```
|
||
|
||
**设计原则**: 利用**局部性原理**,将频繁访问的数据放在高速层次,较少访问的数据放在低速大容量层次。
|
||
|
||
---
|
||
|
||
## 二、存储管理的功能
|
||
|
||
操作系统存储管理需要实现以下五大核心功能:
|
||
|
||
| 功能 | 说明 |
|
||
|------|------|
|
||
| **内存分配与回收** | 为进程分配所需内存,进程结束后回收内存 |
|
||
| **地址转换** | 将程序中的逻辑地址转换为物理地址([[14_分页存储管理\|分页]]、[[15_段式存储管理\|分段]]) |
|
||
| **内存共享** | 多个进程共享同一段代码(如共享库) |
|
||
| **内存保护** | 防止进程越界访问其他进程或内核的内存区域 |
|
||
| **内存扩充** | 通过虚拟存储技术,使程序可用空间大于实际物理内存([[16_虚拟存储器\|虚拟存储器]]) |
|
||
|
||
---
|
||
|
||
## 三、程序的编译与链接过程
|
||
|
||
一个C语言源文件从编写到可执行,经历以下阶段:
|
||
|
||
```mermaid
|
||
flowchart LR
|
||
A["hello.c<br/>源文件"] -->|预处理| B["hello.i<br/>预处理后"]
|
||
B -->|编译| C["hello.s<br/>汇编文件"]
|
||
C -->|汇编| D["hello.o<br/>目标文件(可重定位)"]
|
||
D -->|链接| E["hello / a.out<br/>可执行文件"]
|
||
E -->|装入| F["内存中运行的进程"]
|
||
|
||
style A fill:#e1f5fe
|
||
style C fill:#fff3e0
|
||
style D fill:#fce4ec
|
||
style E fill:#e8f5e9
|
||
style F fill:#f3e5f5
|
||
```
|
||
|
||
### 各阶段说明
|
||
|
||
| 阶段 | 输入 | 输出 | 工具 | 说明 |
|
||
|------|------|------|------|------|
|
||
| **预处理** | `.c` | `.i` | cpp | 展开宏、头文件、条件编译 |
|
||
| **编译** | `.i` | `.s` | cc1 | 翻译为汇编语言 |
|
||
| **汇编** | `.s` | `.o` | as | 翻译为机器指令(可重定位目标文件) |
|
||
| **链接** | `.o` | 可执行文件 | ld | 合并节段、解析符号引用、重定位 |
|
||
| **装入** | 可执行文件 | 进程 | 加载器 | 将程序载入内存并创建进程 |
|
||
|
||
### 查看目标文件的节段
|
||
|
||
使用 `objdump -h` 可以查看可执行文件或目标文件中的节段(Section)信息:
|
||
|
||
```bash
|
||
gcc -c hello.c # 编译为可重定位目标文件
|
||
objdump -h hello.o # 查看节段头信息
|
||
|
||
gcc hello.c -o hello # 链接为可执行文件
|
||
objdump -h hello # 查看可执行文件的节段
|
||
```
|
||
|
||
---
|
||
|
||
## 四、可执行文件结构
|
||
|
||
可执行文件在内存中从低地址到高地址的典型布局如下:
|
||
|
||
```
|
||
高地址 ┌──────────────────┐
|
||
│ 命令行参数 │
|
||
│ 和环境变量 │
|
||
├──────────────────┤
|
||
│ 栈(Stack) │ ← 局部变量、函数调用帧,向下增长 ↓
|
||
│ ↓ │
|
||
│ │
|
||
│ ↑ │
|
||
│ 堆(Heap) │ ← malloc/new动态分配,向上增长 ↑
|
||
├──────────────────┤
|
||
│ .bss 段 │ ← 未初始化的全局/静态变量(不占文件空间)
|
||
├──────────────────┤
|
||
│ .data 段 │ ← 已初始化的全局变量和静态变量
|
||
├──────────────────┤
|
||
│ .rodata 段 │ ← 只读数据(如字符串常量)
|
||
├──────────────────┤
|
||
│ .text 段 │ ← 可执行代码(机器指令)
|
||
低地址 └──────────────────┘
|
||
```
|
||
|
||
| 段名 | 内容 | 是否可写 | 说明 |
|
||
|------|------|---------|------|
|
||
| `.text` | 机器指令代码 | 只读/只执行 | 程序的可执行代码 |
|
||
| `.rodata` | 只读数据 | 只读 | 字符串常量、`const`变量 |
|
||
| `.data` | 已初始化全局变量 | 可读写 | 有初始值的全局和静态变量 |
|
||
| `.bss` | 未初始化全局变量 | 可读写 | 不占文件空间,装入时清零 |
|
||
| 堆(Heap) | 动态分配内存 | 可读写 | `malloc`/`new` 分配 |
|
||
| 栈(Stack) | 函数调用帧 | 可读写 | 局部变量、返回地址等 |
|
||
|
||
---
|
||
|
||
## 五、逻辑地址与物理地址
|
||
|
||
### 核心概念
|
||
|
||
| 术语 | 别名 | 说明 |
|
||
|------|------|------|
|
||
| **逻辑地址** | 虚拟地址(VA)、相对地址 | 程序中使用的地址,从0开始编址 |
|
||
| **物理地址** | PA、绝对地址 | 内存硬件中的实际地址 |
|
||
|
||
> **关键**: 程序中的地址(逻辑地址)**不等于**内存中的实际地址(物理地址)。地址转换由硬件([[01_系统运行机制#四、存储器管理硬件——MMU|MMU]])和操作系统配合完成。
|
||
|
||
### 逻辑地址空间与物理地址空间
|
||
|
||
```mermaid
|
||
flowchart TB
|
||
subgraph VA["逻辑地址空间 (虚拟)"]
|
||
direction TB
|
||
V0["地址 0"]
|
||
V1["..."]
|
||
V2["地址 2^v - 1"]
|
||
end
|
||
|
||
subgraph PA["物理地址空间 (实际)"]
|
||
direction TB
|
||
P0["地址 0"]
|
||
P1["..."]
|
||
P2["地址 2^p - 1"]
|
||
end
|
||
|
||
VA -->|"地址映射<br/>(页表/段表)"| PA
|
||
|
||
style VA fill:#e3f2fd,stroke:#1976d2
|
||
style PA fill:#fff8e1,stroke:#f9a825
|
||
```
|
||
|
||
- **虚拟地址空间**: 大小为 $2^v$ 字节,其中 $v$ 是虚拟地址的位数
|
||
- **物理地址空间**: 大小为 $2^p$ 字节,其中 $p$ 是物理地址的位数
|
||
- 通常 $v \geq p$(虚拟地址空间可以大于物理内存),这就是[[16_虚拟存储器|虚拟存储器]]的基础
|
||
|
||
### 引入逻辑地址的好处
|
||
|
||
1. **进程隔离**: 每个进程拥有独立的虚拟地址空间,互不干扰
|
||
2. **提高内存利用率**: 可以使用[[16_虚拟存储器|虚拟存储器]]技术
|
||
3. **内存保护**: 通过地址转换实现访问权限控制
|
||
|
||
---
|
||
|
||
## 六、内核空间与用户空间
|
||
|
||
操作系统将每个进程的虚拟地址空间划分为**用户空间**和**内核空间**两部分:
|
||
|
||
```mermaid
|
||
block-beta
|
||
columns 1
|
||
block:linux["Linux 进程地址空间 (4GB)"]
|
||
columns 1
|
||
block:kernel_linux["内核空间 (高 1GB)"]
|
||
k1["内核代码、数据、内核栈等"]
|
||
end
|
||
block:user_linux["用户空间 (低 3GB)"]
|
||
u1["栈 ↓"]
|
||
u2["..."]
|
||
u3["堆 ↑"]
|
||
u4[".bss / .data / .rodata / .text"]
|
||
end
|
||
end
|
||
block:win["Windows 进程地址空间 (4GB)"]
|
||
columns 1
|
||
block:kernel_win["内核空间 (高 2GB)"]
|
||
k2["内核代码、驱动等"]
|
||
end
|
||
block:user_win["用户空间 (低 2GB)"]
|
||
u5["用户程序空间"]
|
||
end
|
||
end
|
||
```
|
||
|
||
| 操作系统 | 用户空间 | 内核空间 | 说明 |
|
||
|---------|---------|---------|------|
|
||
| **Linux (32位)** | 0 ~ 3GB (低3GB) | 3GB ~ 4GB (高1GB) | 通过`PAGE_OFFSET`划分 |
|
||
| **Windows (32位)** | 0 ~ 2GB (低2GB) | 2GB ~ 4GB (高2GB) | 可通过`/3GB`启动参数调整为3:1 |
|
||
|
||
**重要规则**: 用户态程序**不能**直接访问内核空间的地址,否则触发保护异常。内核态代码可以访问整个地址空间。
|
||
|
||
---
|
||
|
||
## 七、MMU 与内存保护
|
||
|
||
**MMU (Memory Management Unit)** 是CPU中负责地址转换和内存保护的硬件单元。
|
||
|
||
### 地址转换流程
|
||
|
||
```mermaid
|
||
flowchart LR
|
||
CPU["CPU 发出<br/>虚拟地址(VA)"] --> MMU["MMU<br/>地址转换"]
|
||
MMU --> PA["物理地址(PA)"]
|
||
PA --> MEM["访问内存"]
|
||
|
||
style MMU fill:#ffcdd2,stroke:#c62828
|
||
```
|
||
|
||
### 页表保护机制
|
||
|
||
MMU通过页表中的**U/S位**和CPU的**模式位**配合实现内存保护:
|
||
|
||
| 页表U/S位 | 含义 |
|
||
|-----------|------|
|
||
| **U=0 (Supervisor)** | 内核态页面,只有内核可以访问 |
|
||
| **U=1 (User)** | 用户态页面,用户态和内核态均可访问 |
|
||
|
||
| CPU模式 | 可访问的页面 |
|
||
|---------|-------------|
|
||
| **用户模式 (User mode)** | 只能访问 U=1 的页面 |
|
||
| **内核模式 (Kernel mode)** | 可以访问 U=0 和 U=1 的页面 |
|
||
|
||
当用户态程序试图访问 U=0 的内核页面时,MMU会产生**保护异常(段错误/Segmentation Fault)**,终止该进程。
|
||
|
||
---
|
||
|
||
## 八、地址转换的主要方式
|
||
|
||
操作系统实现地址转换有多种方式,各有特点:
|
||
|
||
| 方式 | 原理 | 优点 | 缺点 |
|
||
|------|------|------|------|
|
||
| **重定位寄存器(基址寄存器)** | PA = VA + 基址值 | 简单 | 程序必须连续存放 |
|
||
| **静态重定位** | 装入时一次性修改所有地址 | 无需硬件支持 | 装入后不能移动 |
|
||
| **动态重定位** | 执行时通过MMU实时转换 | 灵活,支持移动 | 需要硬件支持 |
|
||
| **[[14_分页存储管理\|分页]]** | 按页划分,通过页表映射 | 消除外部碎片 | 有内部碎片、页表开销 |
|
||
| **[[15_段式存储管理\|分段]]** | 按逻辑段划分,通过段表映射 | 符合程序逻辑 | 外部碎片问题 |
|
||
|
||
---
|
||
|
||
## 九、小结
|
||
|
||
```mermaid
|
||
mindmap
|
||
root((存储管理基础))
|
||
存储层次
|
||
寄存器
|
||
Cache
|
||
主存
|
||
Flash
|
||
磁盘
|
||
管理功能
|
||
分配回收
|
||
地址转换
|
||
内存共享
|
||
内存保护
|
||
内存扩充
|
||
程序装入
|
||
编译链接
|
||
可执行文件结构
|
||
逻辑地址vs物理地址
|
||
地址空间
|
||
虚拟地址空间
|
||
物理地址空间
|
||
内核空间/用户空间
|
||
MMU保护
|
||
页表U/S位
|
||
CPU模式位
|
||
```
|
||
|
||
---
|
||
|
||
## 关联笔记
|
||
|
||
- [[01_系统运行机制]] — CPU工作模式(用户态/内核态)与MMU硬件基础
|
||
- [[14_分页存储管理]] — 分页式地址转换的详细实现
|
||
- [[15_段式存储管理]] — 分段式地址转换
|
||
- [[16_虚拟存储器]] — 虚拟存储器的实现原理
|
||
|
||
---
|
||
|
||
**上一讲**: [[01_系统运行机制]]
|
||
**下一讲**: [[14_分页存储管理]]
|