【Linux】页表的实现与地址转换

本文介绍了页表的实现原理及地址转换过程。包括页表结构、分页机制、硬件寄存器作用及其如何通过不同层级页表实现虚拟地址到物理地址的映射。通过实例详细解析了两级和四级页表的工作机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

页表的实现与地址转换

页表是软件实现的,但是页表的查找是MMU完成的,所以硬件定义了页表的实现规则,软件做的只有选择页表的级别,是否使用huge page以及填充对应的权限标志位。每个进程都拥有一个自己的页表,在linux中,有一个页目录数组,这是分页机制的最高层,每个进程的页表对应其中的一个页目录项,通过cr3寄存器(存放页目录项的物理地址)可以访问。一个进程的页表,对应的页表项中对应页的物理地址。

分页机制

两级页表举例:

两级表的第一级表称为页目录,存储在一个4K字节的页中,页目录表共有1K个表项,每个表项为4个字节,线性地址最高的10位(22-31)用来产生第一级表索引,由该索引得到的表项中的内容定位了两级页表中的一个表的地址,即下级页表所在的内存块号。

第二级页表称为页表,存储在一个4K字节页中,它包含了1K字节的表项,每个表项包含了一个页的物理地址。二级页表由线性地址的中间10位(12-21)位进行索引,定位页表表项,获得页的物理地址。页物理地址的高20位与线性地址的低12位形成最后的物理地址。
在这里插入图片描述

在这里插入图片描述

四级页表举例如图:
在这里插入图片描述

页表PGD的首地址是通过mm_struct中的pgd得到的:pgd_t *pgd,pgd_offset(),pmd_offset(),pmd_offset()分别用于从线性地址中提取PGD,PMD和PTE表所需的index.

PGD,PUD,PMD,PTE分别都是一个4k的page,其实,PGD,PUD,PMD,PTE是四张table,table的大小都是4k,其中table的entry分别是pgd_t,pud_t,pmd_t,pte_t,都是unsigned long类型(8字节),4k(2的12次方)/8字节=512个entry(2的9次方);PTE的table大小也是4k,entry大小也是8字节,所以,PTE表中可以存放512个entry(也就是512个物理地址),PTRS_PER_***宏表示表中项(entry)的个数。

linux中用PAGE_SHIFT表示一个PTE所对应的内存范围,表示线性地址offset字段的位数。该宏的值被定义为12位,即页的大小为4KB。

#define PAGE_SHIFT 12

PMD_SHIFT和PGDIR_SHIFT分别表示一个PMD和一个PGD所对应的内存所需要的bit数。在四级分页模型中,PGDIR_SHIFT占据39位,即9位页上级目录,9位中间目录,9位页表和12位偏移。页全局目录同样占线性地址的9位,因此PTRS_PER_PGD为512.
在这里插入图片描述
PAGE_SIZE,PMD_SIZE,PGDIR_SIZE则分别表示一个PTE,PMD,PGD所对应的内存范围的大小。PAGE_MASK,PMD_MASK,PGDIR_MASK则是用于提取地址位的掩码,MASK与SIZE的大小如下图所示:
在这里插入图片描述

关于类似的相关宏参考:https://blog.youkuaiyun.com/weixin_34186128/article/details/93170177

地址转换过程(代码实现MMU转换过程)

#include <linux/init.h>
#include <linux/module.h>
#include <linux/mm.h>
#include <linux/mm_types.h>
#include <linux/sched.h>
#include <linux/export.h>

static unsigned long cr0;
static unsigned long cr3;

static unsigned long vaddr = 0;

static void get_pgtable_macro(void)
{
        cr0 = read_cr0();
        cr3 = read_cr3_pa();

        printk("cr0 = 0x%lx, cr3 = 0x%lx\n", cr0, cr3);

        printk("PGDIR_SHIFT = %d\n", PGDIR_SHIFT);
        printk("P4D_SHIFT = %d\n", P4D_SHIFT);
        printk("PUD_SHIFT = %d\n", PUD_SHIFT);
        printk("PMD_SHIFT = %d\n", PMD_SHIFT);
        printk("PAGE_SHIFT = %d\n", PAGE_SHIFT);

        //页目录表中项的个数
        printk("PTRS_PER_PGD = %d\n", PTRS_PER_PGD);
        printk("PTRS_PER_P4D = %d\n", PTRS_PER_P4D);
        printk("PTRS_PER_PUD = %d\n", PTRS_PER_PUD);
        printk("PTRS_PER_PMD = %d\n", PTRS_PER_PMD);
        printk("PTRS_PER_PTE = %d\n", PTRS_PER_PTE);

        //页内偏移掩码,用来屏蔽 page_offset
        printk("PAGEMASK = 0x%lx\n", PAGE_MASK);
}

void vaddr2paddr(unsigned long vaddr)
{
        pgd_t *pgd;
        p4d_t *p4d;
        pud_t *pud;
        pmd_t *pmd;
        pte_t *pte;

        unsigned long paddr = 0;
        unsigned long page_addr = 0;
        unsigned long page_offset = 0;

       //取得页全局目录项
       //pgd_index(addr):返回PGD包含的项中,地址字段值为addr的项的索引,下同
       //pgd_val(pgd)获得pgd所指的页全局目录项,下同
        pgd = pgd_offset(current->mm, vaddr);
        printk("pgd_val = 0x%lx, pgd_index = %lu\n", pgd_val(*pgd), pgd_index(vaddr));

        //取得 p4d
        p4d = p4d_offset(pgd, vaddr);
        printk("p4d_val = 0x%lx, p4d_index = %lu\n", p4d_val(*p4d), p4d_index(vaddr));

        //取得页上级目录项
        pud = pud_offset(p4d, vaddr);
        printk("pud_val = 0x%lx, pud_index = %lu\n", pud_val(*pud), pud_index(vaddr));

        //取得页中间目录项
        pmd = pmd_offset(pud, vaddr);
        printk("pmd_val = 0x%lx, pmd_index = %lu\n", pmd_val(*pmd), pmd_index(vaddr));

        //取得页表(在主内核页表中查找)
        pte = pte_offset_kernel(pmd, vaddr);
        printk("pte_val = 0x%lx, pte_index = %lu\n", pte_val(*pte), pte_index(vaddr));

        //取得的页表项的低 12 位放的是页属性
        //所以用 PAGE_MASK 来清除
        page_addr = pte_val(*pte) & PAGE_MASK;
        
        //取得页内偏移 page_offset
        //也就是只取虚拟地址的低 12 位
        page_offset = vaddr & ~PAGE_MASK;

        //然后和并页表地址和页内偏移取得物理地址
        paddr = page_addr | page_offset;

        printk("page_addr = 0x%lx, page_offset = 0x%lx\n", page_addr, page_offset);
        printk("virtual address = 0x%lx, physical address = 0x%lx\n", vaddr, paddr);

}

static int __init v2p_init(void)
{
        unsigned long vaddr = 0;
        printk("vritual address to physical address module is running..\n");
        get_pgtable_macro();
        printk("\n");
        vaddr = __get_free_page(GFP_KERNEL);
        if(vaddr == 0)
        {
                printk("__get_free_page failed..\n");
                return 0;
        }
        printk("get_page_vaddr = 0x%lx\n", vaddr);
        vaddr2paddr(vaddr);
        return 0;
}

static void __exit v2p_exit(void)
{
        free_page(vaddr);
        printk("module is leaving...\n");
}


module_init(v2p_init);
module_exit(v2p_exit);

在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

董lucky

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值