linux 内存管理(13) - memblock

  • 了解memblock机制。

1.概述

  在引导内核的过程中,需要使用内存, 而这个时候内核的内存管理并没有被创建, 因此也就需要一种精简的内存管理系统先接受这个工作, 而在初始化完成后, 再将旧的接口废弃, 转而使用强大的buddy系统来进行内存管理.

  早期的Linux内核在引导阶段都是通过bootmem来完成初期的内存管理的, 但是后来的版本开始把bootmem弃用了,使用memblock机制.[refer to: Use memblock interface instead of bootmem]

拓展:bootmem
  在启动过程期间,尽管内存管理尚未初始化,但内核仍然需要分配内存以创建各种数据结构。bootmem分配器用于在启动阶段早期分配内存。
  显然,对该分配器的需求集中于简单性方面,而不是性能和通用性。因此内核开发者决定实现一个最先适配(first-fit)分配器用于在启动阶段管理内存。这是可能想到的最简单的方式。
  该分配器使用一个位图来管理页,位图比特位的数目与系统中物理内存页的数目相同。比特位位1,表示页以用;比特位位0,表示页空闲。
  在需要分配内存时,分配器逐位扫描位图,知道找到一个能提供足够连续页的位置,即所谓的最先最佳(first-best)或最先适配位置。
  该过程不是很高效,因为每次分配都必须从头扫描比特链。因此在内核完全初始化之后,不能将该适配器用于内存管理。伙伴系统(连同slab、slub、或slob分配器)是一个好得多的备选方案。

  memblock算法是linux内核初始化阶段的一个内存分配器,本质上是取代了原来的bootmem算法. memblock实现比较简单,而它的作用就是在page allocator初始化之前来管理内存,完成分配和释放请求.

  Memblock is a method of managing memory regions during the early boot period when the usual kernel memory allocators are not up and running.

  Memblock views the system memory as collections of contiguous regions. There are several types of these collections:

  • memory
    describes the physical memory available to the kernel; this may differ from the actual physical memory installed in the system, for instance when the memory is restricted with mem= command line parameter

  • reserved
    describes the regions that were allocated

  • physmap
    describes the actual physical memory regardless of the possible restrictions; the physmap type is only available on some architectures.

2.memblock的数据结构

2.1.struct memblock

include/linux/memblock.h
struct memblock {
    bool bottom_up;  /* is bottom up direction? 
    如果true, 则允许由下而上地分配内存*/
    phys_addr_t current_limit; /*指出了内存块的大小限制*/  
    /*  接下来的三个域描述了内存块的类型,即预留型,内存型和物理内存*/
    struct memblock_type memory;
    struct memblock_type reserved;
#ifdef CONFIG_HAVE_MEMBLOCK_PHYS_MAP
    struct memblock_type physmem;
#endif
};

在这里插入图片描述
2.2.struct memblock_type

struct memblock_type
{
    unsigned long cnt;      /* number of regions */
    unsigned long max;      /* size of the allocated array */
    phys_addr_t total_size; /* size of all regions */
    struct memblock_region *regions;
};

在这里插入图片描述

2.3.struct memblock_region 维护一块内存区块,其定义为:

struct memblock_region
{
    phys_addr_t base;
    phys_addr_t size;
    unsigned long flags;
#ifdef CONFIG_HAVE_MEMBLOCK_NODE_MAP
    int nid;
#endif
};

在这里插入图片描述
  内存区块被维护在不同的 struct memblock_type 的 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_memblok;

2.4.memblock_region flags字段

/* Definition of memblock flags. */
enum {
    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 */
};

MEMBLOCK 内存分配器基础框架如下:
在这里插入图片描述
  MEMBLOCK 分配器使用一个 struct memblock 结构维护着两种内存, 其中成员 memory 维护着可用物理内存区域;成员 reserved 维护着操作系统预留的内存区域。 每个区域使用数据结构 struct memblock_type 进行管理,其成员 regions 负责维护该类型内 存的所有内存区,每个内存区使用数据结构 struct memblock_region 进行维护。

2.5.struct memblock 实例化

  内核实例化了一个 struct memblock 结构,以供 MEMBLOCK 进行内存的管理,其定义如下:

mm/memblock.c:
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",

#ifdef CONFIG_HAVE_MEMBLOCK_PHYS_MAP
        .physmem.regions        = memblock_physmem_init_regions,
        .physmem.cnt            = 1,    /* empty dummy entry */
        .physmem.max            = INIT_PHYSMEM_REGIONS,
        .physmem.name           = "physmem",
#endif

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

  从上面的数据中可以看出,对于可用物理内存,其名字设定为 “memory”,初始状态系统下,可用 物理物理的所有内存区块都维护在 memblock_memory_init_regions 上,当前情况下,可用物 理内存区只包含一个内存区块,然而可用物理内存可管理 INIT_MEMBLOCK_REGIONS 个内存区 块;同理,对于预留内存,其名字设定为 “reserved”,初始状态下,预留物理内存的所有区块 都维护在 memblock_reserved_init_regions 上,当前情况下,预留物理内存区只包含一个内 存区块,然而预留内存区可以维护管理 INIT_MEMBLOCK_RESERVED_REGIONS 个内存区块。

3.APIs

  • memblock_phys_mem_size
memblock_phys_mem_size
phys_addr_t __init_memblock memblock_phys_mem_size(void)
{
	return memblock.memory.total_size;
}

  函数的作用是获得可用物理内存的总体积。函数直接返回 memblock.memory 的 total_size, total_size 成员存储该内存区的体积大小。

  • memblock_reserved_size
memblock_reserved_size
phys_addr_t __init_memblock memblock_reserved_size(void)
{
	return memblock.reserved.total_size;
}

  函数的作用是获得预留区内存的总体积。函数直接返回 memblock.reserved 的 total_size, total_size 成员存储该内存区的体积大小。

  • memblock_start_of_DRAM
/* lowest address */
phys_addr_t __init_memblock memblock_start_of_DRAM(void)
{
	return memblock.memory.regions[0].base;
}

  函数的作用是获得 DRAM 的起始地址。DRAM 的起始地址就是 memblock.memory 内存区 第一个内存区块的起始物理地址。函数直接返回 memblock.memory 的 regions[0].base, regions[0].base 成员存储 DRAM 的起始物理地址。

  • memblock_end_of_DRAM
phys_addr_t __init_memblock memblock_end_of_DRAM(void)
{
	int idx = memblock.memory.cnt - 1;
	return (memblock.memory.regions[idx].base + memblock.memory.regions[idx].size);
}

  函数的作用是获得 DRAM 的终止地址。DRAM 的终止地址就是 memblock.memory 内存区 最后一个内存区块的终止物理地址。最后一个内存区的索引是 memblock.memory.cnt - 1, 所以这个索引对应的内存区的终止地址就是 DRAM 的结束地址。

  • memblock_is_reserved
memblock_is_reserved
bool __init_memblock memblock_is_reserved(phys_addr_t addr)
{
	return memblock_search(&memblock.reserved, addr) != -1;
}

  函数的作用是检查某个物理地址是否属于预留区。参数 addr 指向要检查的地址。 函数调用 memblock_search() 函数进行地址检查。

  • memblock_is_memory
memblock_is_memory
bool __init_memblock memblock_is_memory(phys_addr_t addr)
{
	return memblock_search(&memblock.memory, addr) != -1;
}

  函数的作用是检查某个物理地址是否属于可用内存区。参数 addr 指向要检查的地址。 函数调用 memblock_search() 函数进行地址检查。

  • memblock_is_region_memory
bool __init_memblock memblock_is_region_memory(phys_addr_t base, phys_addr_t size)
{
	int idx = memblock_search(&memblock.memory, base);
	phys_addr_t end = base + memblock_cap_size(base, &size);

	if (idx == -1)
		return false;
	return (memblock.memory.regions[idx].base +
		 memblock.memory.regions[idx].size) >= end;
}

  函数的作用是检查某段内存区是否属于可用内存区。参数 base 需要检查的区段的起始物理 地址。参数 size 需要检查区段的长度。函数调用 memblock_search() 函数对 base 参 数进行检查,memblock_search() 函数返回 base 地址在可用内存区的索引,接着计算出 需要检查区段的终止地址,最后通过检查终止地址是否在 idx 对应的内存区段之内,如果 在表示这段内存区块在可用物理内存区内;如果不在,则表示这段内存区块不属于可用物理 内存区段。

  • memblock_is_region_reserved
bool __init_memblock memblock_is_region_reserved(phys_addr_t base, phys_addr_t size)
{
	memblock_cap_size(base, &size);
	return memblock_overlaps_region(&memblock.reserved, base, size);
}

  函数的作用是检查某段内存区是否属于预留区。参数 base 需要检查的区段的起始物理 地址。参数 size 需要检查区段的长度。函数调用 memblock_cap_size() 函数对 size 参数进行处理之后,传递给 memblock_overlaps_region() 函数检查 base 和 size 对应的内存区是否属于预留区。

  • memblock_get_current_limit
phys_addr_t __init_memblock memblock_get_current_limit(void)
{
	return memblock.current_limit;
}

  函数的作用就是返回 MEMBLOCK 的当前 limit。

  • memblock_set_current_limit
void __init_memblock memblock_set_current_limit(phys_addr_t limit)
{
	memblock.current_limit = limit;
}

  函数的作用就是设置 MEMBLOCK 的当前 limit。

  具体函数使用参考案例:

https://github.com/BiscuitOS/HardStack/tree/master/Memory-Allocator/Memblock-allocator/API

4.案例分析

1>.驱动的源码如下:

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/memblock.h>

int bs_debug = 0;

#ifdef CONFIG_DEBUG_MEMBLOCK_HELPER
int __init debug_memblock_helper(void)
{
	struct memblock_region *reg;
	phys_addr_t size;
	phys_addr_t addr;
	phys_addr_t limit;
	bool state;
	int nid;

	/*
	 * Emulate memory
	 *
	 *                      memblock.memory
	 * 0     | <----------------------------------------> |
	 * +-----+---------+-------+----------+-------+-------+----+
	 * |     |         |       |          |       |       |    |
	 * |     |         |       |          |       |       |    |
	 * |     |         |       |          |       |       |    |
	 * +-----+---------+-------+----------+-------+-------+----+
	 *                 | <---> |          | <---> |
	 *                 Reserved 0         Reserved 1
	 *
	 * Memroy Region:   [0x60000000, 0xa0000000]
	 * Reserved Region: [0x80000000, 0x8d000000]
	 * Reserved Region: [0x90000000, 0x92000000]
	 */
	memblock_reserve(0x80000000, 0xd000000);
	memblock_reserve(0x90000000, 0x2000000);
	pr_info("Memory Regions:\n");
	for_each_memblock(memory, reg)
		pr_info("Region: %#x - %#x\n", reg->base,
					reg->base + reg->size);
	pr_info("Reserved Regions:\n");
	for_each_memblock(reserved, reg)
		pr_info("Region: %#x - %#x\n", reg->base,
					reg->base + reg->size);

	/* Obtain memblock.memory total size */
	size = memblock_phys_mem_size();
	pr_info("Phyiscal Memory total size: %#x\n", size);

	/* Obtain memblock.reserved total size */
	size = memblock_reserved_size();
	pr_info("Reserved Memory total size: %#x\n", size);

	/* Obtain Start physical address of DRAM */
	addr = memblock_start_of_DRAM();
	pr_info("Start address of DRAM:      %#x\n", addr);

	/* Obtain End of physical address of DRAM */
	addr = memblock_end_of_DRAM();
	pr_info("End address of DRAM:        %#x\n", addr);

	/* Check address is memblock.reserved */
	addr = 0x81000000; /* Assume address in memblock.reserved */
	state = memblock_is_reserved(addr);
	if (state)
		pr_info("Address: %#x in reserved.\n", addr);

	/* Check address in memblock.memory */
	addr = 0x62000000; /* Assume address in memblock.memory */
	state = memblock_is_memory(addr);
	if (state)
		pr_info("Address: %#x in memory.\n", addr);

	/* Check region in memblock.memory */
	addr = 0x62000000;
	size = 0x100000; /* Assume [0x62000000, 0x62100000] in memory */
	state = memblock_is_region_memory(addr, size);
	if (state)
		pr_info("Region: [%#x - %#x] in memblock.memory.\n",
				addr, addr + size);

	/* Check region in memblock.reserved */
	addr = 0x80000000;
	size = 0x100000; /* Assume [0x80000000, 0x80100000] in reserved */
	state = memblock_is_region_reserved(addr, size);
	if (state)
		pr_info("Region: [%#x - %#x] in memblock.reserved.\n",
				addr, addr + size);

	/* Obtain current limit for memblock */
	limit = memblock_get_current_limit();
	pr_info("MEMBLOCK current_limit: %#x\n", limit);

	/* Set up current_limit for MEMBLOCK */
	memblock_set_current_limit(limit);

	/* Check memblock regions is hotpluggable */
	state = memblock_is_hotpluggable(&memblock.memory.regions[0]);
	if (state)
		pr_info("MEMBLOCK memory.regions[0] is hotpluggable.\n");
	else
		pr_info("MEMBLOCK memory.regions[0] is not hotpluggable.\n");

	/* Check memblock regions is mirror */
	state = memblock_is_mirror(&memblock.memory.regions[0]);
	if (state)
		pr_info("MEMBLOCK memory.regions[0] is mirror.\n");
	else
		pr_info("MEMBLOCK memory.regions[0] is not mirror.\n");

	/* Check memblock regions is nomap */
	state = memblock_is_nomap(&memblock.memory.regions[0]);
	if (state)
		pr_info("MEMBLOCK memory.regions[0] is nomap.\n");
	else
		pr_info("MEMBLOCK memory.regions[0] is not nomap.\n");

	/* Check region nid information */
	nid = memblock_get_region_node(&memblock.memory.regions[0]);
	pr_info("MEMBLOCK memory.regions[0] nid: %#x\n", nid);
	/* Set up region nid */
	memblock_set_region_node(&memblock.memory.regions[0], nid);

	/* Obtian MEMBLOCK allocator direction */
	state = memblock_bottom_up();
	pr_info("MEMBLOCK direction: %s", state ? "bottom-up" : "top-down");
	/* Set up MEMBLOCK allocate direction */
	memblock_set_bottom_up(state);

	return 0;
}
#endif

  驱动直接编译进内核,将驱动放到 drivers/BiscuitOS/ 目录下,命名为 memblock.c。

2>.添加drivers/BiscuitOS/Kconfig:

menu "Biscuitos support"

config BISCUITOS
    bool "BiscuitOS driver"

if BISCUITOS

config BISCUITOS_DRV 
    bool "BiscuitOS driver"

config DEBUG_MEMBLOCK_HELPER
    bool "memblock helper"

config MEMBLOCK_ALLOCATOR
    bool "MEMBLOCK allocator"

if MEMBLOCK_ALLOCATOR
                                                                                                             
config DEBUG_MEMBLOCK_PHYS_ALLOC_TRY_NID
    bool "memblock_phys_alloc_try_nid()"

endif # MEMBLOCK_ALLOCATOR

endif # BISCUITOS_DRV

endmenu

3>.添加drivers/BiscuitOS/Makefile:

obj-$(CONFIG_MEMBLOCK_ALLOCATOR) += memblock.o  

4>.添加iTop-4412_scp_defconfig:

diff --git a/arch/arm/configs/iTop-4412_scp_defconfig b/arch/arm/configs/iTop-4412_scp_defconfig
index 828edc8f..58022467 100644
--- a/arch/arm/configs/iTop-4412_scp_defconfig
+++ b/arch/arm/configs/iTop-4412_scp_defconfig
 # CONFIG_STRING_SELFTEST is not set
 # CONFIG_VIRTUALIZATION is not set
+CONFIG_BISCUITOS=y
+CONFIG_BISCUITOS_DRV=y
+CONFIG_MEMBLOCK_ALLOCATOR=y
+CONFIG_DEBUG_MEMBLOCK_HELPER=y

5>.增加调试点

  驱动运行还需要在内核的指定位置添加调试点,由于该驱动需要在内核启动阶段就使用,参考下面 patch 将源码指定位置添加调试代码:

diff --git a/arch/arm/kernel/setup.c b/arch/arm/kernel/setup.c
index 375b13f7e..fec6919a9 100644
--- a/arch/arm/kernel/setup.c
+++ b/arch/arm/kernel/setup.c
@@ -1073,6 +1073,10 @@ void __init hyp_mode_check(void)
 void __init setup_arch(char **cmdline_p)
 {
 	const struct machine_desc *mdesc;
+#ifdef CONFIG_DEBUG_MEMBLOCK_HELPER
+	extern int bs_debug;
+	extern int debug_memblock_helper(void);
+#endif

 	setup_processor();
 	mdesc = setup_machine_fdt(__atags_pointer);
@@ -1104,6 +1108,10 @@ void __init setup_arch(char **cmdline_p)
 	strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE);
 	*cmdline_p = cmd_line;

ck_add
+#ifdef CONFIG_DEBUG_MEMBLOCK_HELPER
+	debug_memblock_helper();
+#endif
+
 	early_fixmap_init();
 	early_ioremap_init();

Note:
详细分析:- https://biscuitos.github.io/blog/MMU-ARM32-MEMBLOCK-memblock_information/

  Arm64架构下, 内核在start_kernel()->setup_arch()中通过arm64_memblock_init( )完成了memblock的初始化之后, 接着通过setup_arch()->paging_init()开始初始化分页机制。

refer to

  • Documentation/core-api/boot-time-mm.rst
  • https://www.lagou.com/lgeduarticle/23340.html
  • https://github.com/BiscuitOS/HardStack/tree/master/Memory-Allocator/Memblock-allocator/API
发布了161 篇原创文章 · 获赞 15 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/weixin_41028621/article/details/104620059