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

而一个全局的pfn 要找到相对应的页 则首先需要找到该pfn对应的mem_section ,然后在找到在section_mem_map数组内的index,即全局的pfn主要有两部分组成:section numer 和 在section_mem_map内的数组偏移,其中PFN_SECTION_SHIFT宏为全局pfn section所占的偏移位置:

#define PA_SECTION_SHIFT (SECTION_SIZE_BITS)
#define PFN_SECTION_SHIFT   (SECTION_SIZE_BITS - PAGE_SHIFT)

同样struct page *转成成pfn 也是相反的过程。

vmemmap

由于struct page和pfn的转换在内核中太过于频繁,上述传统的方式转换太过于蛮烦,就借鉴了DISCONTIGMEM 内存模型中的mem_map数组为虚拟数组的变量,引入了虚拟memmap数组概念vmemmap,如下所图:

将所有的mem_section中page 都抽象到一个虚拟数组vmemmap,这样在进行struct page *和pfn转换时,之间使用vmemmap数组即可,如下转换(位于include\asm-generic\memory_model.h)

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

上述转换效率为最高。

vmemmap功能开启

vmemmap功能开启,需要配置CONFIG_SPARSEMEMCONFIG_SPARSEMEM_VMEMMAP 功能,如下图内核配置:

开启vmemmap 需要配置“Sparse Memory virtual memap”,默认情况下vmemap功能为开启的,这样能够提高转换效率。

vmemmap 虚拟空间

vmemap 数组本质上为从内核的虚拟空间中预留一段空间,专门给vmemmap数组使用,在X86 64位平台下,查看\Documentation\x86\x86_64\mm.rst文档中可以看到在一个四级 page table转换表中整个虚拟空间的一个分布状况:

vmemp的 虚拟地址 分布位于0xffffea0000000000~0xffffeaffffffffff地址空间段中。

vmemmap分配物理内存

虽然内核空间已经位vmemmap地址分配了虚拟地址空间,实际使用过程中还是要分配物理内存,vmemmap数组之间分配物理内存过程如下:

sparse_init_nid

《linux内核那些事之Sparse内存模型初始化》中已经说明了spare内存的初始化过程,其中sparse_init_nid()函数为初始化一个node 节点上的mem_section结果初始化,其中包括vmemmap数组的初始化:


/** Initialize sparse on a specific node. The node spans [pnum_begin, pnum_end)* And number of present sections in this node is map_count.*/
static void __init sparse_init_nid(int nid, unsigned long pnum_begin,unsigned long pnum_end,unsigned long map_count)
{struct mem_section_usage *usage;unsigned long pnum;struct page *map;usage = sparse_early_usemaps_alloc_pgdat_section(NODE_DATA(nid),mem_section_usage_size() * map_count);if (!usage) {pr_err("%s: node[%d] usemap allocation failed", __func__, nid);goto failed;}sparse_buffer_init(map_count * section_map_size(), nid);for_each_present_section_nr(pnum_begin, pnum) {unsigned long pfn = section_nr_to_pfn(pnum);if (pnum >= pnum_end)break;map = __populate_section_memmap(pfn, PAGES_PER_SECTION,nid, NULL);if (!map) {pr_err("%s: node[%d] memory map backing failed. Some memory will not be available.",__func__, nid);pnum_begin = pnum;goto failed;}check_usemap_section_nr(nid, usage);sparse_init_one_section(__nr_to_section(pnum), pnum, map, usage,SECTION_IS_EARLY);usage = (void *) usage + mem_section_usage_size();}sparse_buffer_fini();return;
failed:/* We failed to allocate, mark all the following pnums as not present */for_each_present_section_nr(pnum_begin, pnum) {struct mem_section *ms;if (pnum >= pnum_end)break;ms = __nr_to_section(pnum);ms->section_mem_map = 0;}
}

__populate_section_memmap()函数为该section中的物理内存申请相对应数量的struct page *数组,如果开启了vmemmap数组功能,则申请的struct page*位于vmemmap数组中。

__populate_section_memmap

__populate_section_memmap ()函数实现位于mm\sparse-vmemmap.c文件中:


struct page * __meminit __populate_section_memmap(unsigned long pfn,unsigned long nr_pages, int nid, struct vmem_altmap *altmap)
{unsigned long start;unsigned long end;/** The minimum granularity of memmap extensions is* PAGES_PER_SUBSECTION as allocations are tracked in the* 'subsection_map' bitmap of the section.*/end = ALIGN(pfn + nr_pages, PAGES_PER_SUBSECTION);pfn &= PAGE_SUBSECTION_MASK;nr_pages = end - pfn;start = (unsigned long) pfn_to_page(pfn);end = start + nr_pages * sizeof(struct page);if (vmemmap_populate(start, end, nid, altmap))return NULL;return pfn_to_page(pfn);
}

函数参数:

  • pfn : section nr转换后的全局pfn。
  • nr_pages: 为该section 的实际物理页数。
  • nid: node id.
  • altmap: 此时为NULL

源码大概处理:

  • 首先计算出 实际end结束pfn(注意此时end是从0开始),注意需要和PAGES_PER_SUBSECTION对齐。
  • 计算出实际所需要的page 数目nr_pages.
  • 使用pfn_to_page(pfn)计算出在vmemmap虚拟数组中开始start地址即内核虚拟地址指向的是struct *page对应的虚拟地址
  • 计算出end 内核虚拟实际结束地址,上述两个步骤是精华所在,通过

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

  • 计算出在虚拟地址对应的start和end地址。
  • 调用vmemmap_populate,为所vmemmap所需要的虚拟地址申请对应的物理内存,并建立相应的page table
  • 最后通过pfn_to_page返回对应的mem_section对应的起始struct page *地址

vmemmap_populate

为相应的虚拟vmemmap数组申请相对应的实际物理内存,x86 64位环境函数实现位于arch\x86\mm\init_64.c文件中源码如下:

int __meminit vmemmap_populate(unsigned long start, unsigned long end, int node,struct vmem_altmap *altmap)
{int err;if (end - start < PAGES_PER_SECTION * sizeof(struct page))err = vmemmap_populate_basepages(start, end, node);else if (boot_cpu_has(X86_FEATURE_PSE))err = vmemmap_populate_hugepages(start, end, node, altmap);else if (altmap) {pr_err_once("%s: no cpu support for altmap allocations\n",__func__);err = -ENOMEM;} elseerr = vmemmap_populate_basepages(start, end, node);if (!err)sync_global_pgds(start, end - 1);return err;
}

首先根据不同的地址范围检查,进入不同分支,默认是进入vmemmap_populate_basepages()函数

vmemmap_populate_basepages

spare 模型开启vmemmap功能,该函数实现位于mm\sparse-vmemmap.c 文件中:

int __meminit vmemmap_populate_basepages(unsigned long start,unsigned long end, int node)
{unsigned long addr = start;pgd_t *pgd;p4d_t *p4d;pud_t *pud;pmd_t *pmd;pte_t *pte;for (; addr < end; addr += PAGE_SIZE) {pgd = vmemmap_pgd_populate(addr, node);if (!pgd)return -ENOMEM;p4d = vmemmap_p4d_populate(pgd, addr, node);if (!p4d)return -ENOMEM;pud = vmemmap_pud_populate(p4d, addr, node);if (!pud)return -ENOMEM;pmd = vmemmap_pmd_populate(pud, addr, node);if (!pmd)return -ENOMEM;pte = vmemmap_pte_populate(pmd, addr, node);if (!pte)return -ENOMEM;vmemmap_verify(pte, node, addr, addr + PAGE_SIZE);}return 0;
}

由于此时 buddy 和zone内存管理还没有建立,所以之间刷新page table 建立对应的物理内存映射:

由于MMU一般是按照PAGE_SIZE(4k)建立对应的映射管理,所以按照实际需要申请的物理页数按照每页建立相对应的page tabe

按照分级划分页,首先建立pgd表映射vmemmap_pgd_populate

建立p4d表映射vmemmap_p4d_populate

建立pud表映射vmemmap_pud_populate

建立pmd表映射vmemmap_pmd_populate

建立pte表映射vmemmap_pte_populate

最后对vmemmap进行检查vmemmap_verify

vmemmap_alloc_block

在上述建立pgd、p4d、pud、pmd和pte表都需要申请相应的物理内存,最后都是调用的vmemmap_alloc_block接口,申请内存:


void * __meminit vmemmap_alloc_block(unsigned long size, int node)
{/* If the main allocator is up use that, fallback to bootmem. */if (slab_is_available()) {gfp_t gfp_mask = GFP_KERNEL|__GFP_RETRY_MAYFAIL|__GFP_NOWARN;int order = get_order(size);static bool warned;struct page *page;page = alloc_pages_node(node, gfp_mask, order);if (page)return page_address(page);if (!warned) {warn_alloc(gfp_mask & ~__GFP_NOWARN, NULL,"vmemmap alloc failure: order:%u", order);warned = true;}return NULL;} elsereturn __earlyonly_bootmem_alloc(node, size, size,__pa(MAX_DMA_ADDRESS));
}

vmemmap_alloc_block()函数提供了两种物理内存申请方式:

  • 当zone,buddy以及slab模块都建立并初始化完成则直接调用alloc_pages_node()函数直接从buddy中获取物理内存
  • 而此时在位vmemmap数组申请物理内存时,并没有建立zone及buddy模块,需要直接从mem block中申请内存,调用__earlyonly_bootmem_alloc()接口意味着从bootmem阶段中申请物理内存。

以上是vmemmap 在初始化中虚拟内核和相对应的物理内存建立。

page 和pfn相互转换

vmemmap数组建立完成之后,page和pfn之间相互转换就变得非常简单:

/* memmap is virtually contiguous.  */
#define __pfn_to_page(pfn)  (vmemmap + (pfn))
#define __page_to_pfn(page) (unsigned long)((page) - vmemmap)

调用关系

加入vmemmap调用关系更新:

linux内核那些事之Sparse vmemmap相关推荐

  1. linux内核那些事之Sparse内存模型初始化

    由于现在运行的设备中大都采用sparse内存模型,而<understanding the linux virtual memory manager>书中主要以2.4和2.6内核源码基础上进 ...

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

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

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

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

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

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

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

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

  6. linux内核那些事之buddy

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

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

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

  8. linux内核那些事之物理内存模型之SPARSE(3)

    在内核FLAT和DISCONTIGMEM管理模型中,其实一直都存在两个问题 管理物理内存的数据结构本身占用内存较多,不使用于较大内存情况 无法解决空洞问题,不管是FLAT还是SPARCE模型都无法解决 ...

  9. linux内核那些事之struct page

    struct page page(页)是linux内核管理物理内存的最小单位,内核将整个物理内存按照页对齐方式划分成千上万个页进行管理,内核为了管理这些页将每个页抽象成struct page结构管理每 ...

最新文章

  1. Linux那些事儿 之 戏说USB(大结局)还是那个match
  2. 动画产业基础学习教程 Rad How to Class – Animation Industry Fundamentals
  3. 商务之路有多远,贿赂就有多远吗? 续一
  4. 云serverlinux又一次挂载指定文件夹(非扩充)
  5. Git学习教程(一):git简介
  6. grub 引导 多linux系统,GRUB 多系统引导
  7. QT实现太阳系系统八大行星
  8. 怎么查看python是否安装好了pyinstaller_Python PyInstaller安装和使用教程(详解版)...
  9. Spring @Qualifier 注释
  10. 实战系列-IDEA中Spring MVC实现接口功能
  11. 【图论】Prim算法求最小生成树详解
  12. 软件测试-微信红包测试点
  13. 《刺杀骑士团长》读后感
  14. gdb 查看是否 栈溢出_64位Linux栈溢出教程
  15. 图像增强—彩色增强技术
  16. 产品可靠性测试 - 学习笔记(1)
  17. 单片机小白学步系列(八) 用面包板搭建实验电路
  18. 图片太大了怎么改小KB?教你2招无损图片压缩
  19. js+java 实现图片在线预览功能
  20. TCP协议中常用的FTP/HTTP/HTTPS/SSH等常见端口号

热门文章

  1. JEEWX 使用ngrok将本地Web服务映射到外网
  2. jsp中forward与sendRedirect的区别
  3. c++ 走向高级之日积月累
  4. 48.检测对象是否为空
  5. 用Markdown写博客快速入门
  6. Android应用程序创建桌面快捷方式
  7. 再谈Jquery Ajax方法传递到action
  8. OJ1114: 逆序(数组)(C语言)
  9. php mysql增删查改 主码不能修改_PHP 数据库练习
  10. 信息学奥赛一本通 1405:质数的和与积 | OpenJudge NOI 2.1 7827:质数的和与积 | 小学奥数 7827