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

   日期:2024-12-26    作者:az2eq 移动:http://oml01z.riyuangf.com/mobile/quote/33214.html

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

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

  1. #define PAGE_OFFSET     UL(0xffffffc000000000)
  2. #define PHYS_OFFSET     ({ memstart_addr; })
  3. //把内核态虚拟地址转成物理地址
  4. #define __virt_to_phys(x)   (((phys_addr_t)(x) - PAGE_OFFSET + PHYS_OFFSET))
  5. //把物理内存地址转成内核态虚拟地址
  6. #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

 

内核打印

 

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


特别提示:本信息由相关用户自行提供,真实性未证实,仅供参考。请谨慎采用,风险自负。


举报收藏 0评论 0
0相关评论
相关最新动态
推荐最新动态
点击排行
{
网站首页  |  关于我们  |  联系方式  |  使用协议  |  隐私政策  |  版权隐私  |  网站地图  |  排名推广  |  广告服务  |  积分换礼  |  网站留言  |  RSS订阅  |  违规举报  |  鄂ICP备2020018471号