29 Virtual address to physical address conversion

foreword

Hehe, this is a very basic part of memory management in Linux 

The address operated by the user program is a virtual address, and the virtual address is converted into a physical address by mmu 

The address seen by the user program is a complete world, only a page fault interrupt is generated when it is specifically needed, and then a specific physical page is allocated 

What I want to talk about here is the translation from virtual address to physical address 

Experience the conversion of virtual addresses to physical addresses

It mainly comes from the kernel module to experience, and the tested module code comes from the following link 

Linux kernel learning 3 - convert virtual address to physical address z

The executed container is the centos 7.x desktop version, and the original module code has been adjusted to some extent 

#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>
#include <linux/delay.h>


static unsigned long cr0,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);   //指示page offset字段,映射的是一个页面的大小,一个页面大小是4k,转换成以2为底的对数就是12,其他的宏类似

    //下面的这些宏是用来指示相应的页目录表中的项的个数的,这些宏都是为了方便寻页时进行位运算的
    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);
    printk("PAGE_MASK = 0x%lx\n", PAGE_MASK);   //page_mask,页内偏移掩码,用来屏蔽掉page offset字段
}

static unsigned long 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 = pgd_offset(current->mm,vaddr);  //第一个参数是当前进程的mm_struct结构(我们申请的线性地址空间是内核,所以应该查内核页表,又因为所有的进程都共享同一个内核页表,所以可以用当前进程的mm_struct结构来进行查找)
    printk("pgd_addr = 0x%lx, *pgd = 0x%lx, pgd_page_addr = 0x%lx, pgd_val = 0x%lx, pgd_index = %lu\n", pgd, *pgd, pgd_page_vaddr(*pgd), pgd_val(*pgd),pgd_index(vaddr));
    if (pgd_none(*pgd)){
        printk("not mapped in pgd\n");
        return -1;
    }

//    p4d = p4d_offset(pgd, vaddr);  //查找到的页全局目录项pgd作为下级查找的参数传入到p4d_offset中
//    printk("p4d_val = 0x%lx, p4d_index = %lu\n", p4d_val(*p4d),p4d_index(vaddr));
//    if(p4d_none(*p4d))
//    {
//        printk("not mapped in p4d\n");
//        return -1;
//    }

    pud = pud_offset(pgd, vaddr);
    printk("pud_addr = 0x%lx, *pud = 0x%lx, pud_page_addr = 0x%lx, pud_val = 0x%lx, pud_index = %lu\n", pud, *pud, pud_page_vaddr(*pud), pud_val(*pud),pud_index(vaddr));
    if (pud_none(*pud)) {
        printk("not mapped in pud\n");
        return -1;
    }

    pmd = pmd_offset(pud, vaddr);
    printk("pmd_addr = 0x%lx, *pmd = 0x%lx, pmd_page_addr = 0x%lx, pmd_val = 0x%lx, pmd_index = %lu\n", pmd, *pmd, pmd_page_vaddr(*pmd), pmd_val(*pmd),pmd_index(vaddr));
    if (pmd_none(*pmd)) {
        printk("not mapped in pmd\n");
        return -1;
    }

    pte = pte_offset_kernel(pmd, vaddr);  //与上面略有不同,这里表示在内核页表中查找,在进程页表中查找是另外一个完全不同的函数   这里最后取得了页表的线性地址
    printk("pte_addr = 0x%lx, *pte = 0x%lx, pte_val = 0x%lx, ptd_index = %lu\n", pte, *pte, pte_val(*pte),pte_index(vaddr));

    if (pte_none(*pte)) {
        printk("not mapped in pte\n");
        return -1;
    }
    //从页表的线性地址中取出该页表所映射页框的物理地址
    page_addr = pte_val(*pte) & PAGE_MASK;    //取出其高48位
    //取出页偏移地址,页偏移量也就是线性地址中的低12位
    page_offset = vaddr & ~PAGE_MASK;
    //将两个地址拼接起来,就得到了想要的物理地址了
    paddr = page_addr | page_offset;
    printk("page_addr = %lx, page_offset = %lx\n", page_addr, page_offset);
    printk("vaddr = %lx, paddr = %lx\n", vaddr, paddr);
    return paddr;
}

static int __init v2p_init(void)    //内核模块的注册函数
{
    unsigned long vaddr = 0 ;
    printk("vaddr to paddr module is running..\n");
    get_pgtable_macro();
    printk("\n");
    vaddr = __get_free_page(GFP_KERNEL);   //在内核的ZONE_NORMAL中申请了一块页面,GFP_KERNEL标志指示优先从内核的ZONE_NORMAL中申请页框
    if (vaddr == 0) {
        printk("__get_free_page failed..\n");
        return 0;
    }
    sprintf((char *)vaddr, "hello world from kernel");   //在地址中写入hello
    printk("get_page_vaddr=0x%lx\n", vaddr);
    vaddr2paddr(vaddr);
    return 0;
}
static void __exit v2p_exit(void)    //内核模块的卸载函数
{
    printk("vaddr to paddr module is leaving..\n");
    free_page(vaddr);   //将申请的线性地址空间释放掉
}


module_init(v2p_init);
module_exit(v2p_exit);
MODULE_LICENSE("GPL");


Makefile is as follows

PROJ=Test05Virt2Physical
KERN_DIR=/lib/modules/$(shell uname -r)/build

obj-m+=$(PROJ).o

all:
	make ARCH=x86_64 -C $(KERN_DIR) M=$(shell pwd) modules

clean:
	make -C $(KERN_DIR) M=$(shell pwd)  clean
	rm -rf modules.order

Add the kernel module output log as follows, here is a process of converting a virtual address to a physical address 

待调试虚拟地址为 0xffff9346f8bc6000 

mm->pgd 中存储的是 pgd 的首部, 加上 pgd 的索引 294, 计算得到 pgd 的地址, 0xffff9346c7dc8930, pgd 的值为 0x29275067 
pgd 的值为 0x29275067, 存储数据的页地址为 0xffff9346e9275000, 加上 pud 的索引 283, 计算得到 pud 的地址, 0xffff9346e9275000 + 283 * 8 = 0xffff9346e92758d8, pud 的值为 0x29276067 
pud 的值为 0x29276067, 存储数据的页地址为 0xffff9346e9276000, 加上 pmd 的索引 453, 计算得到 pmd 的地址, 0xffff9346e9276000 + 453 * 8 = 0xffff9346e9276e28, pmd 的值为 0x29276067
pmd 的值为 0x29276067, 存储数据的页地址为 0xffff9346f6f5a000, 加上 pte 的索引为 454, 计算得到 pte 的地址, 0xffff9346f6f5a000 + 454 * 8 = 0xffff9346f6f5ae30, pte 的值为 0x8000000038bc6063

pte 指向的即为对应的物理页信息, mask 掉后面 12bit, 加上偏移即为物理地址 8000000038bc6000
[ 3319.130839] vaddr to paddr module is running..
[ 3319.130842] cr0 = 0x80050033, cr3 = 0x7dc8000
[ 3319.130843] PGDIR_SHIFT = 39
[ 3319.130844] PUD_SHIFT = 30
[ 3319.130845] PMD_SHIFT = 21
[ 3319.130846] PAGE_SHIFT = 12
[ 3319.130847] PTRS_PER_PGD = 512
[ 3319.130848] PTRS_PER_PUD = 512
[ 3319.130848] PTRS_PER_PMD = 512
[ 3319.130849] PTRS_PER_PTE = 512
[ 3319.130850] PAGE_MASK = 0xfffffffffffff000

[ 3319.130853] get_page_vaddr=0xffff9346f8bc6000
[ 3319.130854] pgd_addr = 0xffff9346c7dc8930, *pgd = 0x29275067, pgd_page_addr = 0xffff9346e9275000, pgd_val = 0x29275067, pgd_index = 294
[ 3319.130856] pud_addr = 0xffff9346e92758d8, *pud = 0x29276067, pud_page_addr = 0xffff9346e9276000, pud_val = 0x29276067, pud_index = 283
[ 3319.130857] pmd_addr = 0xffff9346e9276e28, *pmd = 0x36f5a063, pmd_page_addr = 0xffff9346f6f5a000, pmd_val = 0x36f5a063, pmd_index = 453
[ 3319.130912] pte_addr = 0xffff9346f6f5ae30, *pte = 0x8000000038bc6063, pte_val = 0x8000000038bc6063, ptd_index = 454
[ 3319.130914] page_addr = 8000000038bc6000, page_offset = 0
[ 3319.130916] vaddr = ffff9346f8bc6000, paddr = 8000000038bc6000
[ 3326.641119] vaddr to paddr module is leaving..

Linux-based debugging

When dealing with page fault interrupts, calculate pgd_index according to the virtual address, and obtain the address information of the target pgd 

Calculate pud_index based on the virtual address, if the given pud does not exist, create a new one, and get the address information of the pud 

Calculate pmd_index based on the virtual address, if the given pmd does not exist, create a new one, and get the address information of the pmd 

Then the next step is to allocate physical pages specifically, and record the address information of pte in pmd 

pte_alloc_one is the operation of allocating physical pages that we can see here, allocating physical pages based on alloc_pages 

Then pmd_populate records the address information of the physical page pointed to by the current pmd entry, corresponding to the information stored in pte in pmd above 

Structure of pgd, pud, pmd, pte 

The structure here is the structure under x86_64 

Each pgd/pud/pmd/pte structure is an 8-byte entry

typedef struct { pgdval_t pgd; } pgd_t;
typedef struct { pudval_t pud; } pud_t;
typedef struct { pmdval_t pmd; } pmd_t;
typedef struct page *pgtable_t;

/*
 * These are used to make use of C type-checking..
 */
typedef unsigned long	pteval_t;
typedef unsigned long	pmdval_t;
typedef unsigned long	pudval_t;
typedef unsigned long	pgdval_t;
typedef unsigned long	pgprotval_t;

over

reference 

Linux kernel learning 3 - convert virtual address to physical address z

Guess you like

Origin blog.csdn.net/u011039332/article/details/128063043