第 13 章 抽象:地址空间

早期系统:缺乏抽象

在计算机发展的早期,操作系统并未对内存提供抽象,其物理内存布局非常简单:

  • 操作系统:本质上是一组函数库,通常驻留在物理内存的起始位置(如 0KB 开始)。
  • 应用程序:作为一个单一进程运行,占用操作系统之外的剩余物理内存(如从 64KB 开始)。

在这种模式下,用户对系统的要求极少,操作系统开发者的工作也相对简单。

多道程序与时分共享

随着机器成本的增加,如何高效共享机器资源成为核心问题,演进过程如下:

1. 多道程序 (Multiprogramming)

  • 核心理念:当一个进程等待 I/O 时,切换到另一个进程运行,从而提高 CPU 利用率 (Utilization)
  • 背景:早期机器成本极高,提升效率是首要任务。

2. 时分共享 (Time-sharing)

  • 核心理念:强调 交互性 (Interactivity),让多个用户能同时使用机器并获得及时响应。
  • 实现方式的演进
    • 初级方案:运行一个进程,停止时将其全部内存状态保存到磁盘,再加载另一个进程。
    • 问题:磁盘 I/O 太慢,随着内存增加,这种切换变得不可接受。
    • 优化方案:将多个进程同时保留在内存中。在进程切换时,只需保存/恢复寄存器状态,效率大幅提升。

3. 新的挑战:保护 (Protection)

当多个程序同时驻留在内存时,安全性变得至关重要。必须确保一个进程无法读取或修改其他进程(或操作系统)的内存数据。

地址空间 (Address Space)

为了满足用户对易用性和安全性的需求,操作系统提供了一个物理内存的抽象,即 地址空间。它是运行程序所能看到的系统内存视图,是理解内存虚拟化的关键。

1. 地址空间的组成

一个典型的进程地址空间包含以下三个核心部分:

  • 代码 (Code):存储程序的指令。由于是静态的,通常放在地址空间的顶部(从 0 开始)。
  • 堆 (Heap):用于管理动态分配的内存(如 C 语言中的 malloc())。它位于代码段之下,并向 低地址方向 增长。
  • 栈 (Stack):用于保存函数调用信息、局部变量和参数。它从地址空间的底部开始,并向 高地址方向 增长。

布局约定

将堆和栈放在两端并向相反方向增长,是为了最大限度地利用中间的空闲空间。这只是一种常见的布局约定。

2. 内存虚拟化 (Virtualizing Memory)

内存虚拟化的核心在于:程序看到的地址(虚拟地址)与真实的物理地址是分离的。

  • 虚拟视图:程序认为自己被加载到地址 0,并拥有一个连续且巨大的地址空间。
  • 物理现实:操作系统在硬件支持下,将虚拟地址映射到物理内存的任意位置(例如将虚拟地址 0 映射到物理地址 320KB)。
核心问题:如何虚拟化内存?

操作系统如何在单一的物理内存上,为多个共享内存的进程构建出私有的、巨大的地址空间抽象?

3. 隔离原则 (The Principle of Isolation)

隔离是构建可靠系统的基石。

  • 进程隔离:确保一个进程的失败或错误操作不会影响其他进程。
  • 系统保护:防止运行程序读取或修改底层操作系统的内存,确保内核的稳定性。

虚拟内存的目标

虚拟内存(VM)系统的设计主要围绕以下三个核心目标:

  1. 透明性 (Transparency):程序不应感知到内存被虚拟化的事实,其行为应像拥有私有物理内存一样。操作系统与硬件在幕后完成所有复用工作。
  2. 效率 (Efficiency)
    • 时间效率:虚拟化不应显著降低程序运行速度。
    • 空间效率:支持虚拟化所需的额外内存开销应尽可能小。
    • 注:高效虚拟化通常依赖硬件支持(如 TLB)。
  3. 保护 (Protection):确保进程间的 隔离 (Isolation)。进程无法访问或修改其地址空间之外的任何内容(包括其他进程或操作系统的内存)。
程序员看到的地址全是假的

在用户态程序中,你打印出的任何指针(如 main 函数地址、malloc 返回值、局部变量地址)都是 虚拟地址 (Virtual Address)

只有操作系统和硬件知道这些数据在物理内存中的真实位置。虚拟地址只是系统提供的一种内存分布假象。

#include <stdio.h> 
#include <stdlib.h> 
 
int main(int argc, char *argv[]) { 
    printf("location of code : %p\n", (void *) main); 
    printf("location of heap : %p\n", (void *) malloc(1)); 
    int x = 3; 
    printf("location of stack : %p\n", (void *) &x); 
    return 0; 
}

在接下来的章节中,我们将深入探讨实现这些目标所需的 机制 (Mechanism)(硬件与 OS 支持)和 策略 (Policy)(如空间管理与页面置换)。