Linux 内核内存管理 virt_to_page 函数

前言

一、virt_to_page

virt_to_page宏根据内核虚拟地址返回其struct page 结构体指针。

x86_64:

// linux-5.4.18/arch/x86/include/asm/page.h

/* PAGE_SHIFT determines the page size */
#define PAGE_SHIFT	12
#define virt_to_page(kaddr)	pfn_to_page(__pa(kaddr) >> PAGE_SHIFT)

virt_to_page(kaddr) 宏通过__pa宏将虚拟地址转换为物理地址,然后右移PAGE_SHIFT(12位)位将物理地址转换为页帧号pfn,最后调用pfn_to_page将页帧号pfn转换为struct page 结构体指针,实现了将虚拟地址转换为对应struct page结构体指针的功能。

内核虚拟起始地址 --> 物理页面的物理起始地址 --> 页帧号pfn --> struct page 

其中__pa宏用来内核虚拟地址转化为物理地址:

// linux-5.4.18/arch/x86/include/asm/page_64.h

static inline unsigned long __phys_addr_nodebug(unsigned long x)
{
	unsigned long y = x - __START_KERNEL_map;

	/* use the carry flag to determine if x was < __START_KERNEL_map */
	x = y + ((x > y) ? phys_base : (__START_KERNEL_map - PAGE_OFFSET));

	return x;
}

#define __phys_addr(x)		__phys_addr_nodebug(x)

#define __pa(x)		__phys_addr((unsigned long)(x))

aarch64:

#if defined(CONFIG_SPARSEMEM_VMEMMAP)

#define virt_to_page(x)	({						\
	u64 __idx = (__tag_reset((u64)x) - PAGE_OFFSET) / PAGE_SIZE;	\
	u64 __addr = VMEMMAP_START + (__idx * sizeof(struct page));	\
	(struct page *)__addr;						\
})

二、demo

测试平台:centos 7 x86_64

# include 
# include 
# include 
# include 
# include 
# include 

//内核模块初始化函数
static int __init lkm_init(void)
{
    //调用伙伴系统接口分配 2^2 = 4 个连续的物理页,返回其内核虚拟起始地址
    unsigned long virt_address = __get_free_pages(GFP_KERNEL, 2);

    printk("virtual addr = 0x%lx\n", virt_address);
    printk("sizeof(struct page) = 0x%lx\n", sizeof(struct page));

    int i;
    for(i = 0; i < 4; i++ ){
        unsigned long virt_address_1 = virt_address + i * 4096;
        unsigned long phys_address_1 = __pa(virt_address_1);
        unsigned int pfn_1 = __phys_to_pfn(phys_address_1);
        struct page *page_1 = pfn_to_page(pfn_1);

        struct page *page_2 = virt_to_page(virt_address_1);
        printk("virt_address = 0x%lx, phys_address = 0x%lx, pfn = %d, struct page address = 0x%p, struct page address = 0x%p\n",
                virt_address_1, phys_address_1,pfn_1, page_1, page_2);
    }

    free_pages(virt_address, 2);

    return 0;

}

//内核模块退出函数
static void __exit lkm_exit(void)
{
	printk("Goodbye\n");
}


module_init(lkm_init);
module_exit(lkm_exit);

MODULE_LICENSE("GPL");

结果展示:

[199302.056485] virtual addr = 0xffff9d991cbd0000
[199302.056491] sizeof(struct page) = 0x40
[199302.056498] virt_address = 0xffff9d991cbd0000, phys_address = 0x5cbd0000, pfn = 379856, struct page address = 0xffffe2784172f400, struct page address = 0xffffe2784172f400
[199302.056504] virt_address = 0xffff9d991cbd1000, phys_address = 0x5cbd1000, pfn = 379857, struct page address = 0xffffe2784172f440, struct page address = 0xffffe2784172f440
[199302.056509] virt_address = 0xffff9d991cbd2000, phys_address = 0x5cbd2000, pfn = 379858, struct page address = 0xffffe2784172f480, struct page address = 0xffffe2784172f480
[199302.056514] virt_address = 0xffff9d991cbd3000, phys_address = 0x5cbd3000, pfn = 379859, struct page address = 0xffffe2784172f4c0, struct page address = 0xffffe2784172f4c0

可以看到当通过伙伴系统接口申请连续的物理内存,每个物理页内核虚拟地址连续,物理地址连续,相差4096(0x1000),即页帧号也连续,相差1。struct page结构体也要占用实际的物理内存,其物理内存地址也连续,相差0x40。

参考资料

Linux 5.4.18

https://blog.csdn.net/hu1610552336/article/details/113083454

你可能感兴趣的:(Linux,内核常用API,linux,c语言)