内存分配器memblock【转】
转自:http://blog.csdn.net/kickxxx/article/details/54710243
版权声明:本文为博主原创文章,未经博主允许不得转载。
背景
在Linux内核开发过程中, 多少都会存在一个patch, 引入了远超预期的麻烦. 内核2.6.34开发过程中, 这个奖项非CONFIG_NO_BOOTMEM莫属
bootmem本身是个简单的,低级的内存分配器. 在引导程序的初期用来分配内存. 有人可能会想, 没有必要再增加一个内存分配器, 但是由于内存管理代码在被调用前需要很多内核功能都准备好, 要想在启动初期使用内存管理代码会大大增加内存管理的复杂性. 在x86架构上, 会首先使用early_res机制接替BIOS e820的工作, 然后再交给架构独立的bootmem分配器, 最后才是全功能的buddy allocator
YingHai LU认为可以把bootmem从这个过程中去掉, 简化这个过程. 结果就是,
增加了一堆patch来扩展early_res机制, 把本该交给bootmem做的事情都做了, 然后直接到buddy分配器.
这些修改被合入了2.6.34, 老的基于bootmem的代码仍然保留. CONFIG_NO_BOOTMEM用来控制使用哪个分配器,
缺省情况下并不使用bootmem
一切本该非常美好, 但是随着kernel rc版本的发布, 新分配器测试陆续爆出很多问题, Linus发了一封邮件要求revert掉整个patch. 虽然简化代码这个注意看起来不错, 但是rc3仍然导致系统死机, 以及大量使用ifdef, 以及缺省打开CONFIG_NO_BOOTMEM, 导致了社区的怨气.
正常情况下, 新功能缺省情况下是不使能的. 新kernel应该尽最大可能和之前的kernel保持一致. 而CONFIG_NO_BOOTMEM缺省打开导致了很大的改变和问题.
Yinghai在2.6.35基础上又提交了一组patch, 使用logical memory block分配器替代early_res代码, 这组patch看起来比删除bootmem引入了更大的风险
--https://lwn.NET/Articles/382559/
在Yinghai删除bootmem patch的review过程中, 一些reviewers质疑为什么x86不使用logical
memory block(LMB)分配器替换early-res的代码. 当X86使用类似brk()形式的early-res时
Microblaze, PowerPC, SuperH和SPARC架构已经使用LMB进行系统启动初期的内存分配,
所以LMB可以看做是一个generic的解决方案. 使用通用代码的好处是显而易见的: 更多的人review代码, 总体维护代价更带.
所以使用LMB明显更合理.
因此Yinghai在2.6.35上又提交了一组patch来简化启动分配器代码
Data structure
- struct memblock {
- bool bottom_up; /* is bottom up direction? */
- phys_addr_t current_limit;
- struct memblock_type memory;
- struct memblock_type reserved;
- };
bootom_up 设置为true时, 允许内存分配使用bottom-up模式
current_limit memblock分配内存时的上限
memory 描述了当前内存区包含的内存区数目, 总大小, 以及每个内存region
reserved 描述了当前内存块已经分配的内存区数目, 总大小,以及每个内存region. 在reserved中描述的地址范围, 表示不可以再被memblock分配.
memory用来描述memblock全部内存region(不区分分配和未分配), reserved用来描述memblock中已经分配的内存region
- struct memblock_type {
- unsigned long cnt; /* number of regions */
- unsigned long max; /* size of the allocated array */
- phys_addr_t total_size; /* size of all regions */
- struct memblock_region *regions;
- };
cnt regions数目
max 最大regions数目
total_size regions总尺寸
regions regions array
- struct memblock_region {
- phys_addr_t base;
- phys_addr_t size;
- unsigned long flags;
- #ifdef CONFIG_HAVE_MEMBLOCK_NODE_MAP
- int nid;
- #endif
- };
base region 基地址
size region size
flags region
memblock initialization
- static struct memblock_region memblock_memory_init_regions[INIT_MEMBLOCK_REGIONS] __initdata_memblock;
- static struct memblock_region memblock_reserved_init_regions[INIT_MEMBLOCK_REGIONS] __initdata_memblock;
- struct memblock memblock __initdata_memblock = {
- .memory.regions = memblock_memory_init_regions,
- .memory.cnt = 1, /* empty dummy entry */
- .memory.max = INIT_MEMBLOCK_REGIONS,
- .reserved.regions = memblock_reserved_init_regions,
- .reserved.cnt = 1, /* empty dummy entry */
- .reserved.max = INIT_MEMBLOCK_REGIONS,
- .bottom_up = false,
- .current_limit = MEMBLOCK_ALLOC_ANYWHERE,
- };
.memory.regions 和reserved.regions固定数组, 最多支持128个regions
memory.mx和reserved.max的最大值也为128
current_limit 也被定义为最大可能物理地址
memblock API
在include/linux/memblock.h中定义了memblock的API
- int __init_memblock memblock_reserve(phys_addr_t base, phys_addr_t size)
- {
- return memblock_reserve_region(base, size, MAX_NUMNODES, 0);
- }
memblock_reserve API主要是为系统启动阶段为kernel(text, data and initrd), swapper_pg_dir, reserved-memory, memreserve等预留内存.
- int __init_memblock memblock_remove(phys_addr_t base, phys_addr_t size)
- {
- return __memblock_remove(&memblock.memory, base, size);
- }
系统中有两处会调用memblock_remove:
1. early_init_dt_reserve_memory_arch中, 如果不希望这段内存被映射, 那么就调用memblock_remove, 把这段内存从memblock.memory中移除
2. arm_memblock_steal中, 调用memblock_remove从memblock.memory中偷一段内存空间, memblock.memory的regions中将不再包含这段内存空间.
- int __init_memblock memblock_add(phys_addr_t base, phys_addr_t size)
- {
- return memblock_add_region(&memblock.memory, base, size,
- MAX_NUMNODES, 0);
- }
在memblock的memory type中增加一个region
base 是新增region的基地址
size 是新增region的尺寸
对于arm, 仅一处会调用memblock_add: arm_memblock_init 中根据meminfo向memblock.memory中增加region
memory和reserved region
memblock有两个memblock_type成员: memory和reserved
memblock.memory 描述memblock所有内存区(已分配的+未分配的)
memblock.reserved 描述已经分配的内存区, 所有分配和释放操作都是通过修改reserved来实现的. 分配操作在memblock.reserved上增加region, 释放操作则memblock.reserved上释放region.
memblock_reserved
- int __init_memblock memblock_reserve(phys_addr_t base, phys_addr_t size)
- {
- return memblock_reserve_region(base, size, MAX_NUMNODES, 0);
- }
- static int __init_memblock memblock_reserve_region(phys_addr_t base,
- phys_addr_t size,
- int nid,
- unsigned long flags)
- {
- struct memblock_type *_rgn = &memblock.reserved;
- return memblock_add_region(_rgn, base, size, nid, flags);
- }
memblock_reserve -> memblock_reserve_region -> memblock_add_region
调用memblock_add_region时第一个参数为 memblock.reserved, 也就是说在memblock.reserved增加一个region
memblock_add_region流程如下:
1. 如果type->regions[0].size==0, 表示regions数组为空, 添加region[0], 设置region[0]相关成员,即可返回. 此时该memblock_type有一个region了
2. 计算要插入的region数目, [base, base + size]可能会跨越多个已存在的region, 因此数目可能不为1.
3. 由于要插入新region, 所以需要先扩展regions array.
4. 插入这些regions
5. 执行region merge操作
我们可以看到memblock_reserve()操作压根就没考虑memblock.memory, 因为memblock_reserve()只是告诉memblock, 这部分内存被保留了, 不要再参与memblock分配操作
memblock_alloc
- phys_addr_t __init memblock_alloc(phys_addr_t size, phys_addr_t align)
- {
- return memblock_alloc_base(size, align, MEMBLOCK_ALLOC_ACCESSIBLE);
- }
- phys_addr_t __init memblock_alloc_try_nid(phys_addr_t size, phys_addr_t align, int nid)
- {
- phys_addr_t res = memblock_alloc_nid(size, align, nid);
- if (res)
- return res;
- return memblock_alloc_base(size, align, MEMBLOCK_ALLOC_ACCESSIBLE);
- }
memblock_alloc->memblock_alloc_base->memblock_alloc_base_nid
- static phys_addr_t __init memblock_alloc_base_nid(phys_addr_t size,
- phys_addr_t align, phys_addr_t max_addr,
- int nid)
- {
- phys_addr_t found;
- if (!align)
- align = SMP_CACHE_BYTES;
- found = memblock_find_in_range_node(size, align, 0, max_addr, nid);
- if (found && !memblock_reserve(found, size))
- return found;
- return 0;
- }
1. memblock_find_in_range_node查找符合条件的物理地址, 查找过程会涉及到查看memblock.reserve
2. 如果找到了这个物理地址, 调用memblock_reserve进行真正的分配(就是在memblock.reserve中添加region)
memblock_free
释放参数指定的内存区间
- int __init_memblock memblock_free(phys_addr_t base, phys_addr_t size)
- {
- memblock_dbg(" memblock_free: [%#016llx-%#016llx] %pF\n",
- (unsigned long long)base,
- (unsigned long long)base + size - 1,
- (void *)_RET_IP_);
- return __memblock_remove(&memblock.reserved, base, size);
- }
逻辑很简单, 从memblock.reserved中删除[base, base+size]指定范围的region. 这样下次调用memblock_alloc时再检查reserved type时, 这段区域可以再次使用了
内存分配器memblock【转】的更多相关文章
- 内核早期内存分配器:memblock
内核早期内存分配器:memblockLinux内核使用伙伴系统管理内存,那么在伙伴系统工作前,如何管理内存?答案是memblock.memblock在系统启动阶段进行简单的内存管理,记录物理内存的使用 ...
- 14.6.4 Configuring the Memory Allocator for InnoDB 配置InnoDB 内存分配器
14.6.4 Configuring the Memory Allocator for InnoDB 配置InnoDB 内存分配器 当InnoDB 被开发时,内存分配提供了操作系统和 run-time ...
- Nah Lock: 一个无锁的内存分配器
概述 我实现了两个完全无锁的内存分配器:_nalloc 和 nalloc. 我用benchmark工具对它们进行了一组综合性测试,并比较了它们的指标值. 与libc(glibc malloc)相比, ...
- [转]STL的内存分配器
题记:内存管理一直是C/C++程序的红灯区.关于内存管理的话题,大致有两类侧重点,一类是内存的正确使用,例如C++中new和delete应该成对出现,用RAII技巧管理内存资源,auto_ptr等方面 ...
- linux内存管理--伙伴系统和内存分配器
3.1页框的管理 所有的页框描述符都存放在mem_map数组中. 3.1.1page数据结构 struct page { page_flags_t flags; //标志 atomic_t _coun ...
- 14.4.4 Configuring the Memory Allocator for InnoDB InnoDB 配置内存分配器
14.4.4 Configuring the Memory Allocator for InnoDB InnoDB 配置内存分配器 当InnoDB 被开发, 内分配齐 提供了与操作系统和运行库往往缺乏 ...
- CoreCLR源码探索(三) GC内存分配器的内部实现
在前一篇中我讲解了new是怎么工作的, 但是却一笔跳过了内存分配相关的部分. 在这一篇中我将详细讲解GC内存分配器的内部实现. 在看这一篇之前请必须先看完微软BOTR文档中的"Garbage ...
- FDG内存分配器笔记
FDG: 大规模并行系统中的动态内存分配器由于需要全局同步(记账) ,导致性能急剧下降. 代码解析 1.superblock 类中包含两个变量,两个函数.默认superblock大小为2048 ite ...
- [内存管理]连续内存分配器(CMA)概述
作者:Younger Liu, 本作品采用知识共享署名-非商业性使用-相同方式共享 3.0 未本地化版本许可协议进行许可. 原文地址:http://lwn.net/Articles/396657/ 1 ...
随机推荐
- 【bzoj4165】矩阵 堆+STL-map
题目描述 定义和谐矩阵为长不小于 Mina 且宽不小于 Minb 的矩阵,矩阵的权值为整个矩阵内所有数的和.给定一个长为 N,宽为 M 的矩阵 A,求它的所有和谐子矩阵中权值第 K 小的矩阵,并输出它 ...
- ictclas4j 分词工具包 安装流程
首先把 ictclasj解压缩,然后 1.把 Data文件夹整个拷贝到 Eclipse项目的文件夹下, 2.而 bin目录下的 org文件夹整个拷贝到你 Eclipse项目的 bin目录下,(将cla ...
- 具体数学二项式至生成函数章-----致敬Kunth
关于标题取得这么奇怪.因为在具体数学中.这两章是分开叙述的.并且分别叙述得淋漓尽致! 我只参悟其中关于生成函数的一小部分内容(暂时于我够用了.) 提二项式系数之前不得不提组合数.以往在高中用的是符号C ...
- 【刷题】BZOJ 4516 [Sdoi2016]生成魔咒
Description 魔咒串由许多魔咒字符组成,魔咒字符可以用数字表示.例如可以将魔咒字符 1.2 拼凑起来形成一个魔咒串 [1,2]. 一个魔咒串 S 的非空字串被称为魔咒串 S 的生成魔咒. 例 ...
- [JSOI2010]缓存交换 贪心 & 堆
~~~题面~~~ 题解: 首先我们要使得Miss的次数尽量少,也就是要尽量保证每个点在被访问的时候,这个点已经存在于Cache中. 那么我们可以得到一个结论: 如果Cache已满,那么我们就从Cach ...
- [HEOI2016/TJOI2016]序列 CDQ分治
---题面--- 题解: 首先我们观察一下,如果一个点对(j, i), 要符合题中要求要满足哪些条件? 首先我们设 j < i 那么有: j < i max[j] < v[i] v[ ...
- POJ2195:Going Home——题解
http://poj.org/problem?id=2195 题目大意: 有些人和房子,一个人只能进一个房子,人走到房子的路程即为代价. 求所有人走到房子后的最小代价. ——————————————— ...
- BZOJ1599 find the mincost route 【floyd】
题目链接 BZOJ1599 题解 最小环模板?周末了养生一下[逃] 解释一下原理 \(floyd\)算法每一轮求出以\([1,k]\)为中介点的最短路 我们对于一个环,考虑环上编号最大的点,在\(k\ ...
- struts2远程代码执行漏洞汇总整理
一.S2-001 1.漏洞原理 在默认配置下,如果用户所提交的表单出现验证错误,后端会对用户的输入进行解析处理,然后返回并显示处理结果. 举个例子,当你提交的登录表单为username=xishir& ...
- Sightseeing(dijlstar) 计算最短路和次短路的条数
Sightseeing Time Limit: 2000MS Memory Limit: 65536K Total Submissions: 10004 Accepted: 3523 Desc ...