首先说说内核态虚拟地址和物理内存地址转换关系
phys:物理内存地址
virt:内核态虚拟地址
virt内核态虚拟地址与phys物理内存地址是一个线性偏移关系,二者计算公式是 virt=phys-PHYS_OFFSET + PAGE_OFFSET。二者的转换关系可直接调用 __virt_to_phys 和 __phys_to_virt 两个内核宏。PHYS_OFFSET 代表物理内存首地址,PAGE_OFFSET是基于体系架构的偏移值,不同的架构不一样
我们在内核里调用kmalloc 返回的就是内核态虚拟地址。内核态虚拟地址属于什么?我们知道,系统空间分配用户空间和内核空间,比如有些32系统,0~3G 属于用户空间,3G~4G 属于内核空间。内核空间又由直接映射区、高端映射区(包含vmaloc区、动态映射区、固定映射区)组成,我们kmalloc分配内存就是从normal 直接映射区的分配一片内核空间,这片空间的内存地址便是内核态虚拟地址,与物理内存构成线性偏移关系。表面是从直接映射区内核空间分配走一片内核虚拟空间,实际在读写这片内存时,读写对应的是构成映射关系的物理内存。
接下来说说页帧pfn、物理内存的page指针的关系
内核把物理内存以4K大小分成一个个内存单元,每一个内存单元都用struct page结构管理。 mem_map 原型是struct page *mem_map,管理系统的物理内存。可以理解成它是一个指针数组,成员是每个内存单元的struct page指针,比如struct page *mem_map={page0,page1,page2,page3...},mem_map指向第一个内存的page指针。pfn叫做页帧,代表一个内存单元的物理起始地址,实际计算方法是pfn=phys/4K ,即物理内存地址除以4K就是页帧。
pfn和page的转换关系是。page=mem_map + (pfn - ARCH_PFN_OFFSET),ARCH_PFN_OFFSET 应该是第一个物理内存单元的页帧。第1个内存单元page=mem_map+( ARCH_PFN_OFFSET - ARCH_PFN_OFFSET)= mem_map+0,第2个内存单元page=mem_map+(ARCH_PFN_OFFSET+1 - ARCH_PFN_OFFSET) = mem_map+1。
还有其他快捷的转换
再总结一下物理内存page、页帧pfn、内核虚拟地址virt、物理内存地址phys的转换关系
virt=phys-PHYS_OFFSET + PAGE_OFFSET
pfn=phys/4K
page=mem_map + (pfn - ARCH_PFN_OFFSET)
内核里做一个实验,分配连续的物理内存,16K大小,然后以4K为单位打印每个内存单元对应的内核虚拟地址virt、物理地址phys、page指针、页帧pfn。
unsigned long virt,tmp;
tmp = __get_free_pages(GFP_KERNEL,get_order(16*1024));
virt = tmp;
printk("virt:0x%lx phys:0x%lx page:0x%p pfn:%ld\n",virt,(unsigned long)virt_to_phys((void *)virt),virt_to_page((void *)virt),page_to_pfn(virt_to_page((void *)virt)));
virt = tmp + 4*1024;
printk("virt:0x%lx phys:0x%lx page:0x%p pfn:%ld\n",virt,(unsigned long)virt_to_phys((void *)virt),virt_to_page((void *)virt),page_to_pfn(virt_to_page((void *)virt)));
virt = tmp + 8*1024;
printk("virt:0x%lx phys:0x%lx page:0x%p pfn:%ld\n",virt,(unsigned long)virt_to_phys((void *)virt),virt_to_page((void *)virt),page_to_pfn(virt_to_page((void *)virt)));
内核打印
virt:0xffff8bab7da38000 phys:0x2f3da38000 page:0xffffeb8c3cf68e00 pfn:49535544
virt:0xffff8bab7da39000 phys:0x2f3da39000 page:0xffffeb8c3cf68e40 pfn:49535545
virt:0xffff8bab7da3a000 phys:0x2f3da3a000 page:0xffffeb8c3cf68e80 pfn:49535546
可以发现连续两个内存单元,内核虚拟地址virt差0x1000,即4K;phys物理地址相差0x1000;page指针相差0x40,struct page 大小sizeof(struct page)正是0x40;页帧pfn相差1。下边用示意图让我们有个更清晰的认识