跳转到内容

内存模型

OpenOS 的内存管理采用分层设计,从物理内存管理到虚拟内存映射,提供安全、高效的内存访问。

x86_64 提供 48 位虚拟地址空间(256 TiB),OpenOS 将其划分为内核空间和用户空间:

0xFFFFFFFFFFFFFFFF ┌────────────────────┐
│ │
│ 内核空间 │ Ring 0
│ (256 TiB - 128 TiB)│
0xFFFFFFFF80000000 ├────────────────────┤ ← KERNEL_OFFSET
│ │
│ 未映射间隙 │
│ (128 TiB) │
│ │
0x0000800000000000 ├────────────────────┤
│ │
│ 用户空间 │ Ring 3
│ (128 TiB) │
│ │
0x0000000000000000 └────────────────────┘
0xFFFFFFFFFFFFFFFF ┌────────────────────┐
│ 保留 │
0xFFFFFFFFFFFFFF00 ├────────────────────┤
│ IST[0] │ 20 KiB(双重故障栈)
0xFFFFFFFFFFFFB000 ├────────────────────┤
│ 内核栈 (RSP0) │ 32 KiB
0xFFFFFFFFFFFAB000 ├────────────────────┤
│ ... │
0xFFFFFFFF80110000 ├────────────────────┤
│ .data / .bss │ ~16 KiB
0xFFFFFFFF80100000 ├────────────────────┤ ← 内核 .text
│ .text / .rodata │ ~64 KiB
0xFFFFFFFF80000000 ├────────────────────┤ ← KERNEL_OFFSET
│ ... │
0x444444440000 ├────────────────────┤ ← 堆起始
│ 内核堆 │ 100 KiB
0x444444430000 ├────────────────────┤ ← 堆结束
│ ... │
0x00000000B8000 ├────────────────────┤ ← VGA 缓冲区
│ VGA 文本缓冲区 │ 4 KiB
0x00000000B7C00 └────────────────────┘

x86_64 使用四级页表:

虚拟地址 (48 位)
┌───────┬───────┬───────┬───────┬───────────┐
│ PML4 │ PDPT │ PD │ PT │ Offset │
│ 9 位 │ 9 位 │ 9 位 │ 9 位 │ 12 位 │
└───┬───┴───┬───┴───┬───┴───┬───┴─────┬─────┘
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐
│PML4E│→│PDPTE│→│ PDE │→│ PTE │→│ 物理 │
│ │ │ │ │ │ │ │ │ 页面 │
└─────┘ └─────┘ └─────┘ └─────┘ └─────┘
标志说明
Present0页面是否在内存中
Writable1是否可写
User2用户态是否可访问
Write-Through3写透模式
Cache Disabled4禁用缓存
Accessed5是否被访问过
Dirty6是否被写入过
Page Size7大页面(2 MiB / 1 GiB)
NX63禁止执行
映射PresentWritableUserNX
内核代码
内核数据
用户代码
用户数据

OpenOS 使用 bump 分配器 管理物理内存帧:

pub struct BumpFrameAllocator {
next_free_frame: PhysFrame,
end_frame: PhysFrame,
}
impl FrameAllocator<Size4KiB> for BumpFrameAllocator {
fn allocate_frame(&mut self) -> Option<PhysFrame> {
if self.next_free_frame < self.end_frame {
let frame = self.next_free_frame;
self.next_free_frame += 1;
Some(frame)
} else {
None
}
}
}

特点:

  • 简单高效
  • 只能分配,不能释放(bump 特性)
  • 适合早期内核初始化

计划实现更复杂的帧分配器:

  • 伙伴分配器 - 支持释放和合并
  • Slab 分配器 - 针对小对象优化
  • 每 CPU 缓存 - 减少锁竞争

OpenOS 使用 linked_list_allocator crate 管理内核堆:

#[global_allocator]
static ALLOCATOR: LockedHeap = LockedHeap::empty();
pub const HEAP_START: usize = 0x4444_4444_0000;
pub const HEAP_SIZE: usize = 100 * 1024; // 100 KiB
pub fn init_heap() {
unsafe {
ALLOCATOR.lock().init(HEAP_START as *mut u8, HEAP_SIZE);
}
}

特点:

  • 首次适配算法
  • 线程安全(使用 spin::Mutex)
  • 简单,无外部依赖

限制:

  • 固定堆大小(100 KiB)
  • 可能产生碎片
  • 无每 CPU 缓存
use alloc::{boxed::Box, vec, vec::Vec, string::String};
// 分配在堆上
let x = Box::new(42);
let v = vec![1, 2, 3, 4, 5];
let s = String::from("Hello, OpenOS!");
// 释放是自动的(Rust 所有权系统)
  1. 地址空间分离 - 内核和用户空间使用不同的地址范围
  2. 用户空间优化 - 低地址空间适合用户程序(32 位偏移量)
  3. 安全性 - 用户态代码不能直接访问内核页面
  4. 兼容性 - 与 Linux、Windows 等现代 OS 布局一致

引导时,bootloader 设置页表将内核映射到高地址:

物理地址 0x100000 (1 MiB)
虚拟地址 0xFFFFFFFF80100000
KERNEL_OFFSET + 0x100000 = 0xFFFFFFFF80100000

链接脚本使用 AT() 指令处理物理地址:

KERNEL_OFFSET = 0xFFFFFFFF80000000
.text : AT(ADDR(.text) - KERNEL_OFFSET) {
*(.text .text.*)
}
  • 内核代码 - 只读、可执行(RX)
  • 内核数据 - 可读写、不可执行(RW)
  • 用户代码 - 用户可访问、只读、可执行(R-X)
  • 用户数据 - 用户可访问、可读写、不可执行(RW-)
  1. 页表权限位 - 硬件强制执行
  2. SMEP/SMAP - 防止内核执行/访问用户页面
  3. NX 位 - 防止执行数据页面
区域策略说明
内核代码Write-Back最佳性能
内核数据Write-Back最佳性能
VGA 缓冲区Uncached内存映射 I/O
设备 MMIOUncached硬件寄存器
  • 使用 mfence 指令确保内存操作顺序
  • 使用 clflush 刷新特定缓存行
  • 设备访问使用 volatile 确保不被优化掉
  1. 物理帧分配器 - 支持释放和合并
  2. 虚拟内存管理 - 按需分页、页面换出
  3. 共享内存 - 进程间共享页面
  4. 内存映射文件 - mmap 支持
  5. 每 CPU 堆 - 减少锁竞争