引导内存分配器原理

引导内存分配器原理

参考视频链接 https://ke.qq.com/course/417774?flowToken=1040955

a. bootmem分配器

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

/*  include/linux/bootmem.h */
#ifndef CONFIG_NO_BOOTMEM
/*
 * node_bootmem_map is a map pointer - the bits represent all physical 
 * memory pages (including holes) on the node.
 */
typedef struct bootmem_data {
	unsigned long node_min_pfn;
	unsigned long node_low_pfn;
	void *node_bootmem_map;
	unsigned long last_end_off;
	unsigned long hint_idx;
	struct list_head list;
} bootmem_data_t;

extern bootmem_data_t bootmem_node_data[];
#endif

其中每个内存节点有一个bootmem_data实例:

/* include/linux/mmzone.h  */
struct bootmem_data;
typedef struct pglist_data {
	struct zone node_zones[MAX_NR_ZONES];
	struct zonelist node_zonelists[MAX_ZONELISTS];
	int nr_zones;
#ifdef CONFIG_FLAT_NODE_MEM_MAP	/* means !SPARSEMEM */
	struct page *node_mem_map;
#ifdef CONFIG_PAGE_EXTENSION
	struct page_ext *node_page_ext;
#endif
#endif

bootmem分配器算法:

  • 只把低端内存添加到bootmem分配器,低端内存是可以直接映射到内核虚拟地址空间的物理内存;
  • 使用一个位图记录哪些物理面被分配,如果物理页被分配,把这个物理页对应的位设置为1;
  • 采用最先适配算法,扫描位图,找到第一个足够大的空闲内存块;
  • 为了支持分配小于一页的内存块,记录上次分配的内存块的结束为止后面一个字节的偏移和后面一页的索引,下次分配时,从上次分配的位置后面开始遍历。如果上次分配的最后一个物理页剩余空间足够,可以直接在这个物理页上分配内存。

bootmem分配器对外提供分配内存函数alloc_bootmem,释放内存的函数是free_bootmem。ARM64架构内核不再使用bootmem分配器,但是其它处理器架构还在使用bootmem分配器。

b.memblock分配器

原理: 主要维护两种内存,第一种是系统可用的物流内存,即系统实际含有的物理内存,其值从DTS中进行配置,通过uboot实际探测之后传入到内核。第二种内存是内核预留给操作系统的内存,这部分内存作为特殊功能使用,不能作为共享内存使用。

struct memblock {
	bool bottom_up;  //分配的内存方式,值为true表示从低地址向上分配,值为false表示从高地址向下分配
	phys_addr_t current_limit; //可分配内存的最大物理地址
	struct memblock_type memory; //内存类型(包括已分配的内存和未分配的内存)
	struct memblock_type reserved;//预留内型(已分配的内存)
#ifdef CONFIG_HAVE_MEMBLOCK_PHYS_MAP
	struct memblock_type physmem; //物理内存类型
#endif
};

物理内存类型和内存类型的区别: 内存类型是无里内存类型的子集,在引导内核时可以使用内核参数"mem=nn[KMG]",指定可用内存的大小,导致内核不能看见所有的内存;物理内在类型总是包含所有的内存范围,内存类型只包含内核参数"mem="指定的可用内在范围。

/* Definition of memblock flags. */
enum {
	MEMBLOCK_NONE		= 0x0,	/* No special request */
	MEMBLOCK_HOTPLUG	= 0x1,	/* hotpluggable region */
    //表示镜像的区域,将内存数据做两个复制,分配在住内存和镜像内存中
	MEMBLOCK_MIRROR		= 0x2,	/* mirrored region */
};

struct memblock_region {
	phys_addr_t base; //起始物理地址
	phys_addr_t size; //长度
	unsigned long flags; //成员flags标志
#ifdef CONFIG_HAVE_MEMBLOCK_NODE_MAP 
	int nid;   //节点编号
#endif
};

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;
};

ARM64内核初始化memblock分配器流程

在源文件mm/memblock.c 定义全局变量memblock, 把成员bottom_up初始化为false, 表示从高地址向下分配。

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

	.reserved.regions	= memblock_reserved_init_regions,
	.reserved.cnt		= 1,	/* empty dummy entry */
	.reserved.max		= INIT_MEMBLOCK_REGIONS,

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

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

ARM64内核初始化memblock分配器过程:

a. 解析设备树二进制文件中的节点/memory,把所有物理内存范围添加到memblock;

b. 在函数arm64_memblock_init 中初始化memblock。

void __init arm64_memblock_init(void)
{
	memblock_enforce_memory_limit(memory_limit);

	/*
	 * Register the kernel text, kernel data, initrd, and initial
	 * pagetables with memblock.
	 */
	memblock_reserve(__pa(_text), _end - _text);
#ifdef CONFIG_BLK_DEV_INITRD
	if (initrd_start)
		memblock_reserve(__virt_to_phys(initrd_start), initrd_end - initrd_start);
#endif

	early_init_fdt_scan_reserved_mem();

	/* 4GB maximum for 32-bit only capable devices */
	if (IS_ENABLED(CONFIG_ZONE_DMA))
		arm64_dma_phys_limit = max_zone_dma_phys();
	else
		arm64_dma_phys_limit = PHYS_MASK + 1;
	dma_contiguous_reserve(arm64_dma_phys_limit);

	memblock_allow_resize();
	memblock_dump_all();
}

Memblock分配器编程接口:

//插入一块可用的物理内存
//base 指向要添加的内存块的起始物理地址
// size 指向要添加内存块的大小
int __init_memblock memblock_add(phys_addr_t base, phys_addr_t size)
{
	//函数直接调用memblock_add_region()函数将内存区快添加到memblock.memory
	return memblock_add_region(base, size, MAX_NUMNODES, 0);
}

static int __init_memblock memblock_add_region(phys_addr_t base,
						phys_addr_t size,
						int nid, // 指向NUMA
						unsigned long flags) //指向新加入内存块对应的flags 
{
	struct memblock_type *type = &memblock.memory;

	memblock_dbg("memblock_add: [%#016llx-%#016llx] flags %#02lx %pF\n",
		     (unsigned long long)base,
		     (unsigned long long)base + size - 1,
		     flags, (void *)_RET_IP_);

	return memblock_add_range(type, base, size, nid, flags);
}

/**
 * memblock_add_range - add new memblock region
 * @type: memblock type to add new region into
 * @base: base address of the new region
 * @size: size of the new region
 * @nid: nid of the new region
 * @flags: flags of the new region
 *
 * Add new memblock region [@base,@base+@size) into @type.  The new region
 * is allowed to overlap with existing ones - overlaps don't affect already
 * existing regions.  @type is guaranteed to be minimal (all neighbouring
 * compatible regions are merged) after the addition.
 *
 * RETURNS:
 * 0 on success, -errno on failure.
 */
int __init_memblock memblock_add_range(struct memblock_type *type,
				phys_addr_t base, phys_addr_t size,
				int nid, unsigned long flags)
{
	bool insert = false;
	phys_addr_t obase = base;
	phys_addr_t end = base + memblock_cap_size(base, &size);
	int i, nr_new;

	if (!size)
		return 0;

	/* special case for empty array */
	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 accomodate the new area.  The second actually inserts them.
	 */
	base = obase;
	nr_new = 0;

	for (i = 0; i < type->cnt; i++) {
		struct memblock_region *rgn = &type->regions[i];
		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_MAP
			WARN_ON(nid != memblock_get_region_node(rgn));
#endif
			WARN_ON(flags != rgn->flags);
			nr_new++;
			if (insert)
				memblock_insert_region(type, i++, 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, i, base, end - base,
					       nid, flags);
	}

	/*
	 * If this was the first round, resize array and repeat for actual
	 * insertions; otherwise, merge and return.
	 */
	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;
	}
}

/**
 * memblock_insert_region - insert new memblock region
 * @type:	memblock type to insert into
 * @idx:	index for the insertion point
 * @base:	base address of the new region
 * @size:	size of the new region
 * @nid:	node id of the new region
 * @flags:	flags of the new region
 *
 * Insert new memblock region [@base,@base+@size) into @type at @idx.
 * @type must already have extra room to accomodate the new region.
 */
static void __init_memblock memblock_insert_region(struct memblock_type *type,
						   int idx, phys_addr_t base,
						   phys_addr_t size,
						   int nid, unsigned long flags)
{
	struct memblock_region *rgn = &type->regions[idx];

	BUG_ON(type->cnt >= type->max);
	memmove(rgn + 1, rgn, (type->cnt - idx) * sizeof(*rgn));
	rgn->base = base;
	rgn->size = size;
	rgn->flags = flags;
	memblock_set_region_node(rgn, nid);
	type->cnt++;
	type->total_size += size;
}
// 从可用物理内存区删除一块可用物理内存
// base指向需要删除物理内存的起始物理地址
// size 指向需要删除物理内存的大小
int __init_memblock memblock_remove(phys_addr_t base, phys_addr_t size)
{
	return memblock_remove_range(&memblock.memory, base, size);
}

//函数计算出删除物理内存的终止物理地址之后,直接调用下面此函数来删除指定的物理内存区域
// type 指明要从那一块内存区删除物理地址
// 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;
}
/* 从指定地址之前分配物理地址*/
phys_addr_t __init memblock_alloc(phys_addr_t size, phys_addr_t align)
{
	return memblock_alloc_base(size, align, MEMBLOCK_ALLOC_ACCESSIBLE);
}

//size 指明要分配物理内存大小
//align 对齐方式
//max_addr 指明最大可分配物理地址
phys_addr_t __init memblock_alloc_base(phys_addr_t size, phys_addr_t align, phys_addr_t max_addr)
{
	phys_addr_t alloc;
    //调用此函数获得一块可用物理内存区快,如果找到的物理内存区块的起始地址为0,就报错
	alloc = __memblock_alloc_base(size, align, max_addr);

	if (alloc == 0)
		panic("ERROR: Failed to allocate 0x%llx bytes below 0x%llx.\n",
		      (unsigned long long) size, (unsigned long long) 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);
}

static phys_addr_t __init memblock_alloc_base_nid(phys_addr_t size,
					phys_addr_t align, phys_addr_t max_addr,
					int nid, ulong 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, ulong flags)
{
	phys_addr_t found;

	if (!align)
		align = SMP_CACHE_BYTES;

	found = memblock_find_in_range_node(size, align, start, end, nid,
					    flags);
	if (found && !memblock_reserve(found, size)) {
		/*
		 * The min_count is set to 0 so that memblock allocations are
		 * never reported as leaks.
		 */
		kmemleak_alloc(__va(found), size, 0, 0);
		return found;
	}
	return 0;
}
//从预留内存区中删除一块预留的内存区块
//base 指向要删除预留内存的起始物理地址
// size 指向要删除预留内存的大小
int __init_memblock 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(__va(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;
}

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值