将内核页映射到多个内核虚拟地址上

后端开发 2026-07-11

我在一个针对AArch64的 Linux内核模块上工作,我想在内核中为一个已存在的内存区域创建 别名:也就是说,将第二个(或第三、第四等)内核虚拟地址范围映射到已分配的内存。

我想实现的逻辑是:

  1. 分配一个较大的虚拟内存数组(1024页)不分配物理页 - 只保留虚拟地址空间:

struct vm_struct *area = get_vm_area_fn(1024*PAGE_SIZE, VM_ALLOC); 2.分配 4个物理页

struct page *frames[4]; for (int i = 0; i < 4; i++) frames[i] = alloc_page(GFP_KERNEL); 3.以 轮循 方式把大数组中的每一页映射到一个物理帧:

for (int i = 0; i < 1024; i++) map_one_page((uintptr_t)area->addr + i*PAGE_SIZE, frames[i % 4]);

经过此步骤,我预计这1024页的数组将以每4 页为一组呈现 别名化,也就是说每4 页共享同一块物理内存。

但当我实现映射函数如下时:

static int map_one_page(unsigned long va, struct page *page) {
    pgd_t *pgd;
    p4d_t *p4d;
    pud_t *pud;
    pmd_t *pmd;
    pte_t *pte;
    int err = 0;

    pgd = pgd_offset(init_mm_ptr, va);
    if (pgd_none(*pgd))
        return -ENOMEM;

    err = __p4d_alloc_fn(init_mm_ptr, pgd, va);
    if (err)
        return err;
    p4d = p4d_offset(pgd, va);

    err = __pud_alloc_fn(init_mm_ptr, p4d, va);
    if (err)
        return err;
    pud = pud_offset(p4d, va);

    err = __pmd_alloc_fn(init_mm_ptr, pud, va);
    if (err)
        return err;
    pmd = pmd_offset(pud, va);

    err = __pte_alloc_kernel_fn(pmd, va);
    if (err)
        return err;
    pte = pte_offset_kernel(pmd, va);

    /* Build PTE */
    pte_t entry = pfn_pte(page_to_pfn(page), PAGE_KERNEL);

    /* Install */
    set_pte_at(init_mm_ptr, va, pte, entry);

    flush_tlb_kernel_range(va, va + PAGE_SIZE);
    return 0;
}

其中init_mm_ptr等于 &init_mm,其他所有 _fn 函数都是指向内核函数且名称相应的指针(我这样做是因为出于某些原因无法直接调用它们)。

实现上述之后,我先尝试写入 ((char)*v->addr)[0],紧接着再写入 ((char)v->addr)[PAGE_SIZE4],然后打印这两个地址,期望在两种情形下都打印出第二个值,但实际打印出的分别是各自的值(也就是说它们并未发生别名)。

解决方案

我已经成功让它工作,如下所示:

#define NUM_PHYS_PAGES 4
#define NUM_VA_PAGES   128

static struct page** pages = NULL;
struct page *phys_pages[NUM_PHYS_PAGES] = { 0 };
unsigned long base_va = 0;

static int __init alias_mapping_init(void) {
        int i, ret = 0;
        size_t size = NUM_VA_PAGES * PAGE_SIZE;

        for (i = 0; i < NUM_PHYS_PAGES; ++i) {
                phys_pages[i] = alloc_page(GFP_KERNEL);
                if (NULL == phys_pages[i]) {
                        pr_err("[alias] alloc_page[%d] failed\n", i);
                        ret = -ENOMEM;
                        goto err_free_phys;
                }
                clear_highpage(phys_pages[i]);
        }

        pages = kmalloc_array(NUM_VA_PAGES, sizeof(*pages), GFP_KERNEL);
        if (NULL == pages) {
                pr_err("[alias] kmalloc_array failed\n");
                ret = -ENOMEM;
                goto err_free_phys;
        }

        for (int j = 0; j < NUM_VA_PAGES; ++j) {
                pages[j] = phys_pages[j % NUM_PHYS_PAGES];
        }

        base_va = (unsigned long)vm_map_ram(pages, NUM_VA_PAGES, -1);
        if (!base_va) {
                pr_err("[alias] vm_map_ram failed\n");
                ret = -ENOMEM;
                goto err_free_pages_array;
        }

        pr_info("[alias] mapping complete: %lx - %lx\n", base_va, base_va + size);

        {
                char *ptr = (char*)base_va;
                pr_info("[alias] initial: p0=%x p4=%x p1=%x\n",
                                ptr[0],
                                ptr[PAGE_SIZE * 4],
                                ptr[PAGE_SIZE * 1]);

                ptr[0] = 0xAA;
                ptr[PAGE_SIZE * 4] = 0xCC;
                ptr[PAGE_SIZE * 1] = 0xBB;

                dsb(ish);
                isb();

                pr_info("[alias] after: p0=%x p4=%x (alias) p1=%x (independent)\n",
                                ptr[0],
                                ptr[PAGE_SIZE * 4],
                                ptr[PAGE_SIZE * 1]);
        }


        return 0;

err_free_pages_array:
        kfree(pages);
        pages = NULL;

err_free_phys:
        while (--i >= 0) {
                __free_page(phys_pages[i]);
        }

        return ret;
}

static void __exit alias_mapping_exit(void) {
        pr_info("[alias] exit\n");

        if (base_va) {
                vm_unmap_ram((void *)base_va, NUM_VA_PAGES);
                base_va = 0;
        }

        kfree(pages);
        pages = NULL;

        for (int i = 0; i < NUM_PHYS_PAGES; ++i) {
                if (phys_pages[i]) {
                        __free_page(phys_pages[i]);
                        phys_pages[i] = NULL;
                }
        }
}

实际而言,我使用的是 vm_map_ram API,它接收描述我想要的映射的 pages 参数。

如果有人发现问题,当然我也很乐意知道。

供后代参考:vmap 也可以替代 vm_map_ram

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章