转自: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. Delphi开发的一些技巧

    [Delphi]Delphi开发的一些技巧 一.提高查询效率先进行准备查询操作: CustomerQuery.Close; if not (CustomerQuery.Prepared) then - ...

  2. WPF绑定到父元素的属性的方法

    应用:绑定到父元素的属性上的方法,看图.

  3. CentOS 挂载(U盘NTFS格式,新硬盘,增加交换分区,扩展根分区等)

    1.挂载fat或者fat32分区的U盘 如果是用VM安装的linux,在vm里挂载U盘有两个前提: 第一,主机里的service要启动: 第二,U盘是连接到虚拟机,而不是主机,需要确认这点: 2.使用 ...

  4. UVA.725 Division (暴力)

    UVA.725 Division (暴力) 题意分析 找出abcdefghij分别是0-9(不得有重复),使得式子abcde/fghij = n. 如果分别枚举每个数字,就会有10^10,肯定爆炸,由 ...

  5. bzoj2326:[HNOI2011]数学作业(分段矩阵乘法)

    题目大意:输入n(n<=10^18)和m,将1~n的整数连起来模m输出,比如n=13则输出12345678910111213模m的数. 设f[i]为1~i整数连起来模m的数,i的位数为k,则有f ...

  6. ContestHunter暑假欢乐赛 SRM 09(TJM大傻逼选手再创佳绩)

    T1 f[i]为前i页最少被撕几页,用二分转移就行了,答案为ans=min(f[i]+(n-i)); 不知道为什么写挂了嗯 二分的l初始应该是0 T2 数位DP f[i][1/0][1/0][1/0] ...

  7. python获取当前工作目录

    py文件所在位置/test/pj/hello.py 用户所在位置:/ 用户执行命令python /test/pj/hello.py 1. os.getcwd() 返回的是执行命令的位置 / 2.sys ...

  8. JavaScript身份证号码有效性验证

    最近需要对身份证合法性进行验证,实名验证是不指望了,不过原来的验证规则太过简单,只是简单的验证了身份证长度,现在业务需要加强下身份证验证规则,网上找到了不少资料,不过都不合偶的心意,无奈只好直接写一个 ...

  9. 解决requests获取源代码时中文乱码问题

    用requests获取源代码时,如果是中文网页,就可能会出现乱码,下面我以中关村的网站为例: import requests url = 'http://desk.zol.com.cn/meinv/' ...

  10. CodeVS 1017 DP

    1017 乘积最大 2000年NOIP全国联赛普及组NOIP全国联赛提高组  时间限制: 1 s  空间限制: 128000 KB  题目等级 : 黄金 Gold 题解  查看运行结果     题目描 ...