如何在内核模块中创建页对齐缓冲区并获取其物理地址?
一、创建页对齐缓冲区的可靠方法
内核提供了专门的内存分配接口来保证页对齐,以下是几种常用方式:
使用
__get_free_pages()/get_free_pages()
这两个函数直接分配连续的物理页,返回的虚拟地址天然是页对齐的。__get_free_pages()是无锁版本,适合中断上下文;get_free_pages()带锁,适合进程上下文。
示例代码:// 分配1个物理页(大小为PAGE_SIZE),GFP_KERNEL为分配掩码 char *buf = (char *)__get_free_pages(GFP_KERNEL, 0); if (!buf) { // 分配失败处理 return -ENOMEM; } // 若需要N个连续页,第二个参数为order(2^order个页),比如分配2个页: // char *buf = (char *)__get_free_pages(GFP_KERNEL, 1); // 使用完后释放内存 // free_pages((unsigned long)buf, order);使用
alloc_pages()+page_address()
先获取struct page结构体指针,再转换为虚拟地址,适合需要直接操作页结构体的场景:// 分配1个物理页 struct page *page = alloc_pages(GFP_KERNEL, 0); if (!page) { return -ENOMEM; } char *buf = page_address(page); // 释放内存 // __free_pages(page, 0);kmalloc()的页对齐用法(限小内存)
如果缓冲区大小小于等于PAGE_SIZE,也可以用kmalloc()配合对齐掩码强制页对齐,但这种方式不如页分配函数直接可靠:char *buf = kmalloc(BUF_LEN, GFP_KERNEL | __GFP_ALIGN_MASK);
二、获取物理地址的正确方式
对于上述页对齐的缓冲区,获取物理地址推荐以下两种方式:
通过
struct page获取(最稳妥)
如果持有struct page指针,直接用page_to_phys()转换:phys_addr_t phys_addr = page_to_phys(page);如果只有虚拟地址,可先通过
virt_to_page()转换为struct page:struct page *page = virt_to_page(buf); phys_addr_t phys_addr = page_to_phys(page);关于
virt_to_phys()的说明
内核模块中的静态变量(如你原来的static char buf[BUF_LEN])位于线性映射区,virt_to_phys()确实可以转换其虚拟地址到物理地址,但静态数组无法保证页对齐,即便转换成功也不符合需求。对于页分配函数得到的虚拟地址,virt_to_phys()同样有效,但page_to_phys()兼容性更好,更推荐使用。
总结
你原来的静态数组无法保证页对齐,必须改用内核的页分配接口(如__get_free_pages()或alloc_pages())创建缓冲区,再通过page_to_phys()获取物理地址,就能满足SVSM环境对页对齐物理地址的要求。
内容的提问来源于stack exchange,提问作者Pinko

