Files
obsidian/操作系统/13_存储管理基础/13_存储管理基础.md

304 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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_分页存储管理]]