物理内存管理 - 理论讲解
1. 物理内存管理的职责
物理内存管理器(PMM)负责:
- 检测系统中有多少物理内存
- 跟踪哪些页帧在使用、哪些空闲
- 提供分配和释放页帧的接口
页帧(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; // 总内存(字节)
};下一步
物理内存管理器让我们能分配页帧,但还不够——我们需要虚拟内存来提供隔离和灵活的地址空间。下一章将实现分页机制!