linux内存管理(五)-引导内存分配器


linux内存三大分配器:引导内存分配器,伙伴分配器,slab分配器

一、引导内存分配器概述

1.引导内存分配器的作用
因为内核里面有很多内存结构体,不可能在静态编译阶段就静态初始化所有的这些内存结构体。另外,在系统启动过程中,系统启动后的物理内存分配器本身也需要初始化,如伙伴分配器,那么伙伴分配器如何获取内存来初始化自己呢 ?为了达到这个目标,我们先实现一个满足要求的但是可能效率不高的笨家伙,引导内存分配器。用它来负责系统初始化初期的内存管理, 最重要的, 用它来初始化我们内存的数据结构, 直到我们真正的内存管理器被初始化完成并能投入使用, 我们将旧的内存管理器丢掉。
2.引导内存分配器的原理
在Linux内核中使用struct bootmem_data来描述一个引导内存分配,其节点结构下的一个成员,也就是说每一个节点都有一个引导内存分配。
引导内存分配使用struct bootmem_data结构中的node_bootmem_map这个bitmap来呈现memory的状态,一个bit代表一个物理页框,也就是用struct page,如果一个bit为1,表示该page已经被分配了,如果bit是0,则表示该page未被分配。为了能够满足比一个page还小的内存块的分配,引导内存分配器会使用last_pos来记住上次分配所使用的PFN以及上次分配所使用的page内的偏移:last_offset,下次分配的时候结合last_pos和last_offset将细小的内存块分配尽量集中在相同的page中。
3引导内存分配器的缺点
尽管引导内存分配器不会造成严重的内存碎片,但是每次分配过程需要线性扫描搜索内存来满足当前的分配。因为是检查bitmap,所以代价比较昂贵,尤其是最先适配(first fit)算法倾向将小块内存放置在物理内存开头,但是这些内存区域在分配大块内存时,也需要扫描,所以该过程十分浪费。所以早期内存分配器在系统启动后就被弃用的原因。
4.bootmem和memblock的比较
但是bootmem也有很多问题. 最明显的就是外碎片的问题, 因此内核维护了memblock内存分配器, 同时用memblock实现了一份bootmem相同的兼容API, 即nobootmem, Memblock以前被定义为Logical Memory Block( 逻辑内存块),但根据Yinghai Lu的补丁, 它被重命名为memblock. 并最终替代bootmem成为初始化阶段的内存管理器。
bootmem是通过位图来管理,位图存在地地址段, 而memblock是在高地址管理内存, 维护两个链表, 即memory和reserved。
memory链表维护系统的内存信息(在初始化阶段通过bios获取的), 对于任何内存分配, 先去查找memory链表, 然后在reserve链表上记录(新增一个节点,或者合并)
bootmem和memblock都是就近查找可用的内存, bootmem是从低到高找, memblock是从高往低找。
在boot传递给kernel memory bank相关信息后,kernel这边会以memblcok的方式保存这些信息,当伙伴系统没有起来之前,在内核中也是要有一套机制来管理memory的申请和释放。linux内核可以通过宏定义选择nobootmem 或者bootmem 来在伙伴起来之前管理内存。这两种机制对提供的API是一致的,因此对用户是透明的。
早期使用的引导内存分配器是 bootmem,目前正在使用 memblock 取代 bootmem。如果开启配置宏 CONFIG_NO_BOOTMEM,memblock 就会取代 bootmem。为了保证兼容性,bootmem 和 memblock 提供了相同的接口。所以下面我们简单看看bootmem,再详细看看memblock 。

二、bootmem

1.结构解析

bootmem结构体位于文件include/linux/bootmem.h:

typedef struct bootmem_data {
	unsigned long node_min_pfn;//节点内存的起始物理页号
	unsigned long node_low_pfn;//节点内存的结束物理页号
	void *node_bootmem_map;//位图指针,每个物理页对应一位,如果物理页被分配则对应位置一。
	unsigned long last_end_off;//最后一次分配的页面内的偏移量(字节);如果为0,则使用的页面已满
	unsigned long hint_idx;//最后一次分配的物理页,下次优先考虑从这个物理页分配
	struct list_head list;//按内存地址排序链表头
} bootmem_data_t;

bootmem接口函数:
1)bootmem分配内存函数:alloc_bootmem
2)bootmem释放内存函数:free_bootmem

#define alloc_bootmem(x) \
	__alloc_bootmem(x, SMP_CACHE_BYTES, BOOTMEM_LOW_LIMIT)

void __init free_bootmem(unsigned long physaddr, unsigned long size)
{
	unsigned long start, end;

	kmemleak_free_part_phys(physaddr, size);//释放映射的内存

	start = PFN_UP(physaddr);//查找到起始位置的物理页
	end = PFN_DOWN(physaddr + size);//查找到结束为止的物理页

	mark_bootmem(start, end, 0, 0);//把释放的物理页对应的位清零
}

ARM64 架构的内核已经不使用 bootmem 分配器,所以就简单的说到这里。

三、memblock

1.结构解析

memblock结构体位于include/linux/memblock.h文件:

struct memblock {
	bool bottom_up;//表示内存分配方式,真:从低地址向上分配,假:从高地址向下分配
	phys_addr_t current_limit;//可分配内存的最大物理地址
	struct memblock_type memory;//内存类型(包括已分配和未分配的)
	struct memblock_type reserved;//预留内存类型(预留起来不可用,例子:设备树)
#ifdef CONFIG_HAVE_MEMBLOCK_PHYS_MAP
	struct memblock_type physmem;//物理内存类型
#endif
};

memory 是内存类型(包括已分配的内存和未分配的内存),reserved 是预留类型(已分配的内存),physmem 是物理内存类型。物理内存类型和内存类型的区别是:内存类型是物理内存类型的子集,在引导内核时可以使用内核参数“mem=nn[KMG]”,指定可用内存的大小,导致内核不能看见所有内存;物理内存类型总是包含所有内存范围,内存类型只包含内核参数“mem=”指定的可用内存范围。

struct memblock_type {
	unsigned long cnt;//区域数量
	unsigned long max;//分配区域的大小
	phys_addr_t total_size;//所有区域的大小
	struct memblock_region *regions;//区域数组指向区域数组
	char *name;//内存类型符号名
};

struct memblock_region {
	phys_addr_t base;//起始物理地址
	phys_addr_t size;//长度
	enum memblock_flags flags;//内存区域标志属性
#ifdef CONFIG_HAVE_MEMBLOCK_NODE_MAP
	int nid;//节点编号
#endif
};
//内存区域标志属性定义
enum memblock_flags {
	MEMBLOCK_NONE		= 0x0,//表示没有特殊要求区域
	MEMBLOCK_HOTPLUG	= 0x1,//表示可以热插拔的区域	
	MEMBLOCK_MIRROR		= 0x2,//表示镜像的区域,将内存数据做两份复制,分配放在主内存和镜像内存中	
	MEMBLOCK_NOMAP		= 0x4,//表示不添加到内核直接映射区域,即线性映射区
};

memblock体系的结构:
在这里插入图片描述

2.memblock初始化

memblock启动流程:
1)解析设备树中的/memory,把所有物理内存添加到memblock
2)在memblock_init中初始化memblock
linux启动从init/main.c文件的start_kernel函数开始,然后从文件setup_arch(arch/arm64/kernel/setup.c文件中)函数检测处理器类型,初始化处理器和内存,其中的arm64_memblock_init(arch/arm64/mm/init.c文件中)函数就是arm64架构的memblock初始化流程。

void __init arm64_memblock_init(void)
{
	const s64 linear_region_size = -(s64)PAGE_OFFSET;

	/* Handle linux,usable-memory-range property */
	//解析设备树文件的内存节点
	fdt_enforce_memory_region();

	/* Remove memory above our supported physical address size */
	//删除超出我们支持的物理地址大小的内存
	memblock_remove(1ULL << PHYS_MASK_SHIFT, ULLONG_MAX);

	/*
	 * Ensure that the linear region takes up exactly half of the kernel
	 * virtual address space. This way, we can distinguish a linear address
	 * from a kernel/module/vmalloc address by testing a single bit.
	 */
	BUILD_BUG_ON(linear_region_size != BIT(VA_BITS - 1));

	/*
	 * Select a suitable value for the base of physical memory.
	 */
	//全局变量memstart_addr记录了内存的起始物理地址
	memstart_addr = round_down(memblock_start_of_DRAM(),
				   ARM64_MEMSTART_ALIGN);

	/*
	 * Remove the memory that we will not be able to cover with the
	 * linear mapping. Take care not to clip the kernel which may be
	 * high in memory.
	 */
	//把线性映射区无法覆盖的物理内存范围从memblock中删除
	memblock_remove(max_t(u64, memstart_addr + linear_region_size,
			__pa_symbol(_end)), ULLONG_MAX);
	if (memstart_addr + linear_region_size < memblock_end_of_DRAM()) {
		/* ensure that memstart_addr remains sufficiently aligned */
		memstart_addr = round_up(memblock_end_of_DRAM() - linear_region_size,
					 ARM64_MEMSTART_ALIGN);
		memblock_remove(0, memstart_addr);
	}

	/*
	 * Apply the memory limit if it was set. Since the kernel may be loaded
	 * high up in memory, add back the kernel region that must be accessible
	 * via the linear mapping.
	 */
	//如果设置了内存限制,要根据限制使用内存
	if (memory_limit != PHYS_ADDR_MAX) {
		memblock_mem_limit_remove_map(memory_limit);//把超出限制的内存移除
		memblock_add(__pa_symbol(_text), (u64)(_end - _text));//添加可以使用的内存
	}

	if (IS_ENABLED(CONFIG_BLK_DEV_INITRD) && initrd_start) {
		/*
		 * Add back the memory we just removed if it results in the
		 * initrd to become inaccessible via the linear mapping.
		 * Otherwise, this is a no-op
		 */
		u64 base = initrd_start & PAGE_MASK;
		u64 size = PAGE_ALIGN(initrd_end) - base;

		/*
		 * We can only add back the initrd memory if we don't end up
		 * with more memory than we can address via the linear mapping.
		 * It is up to the bootloader to position the kernel and the
		 * initrd reasonably close to each other (i.e., within 32 GB of
		 * each other) so that all granule/#levels combinations can
		 * always access both.
		 */
		if (WARN(base < memblock_start_of_DRAM() ||
			 base + size > memblock_start_of_DRAM() +
				       linear_region_size,
			"initrd not fully accessible via the linear mapping -- please check your bootloader ...\n")) {
			initrd_start = 0;
		} else {
			memblock_remove(base, size); /* clear MEMBLOCK_ flags */
			memblock_add(base, size);
			memblock_reserve(base, size);
		}
	}

	if (IS_ENABLED(CONFIG_RANDOMIZE_BASE)) {
		extern u16 memstart_offset_seed;
		u64 range = linear_region_size -
			    (memblock_end_of_DRAM() - memblock_start_of_DRAM());

		/*
		 * If the size of the linear region exceeds, by a sufficient
		 * margin, the size of the region that the available physical
		 * memory spans, randomize the linear region as well.
		 */
		if (memstart_offset_seed > 0 && range >= ARM64_MEMSTART_ALIGN) {
			range /= ARM64_MEMSTART_ALIGN;
			memstart_addr -= ARM64_MEMSTART_ALIGN *
					 ((range * memstart_offset_seed) >> 16);
		}
	}

	/*
	 * Register the kernel text, kernel data, initrd, and initial
	 * pagetables with memblock.
	 */
	//把内核镜像占用的内存添加到memblock的预留区中,表示预留了不再分配出去
	memblock_reserve(__pa_symbol(_text), _end - _text);
#ifdef CONFIG_BLK_DEV_INITRD
	if (initrd_start) {
		memblock_reserve(initrd_start, initrd_end - initrd_start);

		/* the generic initrd code expects virtual addresses */
		initrd_start = __phys_to_virt(initrd_start);
		initrd_end = __phys_to_virt(initrd_end);
	}
#endif

	//扫描设备树中的保留内存区域并添加到memblock的预留区域中
	early_init_fdt_scan_reserved_mem();

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

	reserve_crashkernel();

	reserve_elfcorehdr();

	high_memory = __va(memblock_end_of_DRAM() - 1) + 1;

	dma_contiguous_reserve(arm64_dma_phys_limit);

	memblock_allow_resize();
}

在arm64_memblock_init函数中主要做了一下几个工作:

  1. fdt_enforce_memory_region:解析设备树文件的内存节点
  2. memblock_remove:删除超出我们支持的物理地址大小的内存
  3. memblock_start_of_DRAM:寻找内存的起始物理地址
  4. memblock_remove:把线性映射区无法覆盖的物理内存范围从memblock中删除
  5. memblock_reserve:把内核镜像占用的内存添加到memblock的预留区中,表示预留了不再分配出去
  6. early_init_fdt_scan_reserved_mem:扫描设备树中的保留内存区域并添加到memblock的预留区域中
2.1. fdt_enforce_memory_region:解析设备树文件的内存节点
static void __init fdt_enforce_memory_region(void)
{
	struct memblock_region reg = {
		.size = 0,
	};
	//扫描平坦内存模型的设备树
	of_scan_flat_dt(early_init_dt_scan_usablemem, &reg);

	if (reg.size)
		memblock_cap_memory_range(reg.base, reg.size);
}

int __init of_scan_flat_dt(int (*it)(unsigned long node,
				     const char *uname, int depth,
				     void *data),
			   void *data)
{
	const void *blob = initial_boot_params;
	const char *pathp;
	int offset, rc = 0, depth = -1;

	if (!blob)
		return 0;

	for (offset = fdt_next_node(blob, -1, &depth);
	     offset >= 0 && depth >= 0 && !rc;
	     offset = fdt_next_node(blob, offset, &depth)) {
		
		pathp = fdt_get_name(blob, offset, NULL);///获取/memory节点的路劲
		if (*pathp == '/')
			pathp = kbasename(pathp);
		rc = it(offset, pathp, depth, data);
	}
	return rc;
}

void __init memblock_cap_memory_range(phys_addr_t base, phys_addr_t size)
{
	int start_rgn, end_rgn;
	int i, ret;

	if (!size)
		return;
	
	//在memory类型的所有内存中隔离出[base,base+size]的内存,
	//起始地址和结束地址放到start_rgnh和end_rgn,返回区间个数
	ret = memblock_isolate_range(&memblock.memory, base, size,
						&start_rgn, &end_rgn);
	if (ret)
		return;

	//遍历[end_rgn,memblock.memory.cnt],移除所有MAP区域
	for (i = memblock.memory.cnt - 1; i >= end_rgn; i--)
		if (!memblock_is_nomap(&memblock.memory.regions[i]))
			memblock_remove_region(&memblock.memory, i);//把i从memory中移除
			
	//遍历[0,start_rgn],移除所有MAP区域
	for (i = start_rgn - 1; i >= 0; i--)
		if (!memblock_is_nomap(&memblock.memory.regions[i]))
			memblock_remove_region(&memblock.memory, i);//把i从memory中移除

	/* truncate the reserved regions */
	memblock_remove_range(&memblock.reserved, 0, base);
	memblock_remove_range(&memblock.reserved,
			base + size, PHYS_ADDR_MAX);
}

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;

	//在memory类型的所有内存中隔离出[base,base+size]的内存,
	//起始地址和结束地址放到start_rgnh和end_rgn,返回区间个数
	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;
}

memblock_isolate_range实际上就是通过遍历memblock_type的所有memblock_region区间,将请求区间(base,size)与memblock_type重叠的memblock_region区间分离出来,如果请求区间跨越两个memblock_region区间的边界,则会创建新的memblock_region区间并插入。最后通过start_rgn 和end_rgn记录分离区间的索引,以供后续操作,当然此时的区间与原有区间可能已经不一样了,它只代表重叠的部分。后面的memblock_remove_region是把memblock_isolate_range分离出来的内存移除掉。

2.2. memblock_remove:删除超出我们支持的物理地址大小的内存
int __init_memblock memblock_remove(phys_addr_t base, phys_addr_t size)
{
	phys_addr_t end = base + size - 1;

	memblock_dbg("memblock_remove: [%pa-%pa] %pS\n",
		     &base, &end, (void *)_RET_IP_);

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

memblock_remove_range:

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;

	//在memory类型的所有内存中隔离出[base,base+size]的内存,
	//起始地址和结束地址放到start_rgnh和end_rgn,返回区间个数
	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;
}

memblock_remove_range函数上面已经有详细说明了,就不在多说了。

2.3. memblock_start_of_DRAM:寻找内存的起始物理地址
/* lowest address */
phys_addr_t __init_memblock memblock_start_of_DRAM(void)
{
	return memblock.memory.regions[0].base;
}

这里仅仅是把内存类型的内存的第0个regions的起始页返回而已。

2.4. memblock_remove:把线性映射区无法覆盖的物理内存范围从memblock中删除

memblock_remove上面已经解析过了,这里不再细说。

2.5. memblock_reserve:把内核镜像占用的内存添加到memblock的预留区中,表示预留了不再分配出去
int __init_memblock memblock_reserve(phys_addr_t base, phys_addr_t size)
{
	phys_addr_t end = base + size - 1;

	memblock_dbg("memblock_reserve: [%pa-%pa] %pF\n",
		     &base, &end, (void *)_RET_IP_);

	return memblock_add_range(&memblock.reserved, base, size, MAX_NUMNODES, 0);
}

int __init_memblock memblock_add_range(struct memblock_type *type,
				phys_addr_t base, phys_addr_t size,
				int nid, enum memblock_flags flags)
{
	bool insert = false;
	phys_addr_t obase = base;
	phys_addr_t end = base + memblock_cap_size(base, &size);
	int idx, nr_new;
	struct memblock_region *rgn;

	if (!size)
		return 0;

	
	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 accommodate the new area.  The second actually inserts them.
	 */
	base = obase;
	nr_new = 0;

	//遍历type类型的所有内存块,与新的内存块比较
	for_each_memblock_type(idx, type, rgn) {
		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)
				//添加内存区域,也就是填充struct memblock_region而已
				memblock_insert_region(type, idx++, 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, idx, base, end - base,
					       nid, flags);
	}
	
	//如果需要加入的内存块个数为0则返回,不需要第二次遍历执行加入操作
	if (!nr_new)
		return 0;

	/*
	 * If this was the first round, resize array and repeat for actual
	 * insertions; otherwise, merge and return.
	 */
	//第一次会进入,判断内存区域块是否达到上限,是则退出,否则回到repeat
	//因为insert参数原因,第一次没有真正插入,第二次才会真正的插入
	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_reserve其实是通过memblock_add_range函数,把内存加入到reserved类型中而已。memblock_add_range挺复杂的,他首先记录了需要添加的内存起始和结束地址,然后判断一下该type的内存是否为空,如果是,则需要初始化一下第一个regoin,然后遍历type类型的所有内存块,与新的内存块比较,慢慢的把新的内存添加进来,期间需要注意type类型里面的内存和新添加的内存可能会有重叠部分,需要剔除该重叠部分,然后将其余非重叠部分添加进去。最后还要检查是第一次添加还是第二次添加,如果是第一次添加则需要判断内存区域块是否足够,不够则调用memblock_double_array成倍的添加新的region空间,第二次添加说说明,region空间肯定够了,则调用memblock_merge_regions合并相邻且没有缝隙的内存区域。

2.6. early_init_fdt_scan_reserved_mem:扫描设备树中的保留内存区域并添加到memblock的预留区域中

这里是从设备树二进制文件中的内存保留区域(memory reserve map,对应设备树源文件的字段“/memreserve/”)和节点“/reserved-memory”读取保留的物理内存范围,添加到 memblock.reserved 中。

void __init early_init_fdt_scan_reserved_mem(void)
{
	int n;
	u64 base, size;

	if (!initial_boot_params)
		return;

	/* Process header /memreserve/ fields */
	//遍历memreserve节点,
	for (n = 0; ; n++) {
		//从initial_boot_params,也就是memreserve节点中找到设备树记录的内存
		fdt_get_mem_rsv(initial_boot_params, n, &base, &size);
		if (!size)
			break;
		//把[base,base+size]加入到memblock的预留区域
		early_init_dt_reserve_memory_arch(base, size, 0);
	}

	of_scan_flat_dt(__fdt_scan_reserved_mem, NULL);//扫描平坦内存模型的设备树
	fdt_init_reserved_mem();//分配和初始化所有保存的预留内存区域
}

3.memblock添加内存区域函数

memblock添加内存区域函数是memblock_add:

int __init_memblock memblock_add(phys_addr_t base, phys_addr_t size)
{
	phys_addr_t end = base + size - 1;

	memblock_dbg("memblock_add: [%pa-%pa] %pF\n",
		     &base, &end, (void *)_RET_IP_);
	//直接调用memblock_add_range将内存区块添加到memblock.memory进行管理
	return memblock_add_range(&memblock.memory, base, size, MAX_NUMNODES, 0);
}

memblock_add_range才是调用memblock_add_range将内存区块添加到memblock.memory进行管理的主要函数:

int __init_memblock memblock_add_range(struct memblock_type *type,
				phys_addr_t base, phys_addr_t size,
				int nid, enum memblock_flags flags)
{
	bool insert = false;
	//保存好需要添加的内存为[obase,end]
	phys_addr_t obase = base;
	phys_addr_t end = base + memblock_cap_size(base, &size);//保证end 不会溢出
	int idx, nr_new;
	struct memblock_region *rgn;

	if (!size)
		return 0;

	//如果regions[0].size为0,说明该type没有regions,需要初始化一下regions
	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 accommodate the new area.  The second actually inserts them.
	 */
	base = obase;
	nr_new = 0;

	//遍历type类型的所有内存块,与新的内存块比较
	for_each_memblock_type(idx, type, rgn) {
		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)
				//添加内存区域,也就是填充struct memblock_region而已
				memblock_insert_region(type, idx++, 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, idx, base, end - base,
					       nid, flags);
	}
	
	//如果需要加入的内存块个数为0则返回,不需要第二次遍历执行加入操作
	if (!nr_new)
		return 0;

	/*
	 * If this was the first round, resize array and repeat for actual
	 * insertions; otherwise, merge and return.
	 */
	//因为insert参数原因,第一次没有真正插入,第二次才会真正的插入
		//第一次会进入,判断内存区域块是否达到上限,
		if (!insert) {//第一次会进入
		while (type->cnt + nr_new > type->max)//,判断内存区域块是否达到上限
			if (memblock_double_array(type, obase, size) < 0)//达到上限则需要成倍的添加新的region空间
				return -ENOMEM;
		insert = true;
		goto repeat;
	} else {//第二次进入,region空间肯定够了
		memblock_merge_regions(type);//合并相邻且没有缝隙的内存区域
		return 0;
	}
}

memblock_add_range挺复杂的,他首先记录了需要添加的内存起始和结束地址,然后判断一下该type的内存是否为空,如果是,则需要初始化一下第一个regoin,然后遍历type类型的所有内存块,与新的内存块比较,慢慢的把新的内存添加进来,期间需要注意type类型里面的内存和新添加的内存可能会有重叠部分,需要剔除该重叠部分,然后将其余非重叠部分添加进去。最后还要检查是第一次添加还是第二次添加,如果是第一次添加则需要判断内存区域块是否足够,不够则调用memblock_double_array成倍的添加新的region空间,第二次添加说说明,region空间肯定够了,则调用memblock_merge_regions合并相邻且没有缝隙的内存区域。

4.1.memblock_insert_region

我们在看看memblock_insert_region是怎么把内存区间插入到region的:

static void __init_memblock memblock_insert_region(struct memblock_type *type,
						   int idx, phys_addr_t base,
						   phys_addr_t size,
						   int nid,
						   enum memblock_flags flags)
{
	struct memblock_region *rgn = &type->regions[idx];

	BUG_ON(type->cnt >= type->max);
	//memmove与memcpy功能一致,还能处理source指向的空间与destination指向的空间重叠问题
	memmove(rgn + 1, rgn, (type->cnt - idx) * sizeof(*rgn));//通过memmove把rgn往后面挪移一个位置
	rgn->base = base;
	rgn->size = size;
	rgn->flags = flags;
	memblock_set_region_node(rgn, nid);
	type->cnt++;
	type->total_size += size;
}

memblock_insert_region也就是通过memmove把type类型的memblock_region 往后面挪动一位,空出一个memblock_region,然后填写region的base、size、flage、nid信息,最后把type的cnt和total_size信息也更新一下,就这样子,我们就把一块内存添加到memblock中对用的tpye类型了。接下来,我么再看看之后的memblock_merge_regions是怎么合并memblock中对用的tpye类型的。

4.2.memblock_merge_regions
static void __init_memblock memblock_merge_regions(struct memblock_type *type)
{
	int i = 0;

	/* cnt never goes below 1 */
	//遍历type类型的所有region
	while (i < type->cnt - 1) {
		struct memblock_region *this = &type->regions[i];//记录当前region
		struct memblock_region *next = &type->regions[i + 1];//记录下一个region
		
		if (this->base + this->size != next->base ||	//如果当前region的末尾和下一个region的头不一样
		    memblock_get_region_node(this) !=
		    memblock_get_region_node(next) ||			//或者如果当前region和下一个region的node ID不一样
		    this->flags != next->flags) {		//或者如果当前region和下一个region的不一样
			BUG_ON(this->base + this->size > next->base);
			i++;			//说明当前region和下一个region不能合并,跳过
			continue;
		}
		//来到这里,说明当前region和下一个region可以合并
		this->size += next->size;//修改当前region的大小为当前region和下一个region的总大小
		/* move forward from next + 1, index of which is i + 2 */
		memmove(next, next + 1, (type->cnt - (i + 2)) * sizeof(*next));//把next + 1的region往前挪一个位置,把next覆盖掉
		type->cnt--;//type类型的cnt少了一个
	}
}

memblock_merge_regions遍历type类型的所有region,判断当前region和下一个region是否可以合并,如果不可以,则遍历一下region,如果可以,则修改当前region,然后后面的region往前挪一个位置,把下一个region覆盖掉,最后type的cnt信息更新一下。

4.memblock删除内存区域

memblock删除内存区域函数是memblock_remove:

int __init_memblock memblock_remove(phys_addr_t base, phys_addr_t size)
{
	phys_addr_t end = base + size - 1;

	memblock_dbg("memblock_remove: [%pa-%pa] %pS\n",
		     &base, &end, (void *)_RET_IP_);

	return memblock_remove_range(&memblock.memory, 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;

	//在memory类型的所有内存中隔离出[base,base+size]的内存,
	//起始地址和结束地址放到start_rgnh和end_rgn,返回区间个数
	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;
}

memblock_remove直接调用memblock_remove_range,在memblock_remove_range函数中调用memblock_isolate_range把需要删除的内存区间分离出来,然后再调用memblock_remove_region把刚刚分离的内存移出type的memblock中。我们先看memblock_isolate_range是如何分离的:

static int __init_memblock memblock_isolate_range(struct memblock_type *type,
					phys_addr_t base, phys_addr_t size,
					int *start_rgn, int *end_rgn)
{	
	//保证end不会超出物理地址最大范围PHYS_ADDR_MAX
	phys_addr_t end = base + memblock_cap_size(base, &size);
	int idx;
	struct memblock_region *rgn;

	*start_rgn = *end_rgn = 0;

	if (!size)
		return 0;

	/* we'll create at most two more regions */
	//多创建2个memblock
	while (type->cnt + 2 > type->max)
		if (memblock_double_array(type, base, size) < 0)
			return -ENOMEM;
	//遍历type类型的所有内存块
	for_each_memblock_type(idx, type, rgn) {
		phys_addr_t rbase = rgn->base;//记录当前region的base
		phys_addr_t rend = rbase + rgn->size;//记录当前region的end
		
		//如果当前region的base比需要分离的内存块结束地址还大
		if (rbase >= end)
			break;//需要分离的内存块已经分离完了,可以结束循环了
		if (rend <= base)//如果当前region的end比需要分离的内存块的起始地址还小
			continue;//这个region还没有不需要分离,需要遍历下一个region
		
		//到这里,说明当前region需要分离
		if (rbase < base) {//如果当前region的base比需要分离的内存块开始地址还小
		//说明这个region需要一份为二,前一个region是不需要分离的,后面的regoin是需要分离的
			/*
			 * @rgn intersects from below.  Split and continue
			 * to process the next region - the new top half.
			 */
			//修改当前region的信息,这就是前一块region
			rgn->base = base;
			rgn->size -= base - rbase;
			type->total_size -= base - rbase;
			//插入一块region,这就是后面的region
			memblock_insert_region(type, idx, rbase, base - rbase,
					       memblock_get_region_node(rgn),
					       rgn->flags);
		} else if (rend > end) {//如果当前region的end比需要分离的内存块结束地址还大
		//说明这个region需要一份为二,前一个region是需要分离的,后面的regoin是不需要分离的
			/*
			 * @rgn intersects from above.  Split and redo the
			 * current region - the new bottom half.
			 */
			//修改当前region的信息,这就是前一块region
			rgn->base = end;
			rgn->size -= end - rbase;
			type->total_size -= end - rbase;
			//插入一块region,这就是后面的region
			memblock_insert_region(type, idx--, rbase, end - rbase,
					       memblock_get_region_node(rgn),
					       rgn->flags);
		} else {//这个分支表示整个region都是需要分离的
		//其实在前面的分支把region一分为二后,后面的分支是会遍历的,
		//所以记录分离的内存的其实地址和结束地址都是在这里记录
			/* @rgn is fully contained, record it */
			if (!*end_rgn)
				*start_rgn = idx;
			*end_rgn = idx + 1;
		}
	}

	return 0;
}

memblock_isolate_range首先计算出需要分离的内存块的结束地址end,同时需要保证end不会超出物理地址最大范围PHYS_ADDR_MAX;然后在该tpye的的memblock上多创建2个memblock,因为有2次机会可能需要把一个region分为两个;接着遍历type类型的所有内存块region,判断分离的内存在不在该region中,并且分情况处理;最后记录分离的内存的其实地址和结束地址。
我们在看看memblock_insert_region是怎么把内存区间插入到region的:

static void __init_memblock memblock_insert_region(struct memblock_type *type,
						   int idx, phys_addr_t base,
						   phys_addr_t size,
						   int nid,
						   enum memblock_flags flags)
{
	struct memblock_region *rgn = &type->regions[idx];

	BUG_ON(type->cnt >= type->max);
	//memmove与memcpy功能一致,还能处理source指向的空间与destination指向的空间重叠问题
	memmove(rgn + 1, rgn, (type->cnt - idx) * sizeof(*rgn));//通过memmove把rgn往后面挪移一个位置
	rgn->base = base;
	rgn->size = size;
	rgn->flags = flags;
	memblock_set_region_node(rgn, nid);
	type->cnt++;
	type->total_size += size;
}

memblock_insert_region也就是通过memmove把type类型的memblock_region 往后面挪动一位,空出一个memblock_region,然后填写region的base、size、flage、nid信息,最后把type的cnt和total_size信息也更新一下,就这样子,我们就把一块内存添加到memblock中对用的tpye类型了。接下来,我么再看看之后的memblock_merge_regions是怎么合并memblock中对用的tpye类型的。
memblock_isolate_range把需要删除的内存区间分离出来,记录好分离出来的region,然后是调用memblock_remove_region把内存块移出memblock的:

static void __init_memblock memblock_remove_region(struct memblock_type *type, unsigned long r)
{
	type->total_size -= type->regions[r].size;
	memmove(&type->regions[r], &type->regions[r + 1],
		(type->cnt - (r + 1)) * sizeof(type->regions[r]));
	type->cnt--;

	/* Special case for empty arrays */
	if (type->cnt == 0) {
		WARN_ON(type->total_size != 0);
		type->cnt = 1;
		type->regions[0].base = 0;
		type->regions[0].size = 0;
		type->regions[0].flags = 0;
		memblock_set_region_node(&type->regions[0], MAX_NUMNODES);
	}
}

memblock_remove_region很简单,就是使用memmove函数把后面的region往前挪,直接把需要删除的内存块覆盖掉。最后就是修改type的信息,如果该type已经空了,则需要初始化type。

5.memblock分配内存函数

memblock分配内存函数是memblock_alloc:

phys_addr_t __init memblock_alloc(phys_addr_t size, phys_addr_t align)
{
	return memblock_alloc_base(size, align, MEMBLOCK_ALLOC_ACCESSIBLE);
}


phys_addr_t __init memblock_alloc_base(phys_addr_t size, phys_addr_t align, phys_addr_t max_addr)
{
	phys_addr_t alloc;

	alloc = __memblock_alloc_base(size, align, max_addr);

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

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

	if (!align)
		align = SMP_CACHE_BYTES;

	//在给定范围和节点内找一块空区域
	found = memblock_find_in_range_node(size, align, start, end, nid,
					    flags);
	//memblock_reserve是把找到的空区域添加到memblock.reserved中,表示已经用了
	if (found && !memblock_reserve(found, size)) {
		/*
		 * The min_count is set to 0 so that memblock allocations are
		 * never reported as leaks.
		 */
		 //一个内存块分配物理内存的通知
		kmemleak_alloc_phys(found, size, 0, 0);
		return found;
	}
	return 0;
}

memblock_alloc函数通过层层调用,最后到memblock_alloc_base_nid函数,这个函数才是真真的开始从memblock中申请内存,步骤如下:首先通过memblock_find_in_range_node函数找到一块满足大小的空闲区域,然后通过memblock_reserve把这块memblock添加到memblock.reserved中,表示这块内存已经使用了,最后调用kmemleak_alloc_phys函数,避免刚刚申请的内存被报告为内存泄漏。

5.1.memblock_find_in_range_node
phys_addr_t __init_memblock memblock_find_in_range_node(phys_addr_t size,
					phys_addr_t align, phys_addr_t start,
					phys_addr_t end, int nid,
					enum memblock_flags flags)
{
	phys_addr_t kernel_end, ret;

	/* pump up @end */
	if (end == MEMBLOCK_ALLOC_ACCESSIBLE)
		end = memblock.current_limit;

	/* avoid allocating the first page */
	//start至少比PAGE_SIZE大,避免申请第一页内存
	start = max_t(phys_addr_t, start, PAGE_SIZE);
	end = max(start, end);//防止start比end大
	kernel_end = __pa_symbol(_end);

	/*
	 * try bottom-up allocation only when bottom-up mode
	 * is set and @end is above the kernel image.
	 */
	//如果是自底向上模式并且end位于内核映像区之上,
	if (memblock_bottom_up() && end > kernel_end) {
		phys_addr_t bottom_up_start;

		/* make sure we will allocate above the kernel */
		//确保内存区间不会和kernel image的内存区域重叠
		bottom_up_start = max(start, kernel_end);

		/* ok, try bottom-up allocation first */
		//自底向上模式寻找函数
		ret = __memblock_find_range_bottom_up(bottom_up_start, end,
						      size, align, nid, flags);
		if (ret)
			return ret;

		/*
		 * we always limit bottom-up allocation above the kernel,
		 * but top-down allocation doesn't have the limit, so
		 * retrying top-down allocation may succeed when bottom-up
		 * allocation failed.
		 *
		 * bottom-up allocation is expected to be fail very rarely,
		 * so we use WARN_ONCE() here to see the stack trace if
		 * fail happens.
		 */
		WARN_ONCE(IS_ENABLED(CONFIG_MEMORY_HOTREMOVE),
			  "memblock: bottom-up allocation failed, memory hotremove may be affected\n");
	}
	//到这里,说明自底向上模式并且end位于内核映像之中,所以我们采取自顶向下的方式
	return __memblock_find_range_top_down(start, end, size, align, nid,
					      flags);
}

看了memblock_find_in_range_node函数,我们知道不可以去找第一页的内存,也不可以去找kernel image的内存,所以我们在这里会根据实际情况选择自底向上模式还是自顶向下模式去寻找memblock的内存。

5.1.1 __memblock_find_range_bottom_up

自底向上模式:

static phys_addr_t __init_memblock
__memblock_find_range_bottom_up(phys_addr_t start, phys_addr_t end,
				phys_addr_t size, phys_addr_t align, int nid,
				enum memblock_flags flags)
{
	phys_addr_t this_start, this_end, cand;
	u64 i;

	for_each_free_mem_range(i, nid, flags, &this_start, &this_end, NULL) {
		this_start = clamp(this_start, start, end);
		this_end = clamp(this_end, start, end);

		cand = round_up(this_start, align);//向上舍入,align对齐
		if (cand < this_end && this_end - cand >= size)
			return cand;
	}

	return 0;
}

#define for_each_free_mem_range(i, nid, flags, p_start, p_end, p_nid)	\
	for_each_mem_range(i, &memblock.memory, &memblock.reserved,	\
			   nid, flags, p_start, p_end, p_nid)
	
#define for_each_mem_range(i, type_a, type_b, nid, flags,		\
			   p_start, p_end, p_nid)			\
	for (i = 0, __next_mem_range(&i, nid, flags, type_a, type_b,	\
				     p_start, p_end, p_nid);		\
	     i != (u64)ULLONG_MAX;					\
	     __next_mem_range(&i, nid, flags, type_a, type_b,		\
			      p_start, p_end, p_nid))
			    

5.1.2 __memblock_find_range_top_down

自顶向下模式:

static phys_addr_t __init_memblock
__memblock_find_range_top_down(phys_addr_t start, phys_addr_t end,
			       phys_addr_t size, phys_addr_t align, int nid,
			       enum memblock_flags flags)
{
	phys_addr_t this_start, this_end, cand;
	u64 i;

	for_each_free_mem_range_reverse(i, nid, flags, &this_start, &this_end,
					NULL) {
		this_start = clamp(this_start, start, end);
		this_end = clamp(this_end, start, end);

		if (this_end < size)
			continue;

		cand = round_down(this_end - size, align);//向下舍入,align对齐
		if (cand >= this_start)
			return cand;
	}

	return 0;
}

#define for_each_free_mem_range_reverse(i, nid, flags, p_start, p_end,	\
					p_nid)				\
	for_each_mem_range_rev(i, &memblock.memory, &memblock.reserved,	\
			       nid, flags, p_start, p_end, p_nid)

#define for_each_mem_range_rev(i, type_a, type_b, nid, flags,		\
			       p_start, p_end, p_nid)			\
	for (i = (u64)ULLONG_MAX,					\
		     __next_mem_range_rev(&i, nid, flags, type_a, type_b,\
					  p_start, p_end, p_nid);	\
	     i != (u64)ULLONG_MAX;					\
	     __next_mem_range_rev(&i, nid, flags, type_a, type_b,	\
				  p_start, p_end, p_nid))
5.2 memblock_reserve
int __init_memblock memblock_reserve(phys_addr_t base, phys_addr_t size)
{
	phys_addr_t end = base + size - 1;

	memblock_dbg("memblock_reserve: [%pa-%pa] %pF\n",
		     &base, &end, (void *)_RET_IP_);

	return memblock_add_range(&memblock.reserved, base, size, MAX_NUMNODES, 0);
}

int __init_memblock memblock_add_range(struct memblock_type *type,
				phys_addr_t base, phys_addr_t size,
				int nid, enum memblock_flags flags)
{
	bool insert = false;
	//保存好需要添加的内存为[obase,end]
	phys_addr_t obase = base;
	phys_addr_t end = base + memblock_cap_size(base, &size);//保证end 不会溢出
	int idx, nr_new;
	struct memblock_region *rgn;

	if (!size)
		return 0;

	//如果regions[0].size为0,说明该type没有regions,需要初始化一下regions
	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 accommodate the new area.  The second actually inserts them.
	 */
	base = obase;
	nr_new = 0;

	//遍历type类型的所有内存块,与新的内存块比较
	for_each_memblock_type(idx, type, rgn) {
		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)
				//添加内存区域,也就是填充struct memblock_region而已
				memblock_insert_region(type, idx++, 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, idx, base, end - base,
					       nid, flags);
	}
	
	//如果需要加入的内存块个数为0则返回,不需要第二次遍历执行加入操作
	if (!nr_new)
		return 0;

	/*
	 * If this was the first round, resize array and repeat for actual
	 * insertions; otherwise, merge and return.
	 */
	//因为insert参数原因,第一次没有真正插入,第二次才会真正的插入
		//第一次会进入,判断内存区域块是否达到上限,
		if (!insert) {//第一次会进入
		while (type->cnt + nr_new > type->max)//,判断内存区域块是否达到上限
			if (memblock_double_array(type, obase, size) < 0)//达到上限则需要成倍的添加新的region空间
				return -ENOMEM;
		insert = true;
		goto repeat;
	} else {//第二次进入,region空间肯定够了
		memblock_merge_regions(type);//合并相邻且没有缝隙的内存区域
		return 0;
	}
}

memblock_reserve其实是通过memblock_add_range函数,把内存加入到reserved类型中而已。memblock_add_range挺复杂的,他首先记录了需要添加的内存起始和结束地址,然后判断一下该type的内存是否为空,如果是,则需要初始化一下第一个regoin,然后遍历type类型的所有内存块,与新的内存块比较,慢慢的把新的内存添加进来,期间需要注意type类型里面的内存和新添加的内存可能会有重叠部分,需要剔除该重叠部分,然后将其余非重叠部分添加进去。最后还要检查是第一次添加还是第二次添加,如果是第一次添加则需要判断内存区域块是否足够,不够则调用memblock_double_array成倍的添加新的region空间,第二次添加说说明,region空间肯定够了,则调用memblock_merge_regions合并相邻且没有缝隙的内存区域。

6.memblock释放内存函数

memblock释放内存函数是memblock_free:

int __init_memblock memblock_free(phys_addr_t base, phys_addr_t size)
{
	phys_addr_t end = base + size - 1;

	memblock_dbg("   memblock_free: [%pa-%pa] %pF\n",
		     &base, &end, (void *)_RET_IP_);

	//通知释放部分内存块
	kmemleak_free_part_phys(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;
}

memblock_remove_region:

static void __init_memblock memblock_remove_region(struct memblock_type *type, unsigned long r)
{
	type->total_size -= type->regions[r].size;
	memmove(&type->regions[r], &type->regions[r + 1],
		(type->cnt - (r + 1)) * sizeof(type->regions[r]));
	type->cnt--;

	/* Special case for empty arrays */
	if (type->cnt == 0) {
		WARN_ON(type->total_size != 0);
		type->cnt = 1;
		type->regions[0].base = 0;
		type->regions[0].size = 0;
		type->regions[0].flags = 0;
		memblock_set_region_node(&type->regions[0], MAX_NUMNODES);
	}
}

memblock_remove_region很简单,就是使用memmove函数把后面的region往前挪,直接把需要删除的内存块覆盖掉。最后就是修改type的信息,如果该type已经空了,则需要初始化type。

总结一下:memblock_add和memblock_remove函数是初始化memblock用到的函数,主要是初始化好memblock的内存类型和预留类型的struct memblock_type结构体。
memblock_alloc和memblock_free则是memblock已经初始化好了,我们从memblock申请和释放内存锁调用的函数。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小坚学Linux

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值