在内核FLAT和DISCONTIGMEM管理模型中,其实一直都存在两个问题

  • 管理物理内存的数据结构本身占用内存较多,不使用于较大内存情况
  • 无法解决空洞问题,不管是FLAT还是SPARCE模型都无法解决一个节点内的内存空洞问题,必须是一段连续空间即mem_map数组就很大,即使中间存在黑洞,mem_map数组必须也得申请,比较浪费空间

基于上述两个主要问题linux 物理内存模型技术演进到SPARCE模型 本意是稀疏即不再是连续的,space模式是从https://lwn.net/Articles/134804/开引入到内核中,下面说明了该模型的几个优势:

Sparsemem abstracts the use of discontiguous mem_maps[].This kind of mem_map[] is needed by discontiguous memory machines (like in the old CONFIG_DISCONTIGMEM case) as well as memory hotplug systems.Sparsemem replaces DISCONTIGMEM when enabled, and it is hoped that it can eventually become a complete replacement. A significant advantage over DISCONTIGMEM is that it's completely separated from CONFIG_NUMA.  When producing this patch, it became apparent in that NUMA and DISCONTIG are often confused.

Another advantage is that sparse doesn't require each NUMA node's ranges to be contiguous.  It can handle overlapping ranges between nodes with no problems, where DISCONTIGMEM currently throws away that memory.

如今大部分系统都是采用SPARCE模型,但是如果不了解前两种模型,那么对理解第三种模型意义就显得没有那么深入。

SPARSE模型

针对DISCONTIGMEM模型中一个节点内的mem_map数组必须连续的这一问题,SPARSE模型对技术提出了演进方案,将ZONE_MEM_MAP这一个数组进一步划分颗粒度,将内存划分为一段段SECTION以减少因为空洞带来的mem_map空间浪费,在每个section内划分section mem map,其管理框架大概如下:

物理内存按照实际情况被划分成一段段section进行管理,每个ZONE下面有若干个section组成,一个节点内的mem_map即不在是连续的,只有存在实际物理内存时才会有相应的section以及section mem map,同时继承了DISCONTIGMEM模型中全局mem_map为一个虚拟的数组一样,SPARSE模型也有一个虚拟的全局数组为vmem_map,整个全局section分布如下图(该图来源于https://blog.csdn.net/bin_linux96/article/details/83343420)所示:

mem_section管理

整个mem_section由一个全局mem_section进行管理所有的内存,以方便后续进行转换:

#ifdef CONFIG_SPARSEMEM_EXTREME
extern struct mem_section **mem_section;
#else
extern struct mem_section mem_section[NR_SECTION_ROOTS][SECTIONS_PER_ROOT];
#endif

section为管理内存的一个基本单位,所有section都归属于mem_section,mem_section 为一个二维数组,第一维index为 NR_SECTION_ROOTS,二维index为SECTIONS_PER_ROOT,以便减少空间浪费。一个64位系统中的一个虚拟地址被划分为SECTION_ROOTS、SECTION_PER_ROOT、PFN以及页偏移等,以下为arm 64位的地址划分方法(来自于知乎大神Yannhttps://zhuanlan.zhihu.com/p/220068494)具体划分如下:

整个PFNP被划分位ROOT NUMBER、SECTIONS_PER_ROOT以及PFN_SECTION_SHIT(即对应的mem_section内的PFN)。每位架构不相同,其划分地址范围可能不同。

PAGE与PFN转换

SPARCE同样定义了PAGE与PFN宏如下:

#define page_to_pfn __page_to_pfn
#define pfn_to_page __pfn_to_page

page_to_pfn(classic sparse方法)

将相应物理页转换成pfn,调用的时__page_to_pfn:

#define __page_to_pfn(pg)                    \
({  const struct page *__pg = (pg);                \int __sec = page_to_section(__pg);            \(unsigned long)(__pg - __section_mem_map_addr(__nr_to_section(__sec)));    \
})

pg为指向section数组中的page 结构地址,具体思路

  • 首先调用page_to_section将物理页转换成section 查找要相应的section。
  • 然后根据section获取到相应的section的第一个页的地址即对应的相应的mem_section的mem_map的首地址,
  • 最后根据page的地址减去section mem_map的地址求出pfn

page_to_section(classic sparse方法)

page_to_section函数定如下:

static inline unsigned long page_to_section(const struct page *page)
{return (page->flags >> SECTIONS_PGSHIFT) & SECTIONS_MASK;
}

可以看到section 可以同时存储到page flag中,其page flags可以划分以下几个部分:

将SECTION、NODE等信息存储到flags中主要是为了节省struct page内存空间,各个段 的大小以及位置有相应宏定义:

#define SECTIONS_PGOFF       ((sizeof(unsigned long)*8) - SECTIONS_WIDTH)
#define NODES_PGOFF     (SECTIONS_PGOFF - NODES_WIDTH)
#define ZONES_PGOFF     (NODES_PGOFF - ZONES_WIDTH)
#define LAST_CPUPID_PGOFF   (ZONES_PGOFF - LAST_CPUPID_WIDTH)
#define KASAN_TAG_PGOFF     (LAST_CPUPID_PGOFF - KASAN_TAG_WIDTH)/** Define the bit shifts to access each section.  For non-existent* sections we define the shift as 0; that plus a 0 mask ensures* the compiler will optimise away reference to them.*/
#define SECTIONS_PGSHIFT    (SECTIONS_PGOFF * (SECTIONS_WIDTH != 0))
#define NODES_PGSHIFT       (NODES_PGOFF * (NODES_WIDTH != 0))
#define ZONES_PGSHIFT       (ZONES_PGOFF * (ZONES_WIDTH != 0))
#define LAST_CPUPID_PGSHIFT (LAST_CPUPID_PGOFF * (LAST_CPUPID_WIDTH != 0))
#define KASAN_TAG_PGSHIFT   (KASAN_TAG_PGOFF * (KASAN_TAG_WIDTH != 0))/* NODE:ZONE or SECTION:ZONE is used to ID a zone for the buddy allocator */
#ifdef NODE_NOT_IN_PAGE_FLAGS
#define ZONEID_SHIFT        (SECTIONS_SHIFT + ZONES_SHIFT)
#define ZONEID_PGOFF        ((SECTIONS_PGOFF < ZONES_PGOFF)? \SECTIONS_PGOFF : ZONES_PGOFF)
#else
#define ZONEID_SHIFT        (NODES_SHIFT + ZONES_SHIFT)
#define ZONEID_PGOFF        ((NODES_PGOFF < ZONES_PGOFF)? \NODES_PGOFF : ZONES_PGOFF)
#endif#define ZONEID_PGSHIFT        (ZONEID_PGOFF * (ZONEID_SHIFT != 0))#define ZONES_MASK     ((1UL << ZONES_WIDTH) - 1)
#define NODES_MASK      ((1UL << NODES_WIDTH) - 1)
#define SECTIONS_MASK       ((1UL << SECTIONS_WIDTH) - 1)
#define LAST_CPUPID_MASK    ((1UL << LAST_CPUPID_SHIFT) - 1)
#define KASAN_TAG_MASK      ((1UL << KASAN_TAG_WIDTH) - 1)
#define ZONEID_MASK     ((1UL << ZONEID_SHIFT) - 1)

__nr_to_section

根据相应的nr从mem_section数组中查找到相应的struct mem_section结构:

static inline struct mem_section *__nr_to_section(unsigned long nr)
{
#ifdef CONFIG_SPARSEMEM_EXTREMEif (!mem_section)return NULL;
#endifif (!mem_section[SECTION_NR_TO_ROOT(nr)])return NULL;return &mem_section[SECTION_NR_TO_ROOT(nr)][nr & SECTION_ROOT_MASK];
}

此时从page中查到section nr是基于mem_section的一个全局索引,需要将其转换成二维索引,其nr划分如下:

__section_mem_map_addr

获取到 mem_section中的section mem map:

static inline struct page *__section_mem_map_addr(struct mem_section *section)
{unsigned long map = section->section_mem_map;map &= SECTION_MAP_MASK;return (struct page *)map;
}

其中section mem_map的基地为map,将其基地返回

计算出pfn

最后根据根据page 地址计算出在mem map中pfn:

(__pg - __section_mem_map_addr(__nr_to_section(__sec)))

核心思路

 根据page 计算出pfn核心思路就是根据page中的flag获取到相对应的section,然后根据section中的mem_map计算出该page的pfn.理解page中的flag划分 以及nr的划分非常重要。

注意section_mem_map里面包含一层转换,使转换出的pfn为全局pfn,而不是section mem_map内的pfn。

pfn_to_page

根据pfn查找到相应page,实现思路和page_to_pfn相反,根据pfn查找到相对应的mem section然后根据pfn获取到mem secton中的物理页

#define __pfn_to_page(pfn)               \
({  unsigned long __pfn = (pfn);           \struct mem_section *__sec = __pfn_to_section(__pfn);  \__section_mem_map_addr(__sec) + __pfn;        \
})

__pfn_to_section

__pfn_to_section根据pfn查找到相对应的section:

static inline struct mem_section *__pfn_to_section(unsigned long pfn)
{return __nr_to_section(pfn_to_section_nr(pfn));
}

pfn_to_setion_nr从pfn中获取到,pfn整个划分如下:

获取到section_mem_map

__section_mem_map_addr(__sec)

根据获取到的mem_section调用__section_mem_map_addr,

计算page

__section_mem_map_addr(__sec) + __pfn

获取到section_mem_map然后根据pfn获取到物理页。

核心思路

需要了解pfn的划分以及 section_mem_map的基地址在初始化已经转换为全局,不需要先转成section_mem内的index 再转成全局pfn,只需要转成一次就可以

vmemmap(sparse vmemmap方法)

述转换过程可以看出每次转换都需要先找到对应的mem_section 然后找到对应的section_mem_map转换效率过低,为了提高转换效率,内核中使用vmemmap虚拟mem map数组来提高效率,相当于DISCONTIGMEM模型中的全局mem_map数组,引入vmemap数组之后转换得到了很大提高,之间查找数组偏移即可,代码如下:

#define __pfn_to_page(pfn)   (vmemmap + (pfn))
#define __page_to_pfn(page) (unsigned long)((page) - vmemmap)

vmemmap数组功能需要使用CONFIG_SPARSEMEM_VMEMMAP宏开启。

linux内核那些事之物理内存模型之SPARSE(3)相关推荐

  1. linux内核那些事之物理内存模型之DISCONTIGMEM(2)

    距离写linux物理内存模型-FLATMEM已经过去很久了,终于能够抽时间能够继续整下,中间这么长时间一直在看不同版本的内核代码,加之工作比较忙,希望自己能够坚持下去. UMA VS NUMA DIS ...

  2. linux内核那些事之物理内存模型之FLATMEM(1)

    linux内核中物理内存管理是其中比较重要的一块,随着内核从32位到64位发展,物理内存管理也不断进行技术更新,按照历史演进共有FLATMEM.DISCONTIGMEM以及SPRARSEMEM模型.( ...

  3. linux内核那些事之Sparse vmemmap

    <inux内核那些事之物理内存模型之SPARCE(3)>中指出在传统的sparse 内存模型中,每个mem_section都有一个属于自己的section_mem_map,如下图所示: 而 ...

  4. linux内核那些事之buddy

    buddy算法是内核中比较古老的一个模块,很好的解决了相邻物理内存碎片的问题即"内碎片问题",同时有兼顾内存申请和释放效率问题,内核从引入该算法之后一直都能够在各种设备上完好运行, ...

  5. linux内核那些事之pg_data_t、zone结构初始化

    free_area_init 继续接着<linux内核那些事之ZONE>,分析内核物理内存初始化过程,zone_sizes_init()在开始阶段主要负责对各个类型zone 大小进行计算, ...

  6. linux内核那些事之buddy(慢速申请内存__alloc_pages_slowpath)(5)

    内核提供__alloc_pages_nodemask接口申请物理内存主要分为两个部分:快速申请物理内存get_page_from_freelist(linux内核那些事之buddy(快速分配get_p ...

  7. linux内核那些事之buddy(anti-fragment机制)(4)

    程序运行过程中,有些内存是短暂的驻留 用完一段时间之后就可以将内存释放以供后面再次使用,但是有些内存一旦申请之后,会长期使用而得不到释放.长久运行有可能造成碎片.以<professional l ...

  8. linux内核那些事之buddy(anti-fragment机制-steal page)(5)

    继<linux内核那些事之buddy(anti-fragment机制)(4)>,在同一个zone内指定的migrate type中没有足够内存,会启动fallback机制,从fallbac ...

  9. linux内核那些事之mmap_region流程梳理

    承接<linux内核那些事之mmap>,mmap_region()是申请一个用户进程虚拟空间 并根据匿名映射或者文件映射做出相应动作,是实现mmap关键函数,趁这几天有空闲时间 整理下mm ...

最新文章

  1. 【数据库】 兴唐第二十七节课只sql注入
  2. String SringBuffer StringBuilder区别
  3. PHP学习笔记-数组
  4. TabLayout+ViewPager更新fragment的ui数据
  5. 最新进展概述:澄清式提问辅助理解信息检索中的用户意图
  6. mysql数据库入门教程(2):常见命令大全,语法规范
  7. 2015/Province_C_C++_C/9/打印大X
  8. python目标检测答案_你好,这里有一份2019年目标检测指南
  9. Android 开发中使用Intent传递数据的方法
  10. java释放锁_java – 一个线程在完成后释放锁吗?
  11. 容器编排技术 -- Kubernetes kubectl annotate 命令详解
  12. HOW2J 全套教程整理:Java、前端、数据库、中间件、第三方、项目、面试题
  13. 009Maven_建立私服——报错问题
  14. ajax的content-download时间过慢问题的解决与思考
  15. ssm的餐饮点餐系统源码
  16. 惠普T5325 惠普T5565 惠普T5400 瘦客机评测
  17. 微信公众号引流的平台有哪些?
  18. 联想电脑如何关闭/开启windows自动更新
  19. 组成计算机cpu的两大部件是,组成计算机的cpu的两大部分是什么
  20. css 解决因为书名号不满一行就换行情况

热门文章

  1. 阿里技术专家加多:Java异步编程实战之基于JDK中的Future实现异步编程
  2. 数据结构基础(19) --堆与堆排序
  3. Linux常用命令(第二版) --网络通信命令
  4. 记录hadoop3.2.2出现Could not find or load main class org.apache.hadoop.mapreduce.v2.app.MRAppMaster问题
  5. InnoDB文档笔记(三)—— Undo Log
  6. Red 编程语言 2019 开发计划:全速前进!
  7. 作业四 | 个人项目-小学四则运算 “软件”之初版
  8. MS SQL 语法大全
  9. 《PHP和MySQL Web开发从新手到高手(第5版)》一2章 MySQL简介2.1 数据库简介
  10. Atom飞行手册翻译: 3.4 文本处理包