前言
一、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 <linux/init.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/mm_types.h>
# include <linux/mm.h>
# include <linux/gfp.h>
//内核模块初始化函数
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://www.toymoban.com/news/detail-651723.html
https://blog.csdn.net/hu1610552336/article/details/113083454文章来源地址https://www.toymoban.com/news/detail-651723.html
到了这里,关于Linux 内核内存管理 virt_to_page 函数的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!