首先说说内核态虚拟地址和物理内存地址转换关系
- #define PAGE_OFFSET UL(0xffffffc000000000)
- /* PHYS_OFFSET - the physical address of the start of memory. */
- #define PHYS_OFFSET ({ memstart_addr; })
- //把内核态虚拟地址转成物理地址
- #define __virt_to_phys(x) (((phys_addr_t)(x) - PAGE_OFFSET + PHYS_OFFSET))
- //把物理内存地址转成内核态虚拟地址
- #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指针的关系
- #define ARCH_PFN_OFFSET (PAGE_OFFSET >> PAGE_SHIFT)
- //内存单元page指针数组,mem_map+0代表第1个内存单元page,mem_map+1代表第2个内存单元page...
- struct page *mem_map;
- //把页帧转成内存单元对应的page
- #define __pfn_to_page(pfn) (mem_map + ((pfn) - ARCH_PFN_OFFSET))
- //把内存单元对应的page转成页帧
- #define __page_to_pfn(page) ((unsigned long)((page) - mem_map) + ARCH_PFN_OFFSET)
- //把内存单元对应page转成页帧
- #define page_to_pfn __page_to_pfn
- //把页帧转成其内存单元对应page
- #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就是页帧。
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。
还有其他快捷的转换
- //把内核虚拟地址转成页帧
- #define virt_to_pfn(kaddr) (__pa(kaddr) >> PAGE_SHIFT)
- //把页帧转成内核虚拟地址
- #define pfn_to_virt(pfn) __va((pfn) << PAGE_SHIFT)
- //把内核虚拟地址转成其内存单元对应page
- #define virt_to_page(addr) pfn_to_page(virt_to_pfn(addr))
- //把内存单元对应page转成内核虚拟地址
- #define page_to_virt(page) pfn_to_virt(page_to_pfn(page))
- //把内核态虚拟地址转成物理地址
- #define __pa(x) __virt_to_phys((unsigned long)(x))
- //把物理地址转成内核态虚拟地址
- #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、物理地址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。下边用示意图让我们有个更清晰的认识