memblock子系统

Linux内核使用伙伴系统管理内存。在使用伙伴系统之前,内核会使用memblock管理内存(里面的都是物理地址)。在系统启动阶段使用memblock记录物理内存的使用情况。

 

内核启动以后,内存会被分开很多块:1 静态内存,eg代码段,数据段等,属于系统内存的一部分,不会参与内存分配。2 预留的保留内存。3 其余的为内核需要管理的内存(后面伙伴系统管理的内存??),动态内存

memblock子系统就是将以上内存按照功能划分为若干内存区。相应的memblock也会包含保留内存以及动态内存

struct memblock {
	bool bottom_up;  /* is bottom up direction? 表示分配器的分配方式,true表示从低地址向高地址分配,false则相反*/
	phys_addr_t current_limit;//来表示用来限制alloc的内存申请
	struct memblock_type memory;//对应动态内存
	struct memblock_type reserved;//对应保留内存
#ifdef CONFIG_HAVE_MEMBLOCK_PHYS_MAP
	struct memblock_type physmem;
#endif
};
struct memblock_type {
	unsigned long cnt;	/* number of regions表示当前状态(memory/reserved)的内存块可用数 */
	unsigned long max;	/* size of the allocated array */
	phys_addr_t total_size;	/* size of all regions 当前状态(memory/reserved)的空间大小,也就是内存总大小空间*/
	struct memblock_region *regions;/* 用于保存内存块信息的结构(包括基址、大小和标记等)*/
};
struct memblock_region {
	phys_addr_t base;/* 内存块对应的物理基地址 */
	phys_addr_t size;/* 内存块大小 */
	unsigned long flags;
#ifdef CONFIG_HAVE_MEMBLOCK_NODE_MAP
	int nid;
#endif
};

内核中memblock被初始化为了一个全局变量

static struct memblock_region memblock_memory_init_regions[INIT_MEMBLOCK_REGIONS] __initdata_memblock;
static struct memblock_region memblock_reserved_init_regions[INIT_MEMBLOCK_REGIONS] __initdata_memblock;
#ifdef CONFIG_HAVE_MEMBLOCK_PHYS_MAP
static struct memblock_region memblock_physmem_init_regions[INIT_PHYSMEM_REGIONS] __initdata_memblock;
#endif

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

memblock添加

early_init_dt_scan_nodes(会去解析choosen, root, memory三个节点)->early_init_dt_scan_memory(解析出memory节点的起始地址以及daxiao,物理地址,对应dts里面填写的起始地址和大小)->early_init_dt_add_memory_arch-->memblock_add->memblock_add_range

phys_offset是代表物理地址的起始地址。early_init_dt_add_memory_arch主要是对物理地址是否合法进行判断以及裁剪

void __init __weak early_init_dt_add_memory_arch(u64 base, u64 size)
{
	const u64 phys_offset = __pa(PAGE_OFFSET);
	base &= PAGE_MASK;
	size &= PAGE_MASK;
	printk(KERN_EMERG "\r\nxxxxxxx phys_offset %llx\n", phys_offset);
	if (sizeof(phys_addr_t) < sizeof(u64)) {
		if (base > ULONG_MAX) {
			pr_warning("Ignoring memory block 0x%llx - 0x%llx\n",
					base, base + size);
			return;
		}

		if (base + size > ULONG_MAX) {
			pr_warning("Ignoring memory range 0x%lx - 0x%llx\n",
					ULONG_MAX, base + size);
			size = ULONG_MAX - base;
		}
	}

	if (base + size < phys_offset) {
		pr_warning("Ignoring memory block 0x%llx - 0x%llx\n",
			   base, base + size);
		return;
	}
	if (base < phys_offset) {
		pr_warning("Ignoring memory range 0x%llx - 0x%llx\n",
			   base, phys_offset);
		size -= phys_offset - base;
		base = phys_offset;
	}
	memblock_add(base, size);
}

打印了一下phys_offset:0x60000000,确实也和dts里面是一致的。看来确实是物理内存的地址不一定都是从0开始的 。

 memblock.memory:这里是将memory节点的内存加入到memblock中的动态内存中

int __init_memblock memblock_add(phys_addr_t base, phys_addr_t size)
{
	return memblock_add_range(&memblock.memory, base, size,
				   MAX_NUMNODES, 0);
}

 

上面示意图就是新增region的情况:假设region[i]的范围是[rbase, rend].新加入的为[base, end]

如果大小按照上图所示,则会新增两个region[base, rbase]和[rend, end]

memblock_add_range:会执行两次(通过insert控制),第一次主要是查看需要新增几个元素或者说region(nr_new表示)。根据新加入的物理地址范围,判断是否新增region的数量。如果当前元素个数+新增数量(type->cnt + nr_new > type->max)超过了数组长度,则需要进行扩容。

当确定扩容之后或者数组长度足够后,就可以将新加入的地址范围[base, size]加入memblock

在循环第二次的时候,base会被重写赋值。又重新开始,区别是此时会执行插入操作memblock_insert_region(该函数只是简单的将第i到最后一个元素,统统往后移动一个位置)

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;//old base??
	phys_addr_t end = base + memblock_cap_size(base, &size);
	int i, nr_new;
	if (&memblock.memory == type)
	{
		printk(KERN_EMERG "\r\nmemory add, base: %lx, size: %lx\r\n", base, size);
	}
	else if (&memblock.reserved == type)
	{
		printk(KERN_EMERG "\r\nreserved add, base: %lx, size: %lx\r\n", base, size);
	}
	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;

		/* 我感觉这里是想找一个起始地址是小于base的region */
		if (rbase >= end)
			break;
		if (rend <= base)
			continue;
		/*
		 * @rgn overlaps.  If it separates the lower part of new
		 * area, insert that portion.
		 */
		/*
		区域有重叠,即新插入的区域[base, end]必定和当前region[rbase, rend]有交集
		但是看代码感觉只有这种情形出现(不知道为什么)
		base <= rbase 
		end和rend大小关系不定
		*/
		if (rbase > base) {
			nr_new++;
			if (insert)//首次执行insert=fasle,不会执行这里
				memblock_insert_region(type, i++, base,
						       rbase - base/* 从这里看感觉rbase是大于base的 */, nid,
						       flags);
		}
		/* area below @rend is dealt with, forget about it */
		/*
		由于区域有重叠,目前存在两种情况:
		1 当前region已经完全包含新加的区域 base <= rbase  <= end <= rend
		2 部分重叠 base <= rbase <= rend <= end
		因此更新,新的起始地址为rend和end的较小值

		新插入的区域为[min(rend, end), end]
		如果是情况1,那么该范围则不成立,不过后面会检查该情况
		*/
		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;
	}
}

从打印看,设备树里面的memory节点里面定义的物理内存全部都被加入到了动态内存部分

而 。保留内存同样也是从memory节点里面划分出来了一部分,当做保留内存

 

 

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);
	/* 将rgn复制到rgn+1,即将第i + 1到type->cnt的region往后挪一个位置 */
	memmove(rgn + 1, rgn, (type->cnt - idx) * sizeof(*rgn));
	/*
	原来region[i]的范围是[rbase, rend] 
	现在变为了region[i + 1]的范围是[rbase, rend]
	而region[i]的范围是[base, rbase]
	*/
	rgn->base = base;
	rgn->size = size;
	rgn->flags = flags;
	memblock_set_region_node(rgn, nid);
	type->cnt++;
	type->total_size += size;
}

reserved-memory节点保留内存,以下为某dts中保留内存节点的一段。由dts可见它可以定义多个保留内存块属性,每个属性包含一块要保留的内存,在初始化reserved region时,可以通过解析dtb中该节点的各个属性,然后填充reserved region。在这个例子中,保留内存属性还包含了no-map标志,它表示这段内存不要放入线性映射区,因此需要从memory region中移除它们,例如这种方式

no-map”属性决定memory region移除内存区。内核不会给”no-map”属性的内存区建立内存映射,即该内存区不在动态内存管理范围

        reserved-memory {
                #address-cells = <1>;
                #size-cells = <1>;
                ranges;

                nss@40000000 {
                        reg = <0x40000000 0x1000000>;
                        no-map;
                };

                smem@41000000 {
                        reg = <0x41000000 0x200000>;
                        no-map;
                };
        };

需要被管理的内存被加入到了memblock子系统之后,就开始真正的内存管理,页表映射、zone初始化等

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值