linux内存管理-2-初期memblock分配器

内核在启动时通过E820机制获得到可⽤的内存地址范围后,还需要将这些内存都管理起来,以应对后⾯系统运⾏时 的各种功能的内存申请。内存分配器包括两种。刚启动时采⽤是初期分配器。这种内存分配器仅仅只为了满⾜系统 启动时间内对内存⻚的简单管理,管理粒度较粗。另外⼀种是在系统起来后正常运⾏时采⽤的复杂⼀些但能⾼效管 理4KB粒度⻚⾯的伙伴系统,是运⾏时的主要物理⻚内存分配器。

在初期分配器中,在Linux 的早期版本采⽤的是 bootmem。但在 2010 年之后,就慢慢替换成了 memblock 内存 分配器。关于这个替换参⻅这个历史 commit https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/ commit/mm/memblock.c?id=95f72d1ed41a66f1c1c29c24d479de81a0bea36f。本书中我们只介绍较新的 memblock分配器。

1.2 memblock内存分配器的创建

内核在通过E820机制检测到可⽤的内存地址范围后,调⽤e820__memory_setup来把检测结果保存到了把检测结 果保存到了e820_table全局数据结构中。紧接着下⼀步就是调⽤e820__memblock_setup创建memblock内存分配器。

file:// arch/x86/kernel/setup.c
void __init setup_arch(char **cmdline_p) {
	...
	// 保存物理内存检测结果
	e820__memory_setup();
	...
	// membloc内存分配器初始化
	e820__memblock_setup();
	...
}

在看创建memblock之前我们先来看看这种内存分配器是⻓什么样⼦的。memblock的实现⾮常简单,就是按照检 测到的内存地址范围是 usable还是reserved分成两个对象,然后分别⽤memblock_region数组给存起来。
在这里插入图片描述

memblock分配器定义相关的源码位于mm/memblock.c⽂件下。

// file:include/linux/memblock.h
/**
 * struct memblock_region - represents a memory region
 * @base: base address of the region
 * @size: size of the region
 * @flags: memory region attributes
 * @nid: NUMA node id
 */
struct memblock_region {
	phys_addr_t base;
	phys_addr_t size;
	enum memblock_flags flags;
#ifdef CONFIG_NUMA
	int nid;
#endif
};

// file:include/linux/memblock.h
/**
 * struct memblock_type - collection of memory regions of certain type
 * @cnt: number of regions
 * @max: size of the allocated array
 * @total_size: size of all regions
 * @regions: array of regions
 * @name: the memory type symbolic name
 */
struct memblock_type {
	unsigned long cnt;
	unsigned long max;
	phys_addr_t total_size;
	struct memblock_region *regions;
	char *name;
};

// file:include/linux/memblock.h
/**
 * struct memblock - memblock allocator metadata
 * @bottom_up: is bottom up direction?
 * @current_limit: physical address of the current allocation limit
 * @memory: usable memory regions
 * @reserved: reserved memory regions
 */
struct memblock {
	bool bottom_up;  /* is bottom up direction? */
	phys_addr_t current_limit;
	struct memblock_type memory;
	struct memblock_type reserved;
};

// file:mm/memblock.c
struct memblock memblock __initdata_memblock = {
	.memory.regions		= memblock_memory_init_regions,
	.memory.cnt		= 1,	/* empty dummy entry */
	.memory.max		= INIT_MEMBLOCK_MEMORY_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,
};

static struct memblock_region memblock_memory_init_regions[INIT_MEMBLOCK_MEMORY_REGIONS] __initdata_memblock;
static struct memblock_region memblock_reserved_init_regions[INIT_MEMBLOCK_RESERVED_REGIONS] __initdata_memblock;


#define INIT_MEMBLOCK_REGIONS			128
#define INIT_PHYSMEM_REGIONS			4

#ifndef INIT_MEMBLOCK_RESERVED_REGIONS
# define INIT_MEMBLOCK_RESERVED_REGIONS		INIT_MEMBLOCK_REGIONS
#endif

#ifndef INIT_MEMBLOCK_MEMORY_REGIONS
#define INIT_MEMBLOCK_MEMORY_REGIONS		INIT_MEMBLOCK_REGIONS
#endif

e820__memblock_setup会根据e820_table来对 memblock 内存分配器进⾏创建。

void __init e820__memblock_setup(void)
{
	int i;
	u64 end;

	/*
	 * The bootstrap memblock region count maximum is 128 entries
	 * (INIT_MEMBLOCK_REGIONS), but EFI might pass us more E820 entries
	 * than that - so allow memblock resizing.
	 *
	 * This is safe, because this call happens pretty late during x86 setup,
	 * so we know about reserved memory regions already. (This is important
	 * so that memblock resizing does no stomp over reserved areas.)
	 */
	memblock_allow_resize();

	for (i = 0; i < e820_table->nr_entries; i++) {
		struct e820_entry *entry = &e820_table->entries[i];

		end = entry->addr + entry->size;
		if (end != (resource_size_t)end)
			continue;

		if (entry->type == E820_TYPE_SOFT_RESERVED)
			memblock_reserve(entry->addr, entry->size);

		if (entry->type != E820_TYPE_RAM && entry->type != E820_TYPE_RESERVED_KERN)
			continue;

		memblock_add(entry->addr, entry->size);
	}

	/* Throw away partial pages: */
	memblock_trim_memory(PAGE_SIZE);

	memblock_dump_all();
}

创建过程是遍历 e820 table 中的每⼀段内存区域。判断如果是预留内存就调⽤ memblock_reserve 添加到 reserved 成员中,也就是预留内存列表。添加过程是会修改 reserved 中的区域数量 cnt,然后在设置 regions 中 的⼀个元素。如果是可⽤内存就调⽤ memblock_add 添加到 memory 成员中,也就是可⽤内存列表,添加过程同 上。

在 memblock 创建完成后,紧接着还调⽤ memblock_dump_all() 进⾏了⼀次打印输出。这个输出信息对于我们观 察memblock的创建过程⾮常的有帮助。Linux内核会把启动时的各种⽇志信息记录下来,后⾯可以使⽤dmsg命令 来查看。不过memblock_dump_all输出的信息要需要修改Linux启动参数,添加memblock=debug并重启才可以。

我的修改⽅式是编辑/boot/grub/grub.cfg⽂件找到启动参数⾏,在最后⾯添加“memblock=debug”(不同的发⾏ 版可能修改⽅式会有⼀些出⼊)。

在这里插入图片描述
重启后通过查看 /proc/cmdline输出中是否包含了“memblock=debug”,来确认开启⽣效。
在这里插入图片描述
然后就可以通过dmseg可以看到Linux启动时的memblock内存分配器输出的⽇志信息了。
在这里插入图片描述

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值