详解linux引导内存分配器bootmem简介
1.概述
linux系统中使用伙伴系统对物理页面进行分配管理,但是伙伴分配系统需要内核完成初始化以及建立相关内核数据结构后才能够正常工作。因此,我们不难看出在内核初始化相关数据结构时需要另一种内存分配器。早期Linux没有较为完善的引导内存分配器,但是随着硬件的发展和日趋复杂,处理不同体系的内存分配代码也渐渐复杂起来,随之就需要引导内存分配器来初始化系统主要内存分配器的数据结构以确保其正常工作。在内核2.3.23版本中bootmem引导内存分配器补丁被加入,使用位图来表示页面使用状况。然后在内核2.3.48版本时,linux内核移植到IA64时正式使用bootmem作为引导内存分配器。随着时间的流逝,内存检测已经从简单地向BIOS询问扩展内存块的大小演变为处理复杂的表,块,库和群集。这时开始使用memblock作为引导内存分配器。在bootmem向memblock过渡时,出现nobootmem作为兼容层,提供与bootmem类似api。在内核版本4.17时,在linux所支持的24种架构中,只有5种仍在使用bootmem作为唯一的早期内存分配器,14中将memblock与nobootmem一起使用,其余同时使用memblock和bootmem作为引导内存分配器。今天主要介绍bootmem引导内存分配器。
2.内核数据结构
首先查看bootmem_data数据结构,表示每个节点物理内存以及其页面使用情况。
node_min_pfn和node_low_pfn:表示该节点内存物理页面范围:node_min_pfn为起始页面,node_low_pfn则为结束页面;
node_bootmem_map:指向位图,每位表示内存页面使用情况,当页面可以被使用时,所对应的位图设为0,相反则设为1;
last_end_off:表示上次所分配内存的物理地址相对bootmem起始页面偏移(以字节计算);
hint_idx:记录上次设置位图的索引;
list:加入bootmem_data全局链表bdata_list;除了全局链表外,还存在bootmem_node_data所指向的bootmem_data全局数组,索引为内存对应节点号;
【文章福利】小编推荐自己的Linux内核技术交流群:【891587639】整理了一些个人觉得比较好的学习书籍、视频资料共享在群文件里面,有需要的可以自行添加哦!!!(含视频教程、电子书、实战项目及代码)


3.相关函数
3.1 初始化bootmem_data
首先调用mminit_validate_memmodel_limits()函数检查指定范围物理页面是否有效,然后初始化bootmem_data结构,其中mapstart指向所分配位图页面。link_bootmem()函数是将bootmem_data加入bdata_list链表中。最后将所有位图设为1,即页面目前不可用。
该函数将bootmem_data加入链表,bdata_list中节点顺序是按照起始页面从小到大排列。
3.2 释放bootmem所保留的页面
3.3 保留bootmem中页面
该函数sidx和eidx表示锁保留页面的范围,如果flags设置BOOTMEM_EXCLUSIVE标志位则表示将指定范围内页面从bootmem中释放。
3.4 从bootmem中分配内存
该函数size表示所需要分配多少字节的内存,align表示分配内存按多少字节对齐,goal表示分配内存最小物理地址,limit表示分配内存最大物理地址。首先对参数进行检验,size不能为空,align必须为2的指数,同时分配内存不能超出范围。
获取bootmem所保留页面范围并重新计算所分配内存起始物理页面。
设置起始和结束索引,如果bootmem上次分配内存页面大于sidx则设置fallback。
首先获取bootmem位图在sidx和eidx范围数个为0的索引,然后在进行对齐获取起始索引并重新计算结束索引,验证是否超出bootmem所拥有页面范围,超出则退出循环分配失败。否则进入for循环在sidx和eidx遍历查看是否存在页面被bootmem保留,如果存在则以此索引为新sidx进行设置,重新遍历,相反则继续进行分配。根据bootmem上次分配内存的偏移设置所分配内存起始物理地址以及结束地址,最后设置这些页面的位图为1,即保留页面。
检查fallback是否为0,如果不为0则返回继续进行查找。
3.5 释放bootmem中页面
获取bootmem页面范围以及位图。
循环遍历位图释放bootmem未进行保留的页面,即相应位图设为0的页面。
注: 以上均为自己对linux内核4.15.1源码的分析。如果有不足之处,欢迎大家指出。
