插件窝 干货文章 RISC-V Linux启动之页表创建分析

RISC-V Linux启动之页表创建分析

映射 创建 pgd PMD 338    来源:    2025-04-13

RISC-V Linux启动之页表创建分析

概述

RISC-V架构的Linux内核启动过程中,页表创建是一个关键步骤,它实现了从物理地址到虚拟地址的映射,为内核运行提供了内存管理基础。本文将详细分析RISC-V Linux启动过程中页表的创建流程。

启动阶段的内存管理

在RISC-V Linux启动过程中,内存管理经历了几个阶段:

  1. 早期启动阶段:使用简单的1:1映射(物理地址=虚拟地址)
  2. 临时页表阶段:建立初步的页表结构
  3. 最终页表阶段:建立完整的页表结构

关键代码分析

1. 早期页表设置

arch/riscv/kernel/head.S中,内核首先建立早期页表:

#ifdef CONFIG_MMU
    /* 设置早期页表 */
    la a0, early_pg_dir
    call setup_vm
#endif

2. setup_vm函数

setup_vm函数负责建立早期虚拟内存映射:

void __init setup_vm(uintptr_t dtb_pa)
{
    uintptr_t pa = load_pa;
    uintptr_t va = PAGE_OFFSET;

    /* 创建1:1映射 */
    create_pgd_mapping(early_pg_dir, va, pa,
                      PGDIR_SIZE, PAGE_KERNEL_EXEC);

    /* 创建线性映射 */
    create_pgd_mapping(early_pg_dir, kernel_map.virt_addr,
                      kernel_map.phys_addr, kernel_map.size,
                      PAGE_KERNEL_EXEC);
}

3. 页表数据结构

RISC-V使用三级页表结构(Sv39/Sv48): - PGD (Page Global Directory) - PUD (Page Upper Directory) - PMD (Page Middle Directory) - PTE (Page Table Entry)

typedef struct {
    unsigned long pgd;
} pgd_t;

typedef struct {
    unsigned long pud;
} pud_t;

typedef struct {
    unsigned long pmd;
} pmd_t;

typedef struct {
    unsigned long pte;
} pte_t;

4. 页表项标志位

RISC-V页表项包含以下关键标志位: - V (Valid) - R (Readable) - W (Writable) - X (eXecutable) - U (User accessible) - G (Global) - A (Accessed) - D (Dirty)

详细创建流程

  1. 早期页表初始化

    • setup_vm中创建1:1映射和线性映射
    • 使用create_pgd_mapping函数建立映射关系
  2. 临时页表切换

    • early_pg_dir加载到satp寄存器
    • 启用MMU
  3. 最终页表创建

    • paging_init中调用setup_vm_final
    • 创建完整的页表结构
    • 替换临时页表

关键函数分析

create_pgd_mapping

static void __init create_pgd_mapping(pgd_t *pgdp,
                                     uintptr_t va, uintptr_t pa,
                                     uintptr_t sz, pgprot_t prot)
{
    /* 逐级创建页表项 */
    pgd_t *pgd = pgd_offset_pgd(pgdp, va);
    pud_t *pud;
    pmd_t *pmd;
    pte_t *pte;

    if (pgd_none(*pgd)) {
        pud = early_alloc(PTRS_PER_PUD * sizeof(pud_t));
        pgd_populate(pgdp, pgd, pud);
    }

    pud = pud_offset(pgd, va);
    if (pud_none(*pud)) {
        pmd = early_alloc(PTRS_PER_PMD * sizeof(pmd_t));
        pud_populate(pud, pmd);
    }

    pmd = pmd_offset(pud, va);
    if (pmd_none(*pmd)) {
        pte = early_alloc(PTRS_PER_PTE * sizeof(pte_t));
        pmd_populate(pmd, pte);
    }

    pte = pte_offset_kernel(pmd, va);
    set_pte(pte, pfn_pte(pa >> PAGE_SHIFT, prot));
}

setup_vm_final

void __init setup_vm_final(void)
{
    /* 创建swapper_pg_dir */
    create_pgd_mapping(swapper_pg_dir, FIXADDR_START,
                      __pa_symbol(fixmap_pgd_next),
                      PGDIR_SIZE, PAGE_TABLE);

    /* 映射内核 */
    map_kernel();

    /* 映射设备树 */
    map_fdt();

    /* 加载新页表 */
    csr_write(CSR_SATP, PFN_DOWN(__pa_symbol(swapper_pg_dir)) | SATP_MODE);
    local_flush_tlb_all();
}

特殊考虑

  1. 大页支持:RISC-V支持2MB和1GB大页,可减少TLB压力
  2. ASID管理:RISC-V支持地址空间标识符(ASID)来优化TLB刷新
  3. SV39/SV48模式:根据CPU支持选择39位或48位虚拟地址空间

调试技巧

  1. 查看页表内容

    cat /proc/meminfo | grep PageTables
    
  2. 内核调试打印

    #define DEBUG_PGTABLE 1
    
  3. QEMU调试

    qemu-system-riscv64 -d guest_errors,page,trace:riscv_*_pagetable
    

性能优化

  1. 使用大页:减少页表层级,提高TLB命中率
  2. 预分配页表:减少运行时分配开销
  3. 缓存对齐:优化页表访问性能

总结

RISC-V Linux的页表创建过程从简单的1:1映射开始,逐步建立完整的虚拟内存系统。理解这一过程对于内核开发、性能调优和问题诊断都至关重要。随着RISC-V生态的发展,页表管理可能会引入更多优化和新特性。