手写操作系统 - 实现虚拟内存管理

149 阅读3分钟

1、为什么需要虚拟内存

虚拟内存是现代操作系统的核心功能之一,它解决了物理内存管理的多个关键问题:

内存隔离与保护: 在没有虚拟内存的系统中,所有进程都直接访问物理内存。一个进程的错误操作可能破坏其他进程甚至操作系统的数据。虚拟内存为每个进程提供独立的地址空间,实现真正的进程隔离。

简化内存管理: 程序员无需关心物理内存的实际布局。每个进程都拥有从0开始的连续地址空间,编译器链接器可以生成固定的内存布局。

突破物理内存限制: 通过分页机制,可以将暂时不用的内存页换出到磁盘,当需要时再换入,使得程序可以使用比实际物理内存更大的地址空间。

高效的内存共享: 不同的进程可以映射到相同的物理页,实现代码和数据的共享,如共享库机制。

2、如何实现虚拟内存

虚拟内存的核心实现机制是分页。x86 架构 32 位采用二级页表结构:

虚拟地址: 程序使用的地址,通常为32位或64位

物理地址: 实际内存硬件的地址

页: 内存管理的基本单位,通常为4KB

页表: 存储虚拟页到物理页映射关系的数据结构

MMU: 内存管理单元,负责地址转换,32 位虚拟地址分解为:

页目录索引(10位)

页表索引(10位)

页内偏移(12位)

CPU 通过 CR3 寄存器定位页目录,经过二级查表将虚拟地址转换为物理地址。

4314397-945246762654a42d.png

3、手写实现虚拟内存管理

static page_mapping_table_t* find_create_page_mapping_table(page_mapping_dir_t* page_dir, void* virtual_addr) {
    page_mapping_dir_t* target_page_dir = (page_dir + PDE_INDEX(virtual_addr));
    page_mapping_table_t* target_page_table = nullptr;
    if (target_page_dir->present) {
        // 大部分同学可能都看不懂
        target_page_table = (page_mapping_table_t*)(target_page_dir->phy_pt_addr << 12);
    } else {
        target_page_table = alloc_a_page();
        target_page_dir->present = 1;
        target_page_dir->read_write = 1; // 代码段
        target_page_dir->user_mode_acc = 0; // 只能内核访问
        target_page_dir->phy_pt_addr = (u32_t)target_page_table >> 12;
    }
    return target_page_table + PTE_INDEX(virtual_addr);
}

void create_memory_mapping(page_mapping_dir_t* page_dir, void* virtual_addr, void* physics_addr, u32_t count) {
    for (size_t i = 0; i < count; i++)
    {
        page_mapping_table_t* page_mapping_table = find_create_page_mapping_table(page_dir, virtual_addr);
        page_mapping_table->phy_page_addr = (u32_t)physics_addr >> 12;
        assert(page_mapping_table->present == 0);
        page_mapping_table->present = 1;
        page_mapping_table->read_write = 1; // 代码段
        page_mapping_table->user_mode_acc = 0; // 只能内核访问,后面再写进程加载进入用户态,会闪退
        virtual_addr += PAGE_SIZE;
        physics_addr += PAGE_SIZE;
    }
}

// 打开虚拟内存,建立内核的初始化映射
static void init_virtual_memory_mapping() {
    page_mapping_dir_t* page_table_dir = alloc_a_page();
    // 初始化先建立 4M 的内存映射,并且前 4M 的虚拟内存映射到真实(物理)内存的前 4M
    create_memory_mapping(page_table_dir, 0, 0, KERNEL_INIT_MAPPING_SIZE / PAGE_SIZE);
    // 设置 cr3 寄存器(页目录地址)
    set_cr3(page_table_dir);
    // 打开 mmu 虚拟映射硬件
    open_cr0_enable_page();
}

4、手写实现缺页异常

void do_interrupt_handler_page_fault(exception_frame_t* exception_frame) {
    printk("do_interrupt_handler_page_fault\r\n");
    // 哪一块缺页了呢?
    void* page_fault_addr = (void*)read_cr2();
    // 虚拟地址要往下对齐 4kb
    void* virtual_addr = (void*)DOWN_ON(page_fault_addr, PAGE_SIZE);
    page_mapping_dir_t* page_mapping_dir = (page_mapping_dir_t*)read_cr3();
    // 新的物理页映射地址
    void* new_physics_addr = alloc_a_page();
    create_memory_mapping(page_mapping_dir, virtual_addr, new_physics_addr, 1);
    flush_tlb(page_fault_addr);
}

5、用户进程虚拟内存管理

4314397-93ce2b5f01185ea2.png