Linux - 内存 - memblock 分配器

说明

  • memblock是Linux内核启动早期用于管理物理内存的机制,在伙伴系统(Buddy System)接管内存管理之前为系统提供物理内存分配、释放等功能。
  • 相对于伙伴系统,memblock功能和实现较为简单。
  • 本文基于:linux_5.10 arm64平台。

历史

  • 启动早期的内存管理器实现有bootmem和memblock,bootmem是早期内核采用,4.x以后内核内核采用memblock,配置了NO_BOOTMEM宏。
  • memblock取代了bootmem算法。

实现原理

获取物理内存布局

  • 不同于伙伴系统以内存页为操作对象,memblock以物理内存段为操作对象,系统从dtb或者uboot传递来的mem信息中解析出总的物理内存信息(核心信息是地址范围),此时的物理内存是一段段的地址空间,再初始化memblock。

fdt方式

  • dts配置中有memory的配置
* 单段物理内存
memory {
    device_type = "memory";
    reg = <0x0 0x80000000 0x0 0x10000000>;
};
* 多段物理内存
memory@0 {
    device_type = "memory";
    reg = <0x00000000 0x00000000 0x00000000 0x05e00000>,
          <0x00000000 0x05f00000 0x00000000 0x00001000>,
          <0x00000000 0x05f02000 0x00000000 0x00efd000>,
          <0x00000000 0x06e00000 0x00000000 0x0060f000>,
          <0x00000000 0x07410000 0x00000000 0x1aaf0000>,
          <0x00000000 0x22000000 0x00000000 0x1c000000>;
};

uboot bootargs方式

  • uboot启动linux时,可以通过linux的启动参数bootargs,传递物理内存信息(基址和size),初始化memblock。
  • 格式如下:
mem=size@start
  • 配置流程
* 函数调用栈
early_mem  //file: arch/arm64/mm/init.c,解析出物理内存信息,保存在全局变量中
arm64_memblock_init //file: arch/arm64/mm/init.c 
->  memblock_add
* 核心代码
void __init arm64_memblock_init(void)
{
    ...
    if (memory_limit != PHYS_ADDR_MAX) { //全局变量非默认值
        memblock_mem_limit_remove_map(memory_limit); 
        memblock_add(__pa_symbol(_text), (u64)(_end - _text));
    }
    ...
}

可用段查找原理

  • memblock内存分配时可用段查找采用first match算法,即占用首先找到的可以段。
  • 内存分配查找的方向可以是从高到低,也可以是从低到高,通过总context中的成员变量bottom_up决定。

两个阶段

  • memblock有两个阶段
  1. memblock init之前;主要是静态分配,根据dts配置中预留内存定义(reserved memory),内核本身(code等),dtb等,在物理内存上分配出所需的预留内存。
  2. memblock init之后,伙伴系统初始化完之前;主要是Linux内核机制产生的动态内存分配。
  • 两个阶段以以memblock configuration为分隔。

分配结果

  • memblock分配结果都是预留内存,分配结束后固定占用,无法释放和复用。

代码逻辑

  • memblock源码在Linux内核根目录下的:
include/linux/memblock.h 
mm/memblock.c 

数据结构和实例

  • memblock从大到小定义了三个数据结构,如下:
  1. 总context定义
struct memblock {
    bool bottom_up;  //内存分配的方向:从高到低(FALSE)、从低到高(TRUE)
    phys_addr_t current_limit; //最大内存地址
    struct memblock_type memory; //可管理的内存段
    struct memblock_type reserved; //预留内存
};
  1. 内存类型定义
struct memblock_type {
    unsigned long cnt; //内存区域个数(占用数组个数)
    unsigned long max; //最大区域个数(数组总个数)
    phys_addr_t total_size; //该内存类型总大小
    struct memblock_region *regions; //包含的内存区域数组
    char *name;
};
  1. 内存区域(地址段)定义
struct memblock_region {
	phys_addr_t base;  //基址
	phys_addr_t size;  //空间大小
	enum memblock_flags flags; //flag
#ifdef CONFIG_NUMA
	int nid;   //物理内存 node id,NUMA可以存在多个物理内存节点(node)
#endif
};

enum memblock_flags {
    MEMBLOCK_NONE           = 0x0,  /* No special request */ //正常
    MEMBLOCK_HOTPLUG        = 0x1,  /* hotpluggable region */ //可插拔区域
    MEMBLOCK_MIRROR         = 0x2,  /* mirrored region */
    MEMBLOCK_NOMAP          = 0x4,  /* don't add to kernel direct mapping */ //no map区域
};
  • 总context实例,以全局静态变量(保存在BSS段中)形式定义,区域都是预先定义的全局静态数组,数组个数默认128(INIT_MEMBLOCK_REGIONS)。
static struct memblock_region memblock_memory_init_regions[INIT_MEMBLOCK_REGIONS] __initdata_memblock;
static struct memblock_region memblock_reserved_init_regions[INIT_MEMBLOCK_RESERVED_REGIONS] __initdata_memblock;
#ifdef CONFIG_HAVE_MEMBLOCK_PHYS_MAP
static struct memblock_region memblock_physmem_init_regions[INIT_PHYSMEM_REGIONS];
#endif

struct memblock memblock __initdata_memblock = {
    .memory.regions         = memblock_memory_init_regions,
    .memory.cnt             = 1,    /* empty dummy entry */
    .memory.max             = INIT_MEMBLOCK_REGIONS,
    .memory.name            = "memory",

    .reserved.regions       = memblock_reserved_init_regions,
    .reserved.cnt           = 1,    /* empty dummy entry */
    .reserved.max           = INIT_MEMBLOCK_RESERVED_REGIONS,
    .reserved.name          = "reserved",

    .bottom_up              = false,
    .current_limit          = MEMBLOCK_ALLOC_ANYWHERE,
};

API

  1. memblock_add
  • 将内存区域加入memblock可管理的内存区域,即memory的region队列。
  1. memblock_free
  • 将一个物理内存段从预留内存中移除,该内存段重新标记为可用。
int memblock_free(phys_addr_t base, phys_addr_t size)
{
	memblock_dbg("   memblock_free: [%#016llx-%#016llx] %pF\n",
		     (unsigned long long)base,
		     (unsigned long long)base + size - 1,
		     (void *)_RET_IP_);

	kmemleak_free_part_phys(base, size);
	return memblock_remove_range(&memblock.reserved, base, size);
}

调试

  1. 获取memblock的详细分配log,可以通过在uboot bootargs中加入“memblock=debug”,内核启动后,通过dmesg或者/proc/kmsg查看调试信息。
  2. linux kernel启动后可以通过debug fs查看内存地址范围和reserved区域,如下:
/sys/kernel/debug/memblock/memory #交由系统管理的内存 
/sys/kernel/debug/memblock/reserved #预留的内存
* 需要开启配置
CONFIG_DEBUG_FS
CONFIG_ARCH_KEEP_MEMBLOCK //是否保留memblock分配信息
  • 该功能不是很有必要并且会占用一定硬件资源,方法1足以满足调试需求,新版本内核CONFIG_ARCH_KEEP_MEMBLOCK配置默认是关闭的。

交接

  • buddy分配器初始化ok后,memblock分配器将内存管理工作交接给buddy(伙伴)分配器。

标志

  • memblock和伙伴系统的交接标志:释放init进程内存(free_initmem函数处理),之后系统可用内存(/proc/meminfo中的MemTotal)就固定了。

分配实例分析

  • 分配实例

你可能感兴趣的:(#,Linux,内核知识,linux,内存)