page_to_pfn 、virt_to_page、 virt_to_phys、page、页帧pfn、内核虚拟地址、物理内存地址linux内核源码详解

首先说说内核态虚拟地址和物理内存地址转换关系

  1. #define PAGE_OFFSET     UL(0xffffffc000000000)
  2. /* PHYS_OFFSET - the physical address of the start of memory. */
  3. #define PHYS_OFFSET     ({ memstart_addr; })
  4. //把内核态虚拟地址转成物理地址
  5. #define __virt_to_phys(x)   (((phys_addr_t)(x) - PAGE_OFFSET + PHYS_OFFSET))
  6. //把物理内存地址转成内核态虚拟地址
  7. #define __phys_to_virt(x)   ((unsigned long)((x) - PHYS_OFFSET + PAGE_OFFSET))

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指针的关系

  1. #define ARCH_PFN_OFFSET     (PAGE_OFFSET >> PAGE_SHIFT)
  2. //内存单元page指针数组,mem_map+0代表第1个内存单元pagemem_map+1代表第2个内存单元page...
  3. struct page *mem_map;
  4. //把页帧转成内存单元对应的page
  5. #define __pfn_to_page(pfn)  (mem_map + ((pfn) - ARCH_PFN_OFFSET))
  6. //把内存单元对应的page转成页帧
  7. #define __page_to_pfn(page) ((unsigned long)((page) - mem_map) + ARCH_PFN_OFFSET)
  8. //把内存单元对应page转成页帧
  9. #define page_to_pfn __page_to_pfn
  10. //把页帧转成其内存单元对应page
  11. #define pfn_to_page __pfn_to_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就是页帧。

pfnpage的转换关系是。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

还有其他快捷的转换

  1. //把内核虚拟地址转成页帧
  2. #define virt_to_pfn(kaddr)  (__pa(kaddr) >> PAGE_SHIFT)
  3. //把页帧转成内核虚拟地址
  4. #define pfn_to_virt(pfn)    __va((pfn) << PAGE_SHIFT)
  5. //把内核虚拟地址转成其内存单元对应page
  6. #define virt_to_page(addr)  pfn_to_page(virt_to_pfn(addr))
  7. //把内存单元对应page转成内核虚拟地址
  8. #define page_to_virt(page)  pfn_to_virt(page_to_pfn(page))
  9. //把内核态虚拟地址转成物理地址
  10. #define __pa(x)         __virt_to_phys((unsigned long)(x))
  11. //把物理地址转成内核态虚拟地址
  12. #define __va(x)         ((void *)__phys_to_virt((phys_addr_t)(x)))

再总结一下物理内存page、页帧pfn、内核虚拟地址virt、物理内存地址phys的转换关系

virt=phys-PHYS_OFFSET + PAGE_OFFSET

pfn=phys/4K

page=mem_map + (pfn - ARCH_PFN_OFFSET)

内核里做一个实验,分配连续的物理内存,16K大小,然后以4K为单位打印每个内存单元对应的内核虚拟地址virt、物理地址physpage指针、页帧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

可以发现连续两个内存单元,内核虚拟地址virt0x1000,即4Kphys物理地址相差0x1000page指针相差0x40struct page 大小sizeof(struct page)正是0x40;页帧pfn相差1。下边用示意图让我们有个更清晰的认识

page_to_pfn 、virt_to_page、 virt_to_phys、page、页帧pfn、内核虚拟地址、物理内存地址linux内核源码详解_第1张图片

你可能感兴趣的:(内核,内存管理,linux)