常用转换

虚拟地址和物理地址

__pa_symbol()

获取内核程序中符号的物理地址,也就是nm vmlinux命令能显示出的内容。

比如 nm vmlinux | grep -w _text,其结果是“ffffffff81000000 T _text”。

#define __START_KERNEL_map	_AC(0xffffffff80000000, UL)


#define __pa_symbol(x) \
	__phys_addr_symbol(__phys_reloc_hide((unsigned long)(x)))


#define __phys_addr_symbol(x) \
	((unsigned long)(x) - __START_KERNEL_map + phys_base)

这里我们只看x86_64的定义。从定义中我们可以看到,符号的物理地址是虚拟地址 减去 __START_KERNEL_map 再加上 phys_base。

当配置了kaslr时,内核加载的物理地址和编译时的加载地址会有个偏移,这个偏移记录在phys_base。具体解释可见常用全局变量

正因为phys_base的偏移是基于__START_KERNEL_map计算出来的,所以如此计算后就得到了内核代码中符号的物理地址。

__pa()

计算出虚拟地址对应的物理地址。

其实这里计算的时候分了两种情况。

  • 虚拟地址 > __START_KERNEL_map

  • 其他虚拟地址

第一种情况,说明虚拟地址在内核代码空间,所以实际上就退化成和__pa_symbol()一样。 第二种情况,转换过程和__va相反。这说明传入的虚拟地址需要是在内核页表上映射的空间内。

__va() / phys_to_virt()

只能对有内核映射的地址调用该函数,来获得对应地址的虚拟地址。

再x86_64架构下,大概率是这么个定义。

也就是一个物理地址(非内核代码范围内的)的虚拟地址 = 虚拟地址 + 0xffff888000000000。

这是因为在映射整个内存空间时,是这么操作的。具体参见映射完整物理地址

pfn和struct page

这里先列出只有sparsemem的情况

__pfn_to_page(pfn)

先根据pfn找到对应的section,然后对section->section_mem_map做一个偏移运算得到struct page的地址。

__page_to_pfn(pg)

先根据page找到对应的section,注意这里找是因为把sectino number写在了page->flags里。 然后再对section->section_mem_map做一个偏移得到pfn。

虚拟地址和struct page

有了上面两个转换,自然可以推导出这个转换。也就是形成了地址和struct page之间的关系。

virt_to_page()

这个转换没有什么太多可以解释的,不过这里有另一个检查的函数值得一看。

就是这个用来判断虚拟地址是否有效的函数。可以看出在内核中认为有效的虚拟地址空间有两个:

  • __START_KERNEL_map以上的内核代码空间

  • PAGE_OFFSET以上的direct mapping空间

其余部分都是无效空间。

page_to_virt()

Last updated

Was this helpful?