Linux内存模型

前言

Linux中的物理内存被按页框划分,每个页框都会对应一个struct page结构体存放元数据,也就是说每块页框大小的内存都要花费sizeof(struct page)个字节进行管理
内存模型的设计则主要是权衡以下两点(空间与时间):

  1. 尽量少的消耗内存去管理众多的struct page
  2. pfn_to_page和page_to_pfn的转换效率。

稀疏内存模型是当前内核默认的选择,从2005年被提出后沿用至今,但中间经过几次优化,包括:CONFIG_SPARSEMEM_VMEMMAP和CONFIG_SPARSEMEM_EXTREME的引入,这两个配置通常是被打开的,下面的原理介绍也会基于它们开启的情况。

1.SPARSEMEM原理:

  • section的概念:
    SPARSEMEM内存模型引入了section的概念,可以简单将它理解为struct page的集合(数组)。内核使用struct mem_section去描述section,定义如下:
struct mem_section {
        unsigned long section_mem_map;
        /* See declaration of similar field in struct zone */
        unsigned long *pageblock_flags;
};

其中的section_mem_map成员存放的是struct page数组的地址,每个section可容纳PFN_SECTION_SHIFT个struct page,arm64地址位宽为48bit时定义了每个section可囊括的地址范围是1GB。

  • 全局变量**mem_section
    内核中用了一个二级指针struct mem_section **mem_section去管理section,我们可以简单理解为一个动态的二维数组。所谓二维即内核又将SECTIONS_PER_ROOT个section划分为一个ROOT,ROOT的个数不是固定的,根据系统实际的物理地址大小来分配。
  • 物理页帧号PFN
    SPARSEMEM将PFN差分成了三个level,每个level分别对应:ROOT编号、ROOT内的section偏移、section内的page偏移。(可以类比多级页表来理解)
  • vmemmap区域
    vmemmap区域是一块起始地址是VMEMMAP_START,范围是2TB的虚拟地址区域,位于kernel space。以section为单位来存放strcut page结构的虚拟地址空间,然后线性映射到物理内存。
  • 内存热插拔
    SPARSEMEM中section是最小管理单元。内存热插拔也是以section为单位,下图中画的热插拔单位是一个section(ARM64是1GB),通常会大于等于一个section。
    通过下图来可以很好的串联上面几个概念:
    在这里插入图片描述
  • PFN和struct page的转换:
    SPARSEMEM中__pfn_to_page和__page_to_pfn的实现如下:
#define __pfn_to_page(pfn)      (vmemmap + (pfn))
#define __page_to_pfn(page)     (unsigned long)((page) - vmemmap)      
#define vmemmap        ((struct page *)VMEMMAP_START - (memstart_addr >> PAGE_SHIFT))

其中vmemmap指针指向VMEMMAP_START偏移memstart_addr的地址处,memstart_addr则是根据物理起始地址PHYS_OFFSET算出来的偏移,上图画出了三者之间的关系。

2.vmemmap在虚拟地址空间位置

内核虚拟地址空间布局随着内核的迭代不断变化,具体细节可以查看vmlinux.lds和arch/arm64/include/asm/memory.h等文件。举例如下:
linux4.9的内核地址空间布局:
在这里插入图片描述
linux5.4的内核地址空间布局:
在这里插入图片描述

3.virt,phys,page,pfn之间的转换关系

3.1内核态虚拟地址和物理内存地址转换关系

#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))

3.2页帧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

3.3其他快捷的转换

//把内核虚拟地址转成页帧
#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)))

总结

  • 高效
    通过上面__pfn_to_page的实现可以看出其仅用一步计算即可找到对应的struct page。

  • 省内存
    那另一个目标节省内存是如何做到的呢?答案就是按需分配,内存空洞或者被拔掉的内存条,我们不给它分配存放struct page的物理内存(只有虚拟内存)。

  • 物理内存page、页帧pfn、内核虚拟地址virt、物理内存地址phys的转换关系

virt=phys-PHYS_OFFSET + PAGE_OFFSET
pfn=phys/4K
page=mem_map + (pfn - ARCH_PFN_OFFSET)

连续两个内存单元,内核虚拟地址virt差0x1000,即4K;phys物理地址相差0x1000;page指针相差0x40,struct page 大小sizeof(struct page)正是0x40;页帧pfn相差1。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值