# 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
源文件"] -->|预处理| B["hello.i
预处理后"] B -->|编译| C["hello.s
汇编文件"] C -->|汇编| D["hello.o
目标文件(可重定位)"] D -->|链接| E["hello / a.out
可执行文件"] 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 -->|"地址映射
(页表/段表)"| 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 发出
虚拟地址(VA)"] --> MMU["MMU
地址转换"] 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_分页存储管理]]