转自: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

  1. struct memblock {
  2. bool bottom_up;  /* is bottom up direction? */
  3. phys_addr_t current_limit;
  4. struct memblock_type memory;
  5. struct memblock_type reserved;
  6. };

bootom_up 设置为true时, 允许内存分配使用bottom-up模式

current_limit memblock分配内存时的上限

memory 描述了当前内存区包含的内存区数目, 总大小, 以及每个内存region

reserved 描述了当前内存块已经分配的内存区数目, 总大小,以及每个内存region. 在reserved中描述的地址范围, 表示不可以再被memblock分配.

memory用来描述memblock全部内存region(不区分分配和未分配), reserved用来描述memblock中已经分配的内存region

  1. struct memblock_type {
  2. unsigned long cnt;  /* number of regions */
  3. unsigned long max;  /* size of the allocated array */
  4. phys_addr_t total_size; /* size of all regions */
  5. struct memblock_region *regions;
  6. };

cnt         regions数目

max       最大regions数目

total_size    regions总尺寸

regions        regions array

  1. struct memblock_region {
  2. phys_addr_t base;
  3. phys_addr_t size;
  4. unsigned long flags;
  5. #ifdef CONFIG_HAVE_MEMBLOCK_NODE_MAP
  6. int nid;
  7. #endif
  8. };

base      region 基地址

size        region size

flags      region

memblock initialization

  1. static struct memblock_region memblock_memory_init_regions[INIT_MEMBLOCK_REGIONS] __initdata_memblock;
  2. static struct memblock_region memblock_reserved_init_regions[INIT_MEMBLOCK_REGIONS] __initdata_memblock;
  3. struct memblock memblock __initdata_memblock = {
  4. .memory.regions     = memblock_memory_init_regions,
  5. .memory.cnt     = 1,    /* empty dummy entry */
  6. .memory.max     = INIT_MEMBLOCK_REGIONS,
  7. .reserved.regions   = memblock_reserved_init_regions,
  8. .reserved.cnt       = 1,    /* empty dummy entry */
  9. .reserved.max       = INIT_MEMBLOCK_REGIONS,
  10. .bottom_up      = false,
  11. .current_limit      = MEMBLOCK_ALLOC_ANYWHERE,
  12. };

.memory.regions 和reserved.regions固定数组, 最多支持128个regions

memory.mx和reserved.max的最大值也为128

current_limit 也被定义为最大可能物理地址

memblock API

在include/linux/memblock.h中定义了memblock的API

  1. int __init_memblock memblock_reserve(phys_addr_t base, phys_addr_t size)
  2. {
  3. return memblock_reserve_region(base, size, MAX_NUMNODES, 0);
  4. }

memblock_reserve API主要是为系统启动阶段为kernel(text, data and initrd), swapper_pg_dir, reserved-memory, memreserve等预留内存.

  1. int __init_memblock memblock_remove(phys_addr_t base, phys_addr_t size)
  2. {
  3. return __memblock_remove(&memblock.memory, base, size);
  4. }

系统中有两处会调用memblock_remove:

1. early_init_dt_reserve_memory_arch中, 如果不希望这段内存被映射, 那么就调用memblock_remove, 把这段内存从memblock.memory中移除

2. arm_memblock_steal中, 调用memblock_remove从memblock.memory中偷一段内存空间, memblock.memory的regions中将不再包含这段内存空间.

  1. int __init_memblock memblock_add(phys_addr_t base, phys_addr_t size)
  2. {
  3. return memblock_add_region(&memblock.memory, base, size,
  4. MAX_NUMNODES, 0);
  5. }

在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

  1. int __init_memblock memblock_reserve(phys_addr_t base, phys_addr_t size)
  2. {
  3. return memblock_reserve_region(base, size, MAX_NUMNODES, 0);
  4. }
  5. static int __init_memblock memblock_reserve_region(phys_addr_t base,
  6. phys_addr_t size,
  7. int nid,
  8. unsigned long flags)
  9. {
  10. struct memblock_type *_rgn = &memblock.reserved;
  11. return memblock_add_region(_rgn, base, size, nid, flags);
  12. }

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

  1. phys_addr_t __init memblock_alloc(phys_addr_t size, phys_addr_t align)
  2. {
  3. return memblock_alloc_base(size, align, MEMBLOCK_ALLOC_ACCESSIBLE);
  4. }
  5. phys_addr_t __init memblock_alloc_try_nid(phys_addr_t size, phys_addr_t align, int nid)
  6. {
  7. phys_addr_t res = memblock_alloc_nid(size, align, nid);
  8. if (res)
  9. return res;
  10. return memblock_alloc_base(size, align, MEMBLOCK_ALLOC_ACCESSIBLE);
  11. }

memblock_alloc->memblock_alloc_base->memblock_alloc_base_nid

  1. static phys_addr_t __init memblock_alloc_base_nid(phys_addr_t size,
  2. phys_addr_t align, phys_addr_t max_addr,
  3. int nid)
  4. {
  5. phys_addr_t found;
  6. if (!align)
  7. align = SMP_CACHE_BYTES;
  8. found = memblock_find_in_range_node(size, align, 0, max_addr, nid);
  9. if (found && !memblock_reserve(found, size))
  10. return found;
  11. return 0;
  12. }

1. memblock_find_in_range_node查找符合条件的物理地址, 查找过程会涉及到查看memblock.reserve

2. 如果找到了这个物理地址, 调用memblock_reserve进行真正的分配(就是在memblock.reserve中添加region)

memblock_free

释放参数指定的内存区间

  1. int __init_memblock memblock_free(phys_addr_t base, phys_addr_t size)
  2. {
  3. memblock_dbg("   memblock_free: [%#016llx-%#016llx] %pF\n",
  4. (unsigned long long)base,
  5. (unsigned long long)base + size - 1,
  6. (void *)_RET_IP_);
  7. return __memblock_remove(&memblock.reserved, base, size);
  8. }

逻辑很简单, 从memblock.reserved中删除[base, base+size]指定范围的region. 这样下次调用memblock_alloc时再检查reserved type时, 这段区域可以再次使用了

内存分配器memblock【转】的更多相关文章

  1. 内核早期内存分配器:memblock

    内核早期内存分配器:memblockLinux内核使用伙伴系统管理内存,那么在伙伴系统工作前,如何管理内存?答案是memblock.memblock在系统启动阶段进行简单的内存管理,记录物理内存的使用 ...

  2. 14.6.4 Configuring the Memory Allocator for InnoDB 配置InnoDB 内存分配器

    14.6.4 Configuring the Memory Allocator for InnoDB 配置InnoDB 内存分配器 当InnoDB 被开发时,内存分配提供了操作系统和 run-time ...

  3. Nah Lock: 一个无锁的内存分配器

    概述 我实现了两个完全无锁的内存分配器:_nalloc 和 nalloc.  我用benchmark工具对它们进行了一组综合性测试,并比较了它们的指标值. 与libc(glibc malloc)相比, ...

  4. [转]STL的内存分配器

    题记:内存管理一直是C/C++程序的红灯区.关于内存管理的话题,大致有两类侧重点,一类是内存的正确使用,例如C++中new和delete应该成对出现,用RAII技巧管理内存资源,auto_ptr等方面 ...

  5. linux内存管理--伙伴系统和内存分配器

    3.1页框的管理 所有的页框描述符都存放在mem_map数组中. 3.1.1page数据结构 struct page { page_flags_t flags; //标志 atomic_t _coun ...

  6. 14.4.4 Configuring the Memory Allocator for InnoDB InnoDB 配置内存分配器

    14.4.4 Configuring the Memory Allocator for InnoDB InnoDB 配置内存分配器 当InnoDB 被开发, 内分配齐 提供了与操作系统和运行库往往缺乏 ...

  7. CoreCLR源码探索(三) GC内存分配器的内部实现

    在前一篇中我讲解了new是怎么工作的, 但是却一笔跳过了内存分配相关的部分. 在这一篇中我将详细讲解GC内存分配器的内部实现. 在看这一篇之前请必须先看完微软BOTR文档中的"Garbage ...

  8. FDG内存分配器笔记

    FDG: 大规模并行系统中的动态内存分配器由于需要全局同步(记账) ,导致性能急剧下降. 代码解析 1.superblock 类中包含两个变量,两个函数.默认superblock大小为2048 ite ...

  9. [内存管理]连续内存分配器(CMA)概述

    作者:Younger Liu, 本作品采用知识共享署名-非商业性使用-相同方式共享 3.0 未本地化版本许可协议进行许可. 原文地址:http://lwn.net/Articles/396657/ 1 ...

随机推荐

  1. 第50天:scrollTo小火箭返回顶部

    scrollTo(x,y)//可把内容滚动到指定的坐标scrollTo(xpos,ypos)//x,y值必需 1.固定导航栏 <!DOCTYPE html> <html lang=& ...

  2. WPF一个对象显示多个属性

    一个对象显示多个属性使用模板的方法: 如图: <dataTemplate x:key="MyDataTemplate">

  3. SP263 PERIOD - Period

    题目描述 For each prefix of a given string S with N characters (each character has an ASCII code between ...

  4. Python爬取B站视频信息

    该文内容已失效,现已实现scrapy+scrapy-splash来爬取该网站视频及用户信息,由于B站的反爬封IP,以及网上的免费代理IP绝大部分失效,无法实现一个可靠的IP代理池,免费代理网站又是各种 ...

  5. [洛谷P2742]【模板】二维凸包([USACO5.1]圈奶牛Fencing the Cows)

    题目大意:求一个点集凸包边长 题解:求凸包,直接求 卡点:发现在较后面数位上有较小的误差,还以为是浮点数误差,最后发现是构造函数写成了$int$类型 C++ Code: #include <al ...

  6. BZOJ1085:[SCOI2005]骑士精神——题解+IDA*粗略讲解

    http://www.lydsy.com/JudgeOnline/problem.php?id=1085 Description 在一个5×5的棋盘上有12个白色的骑士和12个黑色的骑士, 且有一个空 ...

  7. 信息工程学院技能大赛 计算机程序设计(Java)大赛试题

    前期准备与后期上传工作: (1)必须先建立项目和包,项目名为"JavaContest",包结构为:"contest.c+序号+姓名",其中序号为两位为本人大赛报 ...

  8. bzoj4390: [Usaco2015 dec]Max Flow(LCA+树上差分)

    题目大意:给出一棵树,n(n<=5w)个节点,k(k<=10w)次修改,每次给定s和t,把s到t的路径上的点权+1,问k次操作后最大点权. 对于每次修改,给s和t的点权+1,给lca(s, ...

  9. 差点AFO

    差点就AFO了,小伙伴们一定注意护眼啊. 眼睛总算是活过来了. 还有一个月联赛,加油

  10. 【线段树】【P3372】模板-线段树

    百度百科 Definition&Solution 线段树是一种log级别的树形结构,可以处理区间修改以及区间查询问题.期望情况下,复杂度为O(nlogn). 核心思想见百度百科,线段树即将每个 ...