物理内存管理 - 理论讲解

1. 物理内存管理的职责

物理内存管理器(PMM)负责:

  1. 检测系统中有多少物理内存
  2. 跟踪哪些页帧在使用、哪些空闲
  3. 提供分配和释放页帧的接口

页帧(Page Frame)

物理内存被划分为固定大小的块,称为页帧。在x86中,标准页帧大小为 4KB (4096 bytes)。

物理内存:  |帧0|帧1|帧2|帧3|...|帧N|
地址:      0   4K  8K  12K     N*4K

2. 检测可用内存

方法1:硬编码(简单但不灵活)

#define TOTAL_MEMORY  (128 * 1024 * 1024)  // 假设128MB

方法2:BIOS中断(实模式才能用)

在bootloader阶段使用 INT 0x15, EAX=0xE820 获取内存映射:

  • 这是最可靠的方法
  • 需要在进入保护模式前调用
  • 将结果保存到固定内存位置供内核读取

方法3:探测(Probing)

在保护模式下逐步写入读取内存,检测实际大小。简单但有风险。

3. 位图分配器

原理

用一个bit数组记录每个页帧的使用状态:

位图:  [1][1][0][0][1][0][0][0]...
帧号:   0  1  2  3  4  5  6  7
状态: 已用 已用 空闲 空闲 已用 空闲 空闲 空闲

位图操作

// 设置(标记已使用)
bitmap[frame / 8] |= (1 << (frame % 8));
 
// 清除(标记空闲)
bitmap[frame / 8] &= ~(1 << (frame % 8));
 
// 测试
bool used = bitmap[frame / 8] & (1 << (frame % 8));

查找空闲帧

uint32_t find_free_frame(void) {
    for (uint32_t i = 0; i < total_frames; i++) {
        if (!(bitmap[i / 8] & (1 << (i % 8)))) {
            return i;  // 找到空闲帧
        }
    }
    return (uint32_t)-1;  // 内存耗尽
}

优化:按字节快速扫描

uint32_t find_free_frame_fast(void) {
    uint32_t bitmap_size = total_frames / 8;
    for (uint32_t i = 0; i < bitmap_size; i++) {
        if (bitmap[i] != 0xFF) {
            // 这个字节中有空闲帧
            for (int j = 0; j < 8; j++) {
                if (!(bitmap[i] & (1 << j))) {
                    return i * 8 + j;
                }
            }
        }
    }
    return (uint32_t)-1;
}

4. kmalloc 实现

简易版(Bump Allocator)

最简单的内核内存分配——只分配不释放:

static uint32_t heap_top = KERNEL_HEAP_START;
 
void *kmalloc(uint32_t size) {
    void *ptr = (void *)heap_top;
    heap_top += size;
    return ptr;
}

页对齐版本

某些数据结构(如页表)需要4KB对齐:

void *kmalloc_aligned(uint32_t size) {
    // 对齐到4KB边界
    if (heap_top & 0xFFF) {
        heap_top = (heap_top & ~0xFFF) + 0x1000;
    }
    void *ptr = (void *)heap_top;
    heap_top += size;
    return ptr;
}

5. 内存碎片

外部碎片

多次分配释放后,空闲内存被分割成小块:

分配前: |████████████████|
使用中: |AA|BB|CC|DD|    |
释放B,D: |AA|  |CC|  |    |
          ↑  ↑     ↑
        空闲但不连续,无法分配大块

解决方案

  • 页帧分配器: 固定大小,不产生外部碎片
  • Slab分配器: 预分配固定大小对象池(Linux内核使用)
  • 伙伴系统(Buddy System): 2的幂次大小分配(高级话题)

6. 关键数据结构

// 物理内存管理器状态
struct pmm_state {
    uint8_t *bitmap;        // 位图数组
    uint32_t total_frames;  // 总帧数
    uint32_t used_frames;   // 已用帧数
    uint32_t total_memory;  // 总内存(字节)
};

下一步

物理内存管理器让我们能分配页帧,但还不够——我们需要虚拟内存来提供隔离和灵活的地址空间。下一章将实现分页机制!