转自: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. 在网页中显示器PDF文档

    <iframe src="></iframe> 在需要显示的页面中添加上面语句就可以.

  2. 【bzoj5108】[CodePlus2017]可做题 拆位+乱搞

    题目描述 给出一个长度为 $m$ 的序列 $a$ ,编号为 $a_1\sim a_m$,其中 $n$ 个位置的数已经确定,剩下的位置的数可以任意指定.现在令 $b$ 表示 $a$ 的前缀异或和,求 $ ...

  3. hdu 1068 Girls and Boys (二分匹配)

    Girls and Boys Time Limit: 20000/10000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) ...

  4. CentOS 安装MariaDB

    1.安装 #同时安装mariadb和mariadb-server [root@bigdata-senior01 yum.repos.d]# yum -y install mariadb mariadb ...

  5. 【倍增】LCM QUERY

    给一个序列,每次给一个长度l,问长度为l的区间中lcm最小的. 题解:因为ai<60,所以以某个点为左端点的区间的lcm只有最多60种的情况,而且相同的lcm区间的连续的. 所以就想到一个n*6 ...

  6. POJ2724:Purifying Machine——题解

    http://poj.org/problem?id=2724 描述迈克是奶酪工厂的老板.他有2^N个奶酪,每个奶酪都有一个00 ... 0到11 ... 1的二进制数.为了防止他的奶酪免受病毒侵袭,他 ...

  7. BZOJ1070:[SCOI2007]修车——题解

    http://www.lydsy.com/JudgeOnline/problem.php?id=1070 https://www.luogu.org/problemnew/show/P2053#sub ...

  8. BZOJ4245 [ONTAK2015]OR-XOR 【贪心】

    题目链接 BZOJ4245 题解 套路① 位运算当然要分位讨论,高位优先 考虑在\(or\)下,如果该位为\(0\),则每一位都为\(0\) 套路② 我们选m段异或和,转化为\(m\)个前缀和的点,且 ...

  9. ACE前摄器Proactor模式

    转载于:http://www.cnblogs.com/TianFang/archive/2006/12/31/608952.html 当 OS 平台支持异步操作时,一种高效而方便的实现高性能 Web ...

  10. Naming Company CodeForces - 794C

    Oleg the client and Igor the analyst are good friends. However, sometimes they argue over little thi ...