• 内存分配器memblock【转】


    背景

    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.   
    4. struct memblock memblock __initdata_memblock = {  
    5.     .memory.regions     = memblock_memory_init_regions,  
    6.     .memory.cnt     = 1,    /* empty dummy entry */  
    7.     .memory.max     = INIT_MEMBLOCK_REGIONS,  
    8.   
    9.     .reserved.regions   = memblock_reserved_init_regions,  
    10.     .reserved.cnt       = 1,    /* empty dummy entry */  
    11.     .reserved.max       = INIT_MEMBLOCK_REGIONS,  
    12.   
    13.     .bottom_up      = false,  
    14.     .current_limit      = MEMBLOCK_ALLOC_ANYWHERE,  
    15. };    

    .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.   
    6. static int __init_memblock memblock_reserve_region(phys_addr_t base,  
    7.                            phys_addr_t size,  
    8.                            int nid,  
    9.                            unsigned long flags)  
    10. {  
    11.     struct memblock_type *_rgn = &memblock.reserved;  
    12.     return memblock_add_region(_rgn, base, size, nid, flags);  
    13. }  

    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.   
    6. phys_addr_t __init memblock_alloc_try_nid(phys_addr_t size, phys_addr_t align, int nid)  
    7. {  
    8.     phys_addr_t res = memblock_alloc_nid(size, align, nid);  
    9.   
    10.     if (res)  
    11.         return res;  
    12.     return memblock_alloc_base(size, align, MEMBLOCK_ALLOC_ACCESSIBLE);  
    13. }  

    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.   
    7.     if (!align)  
    8.         align = SMP_CACHE_BYTES;  
    9.   
    10.     found = memblock_find_in_range_node(size, align, 0, max_addr, nid);  
    11.     if (found && !memblock_reserve(found, size))  
    12.         return found;  
    13.   
    14.     return 0;  
    15. }  

    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 ",  
    4.              (unsigned long long)base,  
    5.              (unsigned long long)base + size - 1,  
    6.              (void *)_RET_IP_);  
    7.   
    8.     return __memblock_remove(&memblock.reserved, base, size);  
    9. }  

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

  • 相关阅读:
    MyCAT常用分片规则之分片枚举
    MySQL参数
    CentOS 7下MySQL服务启动失败的解决思路
    ERROR 1819 (HY000): Your password does not satisfy the current policy requirements
    CHECK_NRPE: Received 0 bytes from daemon. Check the remote server logs for error messages.
    MyCAT实现MySQL的读写分离
    搭建Spark的单机版集群
    如何部署Icinga服务端
    MyCAT简易入门
    如何部署Icinga客户端
  • 原文地址:https://www.cnblogs.com/sky-heaven/p/7229322.html
Copyright © 2020-2023  润新知