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

11 KiB
Raw Permalink Blame History

13. 存储管理基础

课程: 操作系统 - 存储器管理 核心内容: 存储器层次结构、存储管理功能、程序编译链接与装入、地址空间


一、存储器层次结构

存储器按照速度和容量形成层次结构越靠近CPU速度越快但容量越小、价格越高。

速度递增 ↑    容量递减 ↑    价格递增 ↑
┌─────────────────────────┐
│        寄存器(Register)    │  ← 最快,纳秒级,几十~几百字节
├─────────────────────────┤
│     高速缓存(Cache)        │  ← L1/L2/L3几MB
├─────────────────────────┤
│      主存(内存/DRAM)       │  ← 几GB~几百GB百纳秒级
├─────────────────────────┤
│      Flash/SSD             │  ← 固态存储
├─────────────────────────┤
│    磁盘缓存(Disk Cache)    │
├─────────────────────────┤
│    固定磁盘(HDD/SSD)       │  ← 几百GB~几TB毫秒级
├─────────────────────────┤
│   可移动存储(U盘/光盘/磁带) │  ← 最慢,容量可很大
└─────────────────────────┘

设计原则: 利用局部性原理,将频繁访问的数据放在高速层次,较少访问的数据放在低速大容量层次。


二、存储管理的功能

操作系统存储管理需要实现以下五大核心功能:

功能 说明
内存分配与回收 为进程分配所需内存,进程结束后回收内存
地址转换 将程序中的逻辑地址转换为物理地址(14_分页存储管理15_段式存储管理
内存共享 多个进程共享同一段代码(如共享库)
内存保护 防止进程越界访问其他进程或内核的内存区域
内存扩充 通过虚拟存储技术,使程序可用空间大于实际物理内存(16_虚拟存储器

三、程序的编译与链接过程

一个C语言源文件从编写到可执行经历以下阶段

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)信息:

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)和操作系统配合完成。

逻辑地址空间与物理地址空间

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. 内存保护: 通过地址转换实现访问权限控制

六、内核空间与用户空间

操作系统将每个进程的虚拟地址空间划分为用户空间内核空间两部分:

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中负责地址转换和内存保护的硬件单元。

地址转换流程

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_段式存储管理 按逻辑段划分,通过段表映射 符合程序逻辑 外部碎片问题

九、小结

mindmap
  root((存储管理基础))
    存储层次
      寄存器
      Cache
      主存
      Flash
      磁盘
    管理功能
      分配回收
      地址转换
      内存共享
      内存保护
      内存扩充
    程序装入
      编译链接
      可执行文件结构
      逻辑地址vs物理地址
    地址空间
      虚拟地址空间
      物理地址空间
      内核空间/用户空间
    MMU保护
      页表U/S位
      CPU模式位

关联笔记


上一讲: 01_系统运行机制 下一讲: 14_分页存储管理