linux内存三大分配器:引导内存分配器,伙伴分配器,slab分配器

一、引导内存分配器

1.引导内存分配器的作用
因为内核里面有很多内存结构体,不可能在静态编译阶段就静态初始化所有的这些内存结构体。另外,在系统启动过程中,系统启动后的物理内存分配器本身也需要初始化,如伙伴分配器,那么伙伴分配器如何获取内存来初始化自己呢 ?为了达到这个目标,我们先实现一个满足要求的但是可能效率不高的笨家伙,引导内存分配器。用它来负责系统初始化初期的内存管理, 最重要的, 用它来初始化我们内存的数据结构, 直到我们真正的内存管理器被初始化完成并能投入使用, 我们将旧的内存管理器丢掉。
2.引导内存分配器的原理
在Linux内核中使用struct bootmem_data来描述一个引导内存分配,其节点结构下的一个成员,也就是说每一个节点都有一个引导内存分配。
引导内存分配使用struct bootmem_data结构中的node_bootmem_map这个bitmap来呈现memory的状态,一个bit代表一个物理页框,也就是用struct page,如果一个bit为1,表示该page已经被分配了,如果bit是0,则表示该page未被分配。为了能够满足比一个page还小的内存块的分配,引导内存分配器会使用last_pos来记住上次分配所使用的PFN以及上次分配所使用的page内的偏移:last_offset,下次分配的时候结合last_pos和last_offset将细小的内存块分配尽量集中在相同的page中。
3引导内存分配器的缺点
尽管引导内存分配器不会造成严重的内存碎片,但是每次分配过程需要线性扫描搜索内存来满足当前的分配。因为是检查bitmap,所以代价比较昂贵,尤其是最先适配(first fit)算法倾向将小块内存放置在物理内存开头,但是这些内存区域在分配大块内存时,也需要扫描,所以该过程十分浪费。所以早期内存分配器在系统启动后就被弃用的原因。
4.bootmem和memblock的比较
但是bootmem也有很多问题. 最明显的就是外碎片的问题, 因此内核维护了memblock内存分配器, 同时用memblock实现了一份bootmem相同的兼容API, 即nobootmem, Memblock以前被定义为Logical Memory Block( 逻辑内存块),但根据Yinghai Lu的补丁, 它被重命名为memblock. 并最终替代bootmem成为初始化阶段的内存管理器。
bootmem是通过位图来管理,位图存在地地址段, 而memblock是在高地址管理内存, 维护两个链表, 即memory和reserved。
memory链表维护系统的内存信息(在初始化阶段通过bios获取的), 对于任何内存分配, 先去查找memory链表, 然后在reserve链表上记录(新增一个节点,或者合并)
bootmem和memblock都是就近查找可用的内存, bootmem是从低到高找, memblock是从高往低找。
在boot传递给kernel memory bank相关信息后,kernel这边会以memblcok的方式保存这些信息,当伙伴系统没有起来之前,在内核中也是要有一套机制来管理memory的申请和释放。linux内核可以通过宏定义选择nobootmem 或者bootmem 来在伙伴起来之前管理内存。这两种机制对提供的API是一致的,因此对用户是透明的
5.bootmem小分析
bootmem结构体位于文件include/linux/bootmem.h:

typedef struct bootmem_data {unsigned long node_min_pfn;//节点内存的起始物理页号unsigned long node_low_pfn;//节点内存的结束物理页号void *node_bootmem_map;//位图指针,每个物理页对应一位,如果物理页被分配则对应位置一。unsigned long last_end_off;//最后一次分配的页面内的偏移量(字节);如果为0,则使用的页面已满unsigned long hint_idx;//最后一次分配的物理页,下次优先考虑从这个物理页分配struct list_head list;//按内存地址排序链表头
} bootmem_data_t;

bootmem接口函数:
1)bootmem分配内存函数:alloc_bootmem
2)bootmem释放内存函数:free_bootmem

#define alloc_bootmem(x) \__alloc_bootmem(x, SMP_CACHE_BYTES, BOOTMEM_LOW_LIMIT)void __init free_bootmem(unsigned long physaddr, unsigned long size)
{unsigned long start, end;kmemleak_free_part_phys(physaddr, size);//释放映射的内存start = PFN_UP(physaddr);//查找到起始位置的物理页end = PFN_DOWN(physaddr + size);//查找到结束为止的物理页mark_bootmem(start, end, 0, 0);//把释放的物理页对应的位清零
}

6.memblock结构解析
memblock结构体位于include/linux/memblock.h文件:

struct memblock {bool bottom_up;//表示内存分配方式,真:从低地址向上分配,假:从高地址向下分配phys_addr_t current_limit;//可分配内存的最大物理地址struct memblock_type memory;//可用物理内存区域(包括已分配和未分配的)struct memblock_type reserved;//预留物理内存区域(预留起来不可用,例子:设备树)
#ifdef CONFIG_HAVE_MEMBLOCK_PHYS_MAPstruct memblock_type physmem;//所有的物理内存区域
#endif
};struct memblock_type {unsigned long cnt;//区域数量unsigned long max;//分配区域的大小phys_addr_t total_size;//所有区域的大小struct memblock_region *regions;//区域数组指向区域数组char *name;//内存类型符号名
};struct memblock_region {phys_addr_t base;//起始物理地址phys_addr_t size;//长度enum memblock_flags flags;//内存区域标志属性
#ifdef CONFIG_HAVE_MEMBLOCK_NODE_MAPint nid;//节点编号
#endif
};
//内存区域标志属性定义
enum memblock_flags {MEMBLOCK_NONE      = 0x0,//表示没有特殊要求区域MEMBLOCK_HOTPLUG = 0x1,//表示可以热插拔的区域 MEMBLOCK_MIRROR     = 0x2,//表示镜像的区域,将内存数据做两份复制,分配放在主内存和镜像内存中 MEMBLOCK_NOMAP      = 0x4,//表示不添加到内核直接映射区域,即线性映射区
};

memblock体系的结构:

7.memblock接口函数解析
1)memblock添加内存区域函数:

int __init_memblock memblock_add(phys_addr_t base, phys_addr_t size)
{phys_addr_t end = base + size - 1;memblock_dbg("memblock_add: [%pa-%pa] %pF\n",&base, &end, (void *)_RET_IP_);//直接调用memblock_add_range将内存区块添加到memblock.memory进行管理return memblock_add_range(&memblock.memory, base, size, MAX_NUMNODES, 0);
}

我们继续追memblock_add_range:

int __init_memblock memblock_add_range(struct memblock_type *type,phys_addr_t base, phys_addr_t size,int nid, enum memblock_flags flags)
{bool insert = false;phys_addr_t obase = base;phys_addr_t end = base + memblock_cap_size(base, &size);int idx, nr_new;struct memblock_region *rgn;if (!size)return 0;if (type->regions[0].size == 0) {WARN_ON(type->cnt != 1 || type->total_size);type->regions[0].base = base;type->regions[0].size = size;type->regions[0].flags = flags;memblock_set_region_node(&type->regions[0], nid);type->total_size = size;return 0;}
repeat:/** The following is executed twice.  Once with %false @insert and* then with %true.  The first counts the number of regions needed* to accommodate the new area.  The second actually inserts them.*/base = obase;nr_new = 0;//遍历所有内存块,与新的内存块比较for_each_memblock_type(idx, type, rgn) {phys_addr_t rbase = rgn->base;phys_addr_t rend = rbase + rgn->size;if (rbase >= end)//新加入的内存块的结束地址已经到了则遍历结束break;if (rend <= base)//即加入的内存块的起始地址还没到则遍历下一块continue;/** @rgn overlaps.  If it separates the lower part of new* area, insert that portion.*///如果新加入的内存起始地址已经到了,但是还没到遍历的内存则插入if (rbase > base) {#ifdef CONFIG_HAVE_MEMBLOCK_NODE_MAPWARN_ON(nid != memblock_get_region_node(rgn));
#endifWARN_ON(flags != rgn->flags);nr_new++;if (insert)//添加内存区域,也就是填充struct memblock_region而已memblock_insert_region(type, idx++, base,rbase - base, nid,flags);}/* area below @rend is dealt with, forget about it */base = min(rend, end);}/* insert the remaining portion */if (base < end) {nr_new++;if (insert)memblock_insert_region(type, idx, base, end - base,nid, flags);}//如果需要加入的内存块个数为0则返回,不需要第二次遍历执行加入操作if (!nr_new)return 0;/** If this was the first round, resize array and repeat for actual* insertions; otherwise, merge and return.*///第一次会进入,判断内存区域块是否达到上限,是则退出,否则回到repeat//因为insert参数原因,第一次没有真正插入,第二次才会真正的插入if (!insert) {while (type->cnt + nr_new > type->max)if (memblock_double_array(type, obase, size) < 0)return -ENOMEM;insert = true;goto repeat;} else {memblock_merge_regions(type);//合并相邻且没有缝隙的内存区域return 0;}
}

2)memblock删除内存区域函数:memblock_remove

int __init_memblock memblock_remove(phys_addr_t base, phys_addr_t size)
{phys_addr_t end = base + size - 1;memblock_dbg("memblock_remove: [%pa-%pa] %pS\n",&base, &end, (void *)_RET_IP_);return memblock_remove_range(&memblock.memory, base, size);
}

memblock_remove_range:

static int __init_memblock memblock_remove_range(struct memblock_type *type,phys_addr_t base, phys_addr_t size)
{int start_rgn, end_rgn;int i, ret;//要删除的内存区域内存区内的内存块存在重叠部分,把这部分需要独立出来ret = memblock_isolate_range(type, base, size, &start_rgn, &end_rgn);if (ret)return ret;//根据要删除内存区的索引号,删除内存区块for (i = end_rgn - 1; i >= start_rgn; i--)memblock_remove_region(type, i);return 0;
}

3)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_base(phys_addr_t size, phys_addr_t align, phys_addr_t max_addr)
{phys_addr_t alloc;alloc = __memblock_alloc_base(size, align, max_addr);if (alloc == 0)panic("ERROR: Failed to allocate %pa bytes below %pa.\n",&size, &max_addr);return alloc;
}phys_addr_t __init __memblock_alloc_base(phys_addr_t size, phys_addr_t align, phys_addr_t max_addr)
{return memblock_alloc_base_nid(size, align, max_addr, NUMA_NO_NODE,MEMBLOCK_NONE);
}phys_addr_t __init memblock_alloc_base_nid(phys_addr_t size,phys_addr_t align, phys_addr_t max_addr,int nid, enum memblock_flags flags)
{return memblock_alloc_range_nid(size, align, 0, max_addr, nid, flags);
}static phys_addr_t __init memblock_alloc_range_nid(phys_addr_t size,phys_addr_t align, phys_addr_t start,phys_addr_t end, int nid,enum memblock_flags flags)
{phys_addr_t found;if (!align)align = SMP_CACHE_BYTES;//在给定范围和节点内找一块空区域found = memblock_find_in_range_node(size, align, start, end, nid,flags);//memblock_reserve是把找到的空区域添加到memblock.reserved中,表示已经用了if (found && !memblock_reserve(found, size)) {/** The min_count is set to 0 so that memblock allocations are* never reported as leaks.*///一个内存块分配物理内存的通知kmemleak_alloc_phys(found, size, 0, 0);return found;}return 0;
}

4)memblock释放内存函数:memblock_free

int __init_memblock memblock_free(phys_addr_t base, phys_addr_t size)
{phys_addr_t end = base + size - 1;memblock_dbg("   memblock_free: [%pa-%pa] %pF\n",&base, &end, (void *)_RET_IP_);//通知释放部分内存块kmemleak_free_part_phys(base, size);return memblock_remove_range(&memblock.reserved, base, size);
}static int __init_memblock memblock_remove_range(struct memblock_type *type,phys_addr_t base, phys_addr_t size)
{int start_rgn, end_rgn;int i, ret;//要删除的内存区域内存区内的内存块存在重叠部分,把这部分需要独立出来ret = memblock_isolate_range(type, base, size, &start_rgn, &end_rgn);if (ret)return ret;//根据要删除内存区的索引号,删除内存区块for (i = end_rgn - 1; i >= start_rgn; i--)memblock_remove_region(type, i);return 0;
}

7.memblock启动流程
1)解析设备树中的/memory,把所有物理内存添加到memblock
2)在memblock_init中初始化memblock
linux启动从init/main.c文件的start_kernel函数开始,然后从文件setup_arch(arch/arm64/kernel/setup.c文件中)函数检测处理器类型,初始化处理器和内存,其中的arm64_memblock_init(arch/arm64/mm/init.c文件中)函数就是arm64架构的memblock初始化流程。

void __init arm64_memblock_init(void)
{const s64 linear_region_size = -(s64)PAGE_OFFSET;/* Handle linux,usable-memory-range property *///解析设备树文件的内存节点fdt_enforce_memory_region();/* Remove memory above our supported physical address size *///删除超出我们支持的物理地址大小的内存memblock_remove(1ULL << PHYS_MASK_SHIFT, ULLONG_MAX);/** Ensure that the linear region takes up exactly half of the kernel* virtual address space. This way, we can distinguish a linear address* from a kernel/module/vmalloc address by testing a single bit.*/BUILD_BUG_ON(linear_region_size != BIT(VA_BITS - 1));/** Select a suitable value for the base of physical memory.*///全局变量memstart_addr记录了内存的起始物理地址memstart_addr = round_down(memblock_start_of_DRAM(),ARM64_MEMSTART_ALIGN);/** Remove the memory that we will not be able to cover with the* linear mapping. Take care not to clip the kernel which may be* high in memory.*///把线性映射区无法覆盖的物理内存范围从memblock中删除memblock_remove(max_t(u64, memstart_addr + linear_region_size,__pa_symbol(_end)), ULLONG_MAX);if (memstart_addr + linear_region_size < memblock_end_of_DRAM()) {/* ensure that memstart_addr remains sufficiently aligned */memstart_addr = round_up(memblock_end_of_DRAM() - linear_region_size,ARM64_MEMSTART_ALIGN);memblock_remove(0, memstart_addr);}/** Apply the memory limit if it was set. Since the kernel may be loaded* high up in memory, add back the kernel region that must be accessible* via the linear mapping.*///如果设置了内存限制,要根据限制使用内存if (memory_limit != PHYS_ADDR_MAX) {memblock_mem_limit_remove_map(memory_limit);//把超出限制的内存移除memblock_add(__pa_symbol(_text), (u64)(_end - _text));//添加可以使用的内存}if (IS_ENABLED(CONFIG_BLK_DEV_INITRD) && initrd_start) {/** Add back the memory we just removed if it results in the* initrd to become inaccessible via the linear mapping.* Otherwise, this is a no-op*/u64 base = initrd_start & PAGE_MASK;u64 size = PAGE_ALIGN(initrd_end) - base;/** We can only add back the initrd memory if we don't end up* with more memory than we can address via the linear mapping.* It is up to the bootloader to position the kernel and the* initrd reasonably close to each other (i.e., within 32 GB of* each other) so that all granule/#levels combinations can* always access both.*/if (WARN(base < memblock_start_of_DRAM() ||base + size > memblock_start_of_DRAM() +linear_region_size,"initrd not fully accessible via the linear mapping -- please check your bootloader ...\n")) {initrd_start = 0;} else {memblock_remove(base, size); /* clear MEMBLOCK_ flags */memblock_add(base, size);memblock_reserve(base, size);}}if (IS_ENABLED(CONFIG_RANDOMIZE_BASE)) {extern u16 memstart_offset_seed;u64 range = linear_region_size -(memblock_end_of_DRAM() - memblock_start_of_DRAM());/** If the size of the linear region exceeds, by a sufficient* margin, the size of the region that the available physical* memory spans, randomize the linear region as well.*/if (memstart_offset_seed > 0 && range >= ARM64_MEMSTART_ALIGN) {range /= ARM64_MEMSTART_ALIGN;memstart_addr -= ARM64_MEMSTART_ALIGN *((range * memstart_offset_seed) >> 16);}}/** Register the kernel text, kernel data, initrd, and initial* pagetables with memblock.*///把内核镜像占用的内存添加到memblock的预留区中,表示预留了不再分配出去memblock_reserve(__pa_symbol(_text), _end - _text);
#ifdef CONFIG_BLK_DEV_INITRDif (initrd_start) {memblock_reserve(initrd_start, initrd_end - initrd_start);/* the generic initrd code expects virtual addresses */initrd_start = __phys_to_virt(initrd_start);initrd_end = __phys_to_virt(initrd_end);}
#endif//扫描设备树中的保留内存区域并添加到memblock的预留区域中early_init_fdt_scan_reserved_mem();/* 4GB maximum for 32-bit only capable devices */if (IS_ENABLED(CONFIG_ZONE_DMA32))arm64_dma_phys_limit = max_zone_dma_phys();elsearm64_dma_phys_limit = PHYS_MASK + 1;reserve_crashkernel();reserve_elfcorehdr();high_memory = __va(memblock_end_of_DRAM() - 1) + 1;dma_contiguous_reserve(arm64_dma_phys_limit);memblock_allow_resize();
}

最后,引导内存分配器退休,会将物理内存填充到伙伴分配器中,移交给伙伴分配器进行管理。

linux内存管理(五)-引导内存分配器相关推荐

  1. 【Linux 内核 内存管理】分区伙伴分配器 ② ( free_area 空闲区域结构体源码 | 分配标志位 | GFP_ZONE_TABLE 标志位区域类型映射表 |分配标志位对应的内存区域类型 )

    文章目录 一.free_area 空闲区域结构体源码分析 二.分配标志位 三.GFP_ZONE_TABLE 标志位区域类型映射表 四.分配标志位对应的内存区域类型 一.free_area 空闲区域结构 ...

  2. linux进程管理内存管理,Linux专业知识四:Linux系统进程管理及查看内存

    本文主讲Linux专业知识之Linux系统进程管理及查看内存的情况,以Redhat RHEL7操作系统为例. 一.进程 程序与进程:程序是静态的(文件),进程是动态的(运行的程序). 进程和线程:一个 ...

  3. 【Linux 内核 内存管理】优化内存屏障 ③ ( 编译器屏障 | 禁止 / 开启内核抢占 与 方法保护临界区 | preempt_disable 禁止内核抢占源码 | 开启内核抢占源码 )

    文章目录 一.禁止 / 开启内核抢占 与 方法保护临界区 二.编译器优化屏障 三.preempt_disable 禁止内核抢占 源码 四.preempt_enable 开启内核抢占 源码 一.禁止 / ...

  4. 属性与内存管理(属性与内存管理都是相互关联的)

    <span style="font-size:18px;"> 属性与内存管理(属性与内存管理都是相互关联的)第一部分一,属性:属性是OC2.0之后出来的新语法,用来取代 ...

  5. 【C 语言必知必会】内存管理、动态分配内存、野指针

    C 语言内存管理.动态分配内存.野指针 文章目录 C 语言内存管理.动态分配内存.野指针 前言: 1.内存分区 1.1 代码区 1.2.1 全局初始化数据区(静态数据区data段) 1.2.2 未初始 ...

  6. JVM自动内存管理机制——Java内存区域(下)

    一.虚拟机参数配置 在上一篇<Java自动内存管理机制--Java内存区域(上)>中介绍了有关的基础知识,这一篇主要是通过一些示例来了解有关虚拟机参数的配置. 1.Java堆参数设置 a) ...

  7. Java内存管理:Java内存区域 JVM运行时数据区

    Java内存管理:Java内存区域 JVM运行时数据区 在前面的一些文章了解到javac编译的大体过程.Class文件结构.以及JVM字节码指令. 下面我们详细了解Java内存区域:先说明JVM规范定 ...

  8. C++:内存管理:C++内存管理详解

    C++语言内存管理是指:对系统的分配.创建.使用这一系列操作.在内存管理中,由于是操作系统内存,使用不当会造成很麻烦的后果.本文将从系统内存的分配.创建出发,并且结合例子来说明内存管理不当会造成的结果 ...

  9. Unity 之 Mono内存管理与泄漏 — 内存是手游的硬伤(转)

    WeTest导读 内存是游戏的硬伤,如果没有做好内存的管理问题,游戏极有可能会出现卡顿,闪退等影响用户体验的现象.本文介绍了在腾讯游戏在Unity游戏开发过程中常见的Mono内存管理问题,并介绍了一系 ...

  10. Linux内核机制总结内存管理之连续内存分配器(二十七)

    文章目录 1 连续内存分配器 1.1 使用方法 1.2 技术原理 重要:本系列文章内容摘自<Linux内核深度解析>基于ARM64架构的Linux4.x内核一书,作者余华兵.系列文章主要用 ...

最新文章

  1. 2021桓台高考成绩查询,桓台中考成绩查询2021
  2. Mysql升级过程的问题
  3. C语言变量的定义包括变量存储类型和变量的什么?
  4. codeup 2044 暴力搜索
  5. 计算机网络(二十七)-IPv4
  6. python怎么退出全屏_wxPython:退出全屏
  7. Python学习(5)——内置函数
  8. 九型人格在招聘中的应用策略
  9. 中国与印度的GDP深层剖析
  10. 外汇EA量化真的可以赚钱吗?还是新型骗局?
  11. 「镁客·请讲」打造一台眼睛专属“跑步机”,鹰视菲诺是如何用AI拯救近视的?...
  12. Oracle学习(八)——————————————子查询
  13. 【Android 仿微信通讯录 导航分组列表-下】自定义View为RecyclerView打造右侧索引导航栏IndexBar
  14. 《男人装》2006.05
  15. SparkML -- LightGBM On Spark
  16. nginx屏蔽某些地区访问网站
  17. 银河麒麟设置默认ROOT账号登录
  18. matlab gmt,科学网—在Matlab中调用GMT画图 - 徐逸鹤的博文
  19. 2.《一个物联网系统的实现》之 EMQX 配置
  20. luffcc项目-13-积分抵扣、发起支付、

热门文章

  1. 精彩十年(4)——缔造神话
  2. 机器学习之SVM多分类
  3. LintCode 547---两数组的交集
  4. Python 脚本如何执行另一个脚本
  5. mysql用户增删改
  6. 当systeminfo不能显示系统启动时间了--用命令行修复一下
  7. conky的自动启动
  8. 【PostgreSQL】PostgreSQL安装步骤
  9. 【hive】hive----自定义UDF 函数-----时间格式化以及取出双引号的代码
  10. app测试过程和重点关注内容