内存模型
OpenOS 的内存管理采用分层设计,从物理内存管理到虚拟内存映射,提供安全、高效的内存访问。
虚拟地址空间布局
Section titled “虚拟地址空间布局”x86_64 地址空间
Section titled “x86_64 地址空间”x86_64 提供 48 位虚拟地址空间(256 TiB),OpenOS 将其划分为内核空间和用户空间:
0xFFFFFFFFFFFFFFFF ┌────────────────────┐ │ │ │ 内核空间 │ Ring 0 │ (256 TiB - 128 TiB)│0xFFFFFFFF80000000 ├────────────────────┤ ← KERNEL_OFFSET │ │ │ 未映射间隙 │ │ (128 TiB) │ │ │0x0000800000000000 ├────────────────────┤ │ │ │ 用户空间 │ Ring 3 │ (128 TiB) │ │ │0x0000000000000000 └────────────────────┘内核空间详细布局
Section titled “内核空间详细布局”0xFFFFFFFFFFFFFFFF ┌────────────────────┐ │ 保留 │0xFFFFFFFFFFFFFF00 ├────────────────────┤ │ IST[0] │ 20 KiB(双重故障栈)0xFFFFFFFFFFFFB000 ├────────────────────┤ │ 内核栈 (RSP0) │ 32 KiB0xFFFFFFFFFFFAB000 ├────────────────────┤ │ ... │0xFFFFFFFF80110000 ├────────────────────┤ │ .data / .bss │ ~16 KiB0xFFFFFFFF80100000 ├────────────────────┤ ← 内核 .text │ .text / .rodata │ ~64 KiB0xFFFFFFFF80000000 ├────────────────────┤ ← KERNEL_OFFSET │ ... │0x444444440000 ├────────────────────┤ ← 堆起始 │ 内核堆 │ 100 KiB0x444444430000 ├────────────────────┤ ← 堆结束 │ ... │0x00000000B8000 ├────────────────────┤ ← VGA 缓冲区 │ VGA 文本缓冲区 │ 4 KiB0x00000000B7C00 └────────────────────┘x86_64 使用四级页表:
虚拟地址 (48 位)┌───────┬───────┬───────┬───────┬───────────┐│ PML4 │ PDPT │ PD │ PT │ Offset ││ 9 位 │ 9 位 │ 9 位 │ 9 位 │ 12 位 │└───┬───┴───┬───┴───┬───┴───┬───┴─────┬─────┘ │ │ │ │ │ ▼ ▼ ▼ ▼ ▼ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │PML4E│→│PDPTE│→│ PDE │→│ PTE │→│ 物理 │ │ │ │ │ │ │ │ │ │ 页面 │ └─────┘ └─────┘ └─────┘ └─────┘ └─────┘| 标志 | 位 | 说明 |
|---|---|---|
| Present | 0 | 页面是否在内存中 |
| Writable | 1 | 是否可写 |
| User | 2 | 用户态是否可访问 |
| Write-Through | 3 | 写透模式 |
| Cache Disabled | 4 | 禁用缓存 |
| Accessed | 5 | 是否被访问过 |
| Dirty | 6 | 是否被写入过 |
| Page Size | 7 | 大页面(2 MiB / 1 GiB) |
| NX | 63 | 禁止执行 |
| 映射 | Present | Writable | User | NX |
|---|---|---|---|---|
| 内核代码 | ✓ | ✗ | ✗ | ✗ |
| 内核数据 | ✓ | ✓ | ✗ | ✓ |
| 用户代码 | ✓ | ✗ | ✓ | ✗ |
| 用户数据 | ✓ | ✓ | ✓ | ✓ |
物理内存管理
Section titled “物理内存管理”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 缓存 - 减少锁竞争
linked_list_allocator
Section titled “linked_list_allocator”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 所有权系统)高半内核设计
Section titled “高半内核设计”为什么选择高半内核?
Section titled “为什么选择高半内核?”- 地址空间分离 - 内核和用户空间使用不同的地址范围
- 用户空间优化 - 低地址空间适合用户程序(32 位偏移量)
- 安全性 - 用户态代码不能直接访问内核页面
- 兼容性 - 与 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-)
- 页表权限位 - 硬件强制执行
- SMEP/SMAP - 防止内核执行/访问用户页面
- NX 位 - 防止执行数据页面
| 区域 | 策略 | 说明 |
|---|---|---|
| 内核代码 | Write-Back | 最佳性能 |
| 内核数据 | Write-Back | 最佳性能 |
| VGA 缓冲区 | Uncached | 内存映射 I/O |
| 设备 MMIO | Uncached | 硬件寄存器 |
- 使用
mfence指令确保内存操作顺序 - 使用
clflush刷新特定缓存行 - 设备访问使用
volatile确保不被优化掉
计划中的功能
Section titled “计划中的功能”- 物理帧分配器 - 支持释放和合并
- 虚拟内存管理 - 按需分页、页面换出
- 共享内存 - 进程间共享页面
- 内存映射文件 - mmap 支持
- 每 CPU 堆 - 减少锁竞争