Linux深入理解内存管理18

Linux深入理解内存管理18(基于Linux6.6)---bootmem内存分配器介绍

一、概述

Bootmem(Bootstrap Memory)是 Linux 内核在系统启动过程中用来分配内存的一个特殊内存分配器。它的设计目的是在系统启动的早期阶段,内核还没有完全初始化所有的内存管理功能(如页表管理、伙伴系统等)时,能够有效地管理和分配内存。

在 Linux 内核的引导阶段,内存分配的需求与常规内存分配需求有所不同。由于内核还没有完全初始化,通常需要一个简单且快速的内存分配机制,而 Bootmem 就是为此目的而设计的。

1.1、Bootmem 内存分配器的背景

在 Linux 启动过程中,系统需要执行许多初始化任务(例如加载内核、初始化硬件、设置内存映射等),这些任务会消耗大量内存,而此时很多常规的内存管理子系统(如伙伴系统、Slab 分配器等)尚未初始化。因此,Bootmem 作为一种快速且简化的内存管理机制,在这个阶段发挥了重要作用。

启动阶段的内存管理需求:

  • 内存分配的需求:在系统启动的初期,内核需要分配内存来加载各种内核模块、设置内核数据结构、初始化硬件设备等。
  • 内存管理的限制:此时,内核尚未初始化完整的内存管理子系统(如伙伴系统、Slab 分配器等),因此不能使用复杂的内存分配策略。
  • 高效性要求:启动阶段的内存分配要求尽可能高效,以便快速完成系统启动。

1.2、Bootmem 的工作原理

Bootmem 的主要目的是在 Linux 启动时管理物理内存的分配。它的工作流程可以分为几个阶段:

1、内存区域的标记
  • 可用内存标记:在启动时,内核会获取系统的物理内存布局,并标记哪些内存区域是可用的。这个过程会使用 e820(在 x86 架构上)或类似机制来扫描系统的物理内存区域并标记内存区域的状态(例如,已使用、可用、保留等)。
2、分配内存
  • 简单的内存分配:Bootmem 的内存分配方式比较简单,通常是直接从标记为 "可用" 的内存区域中分配内存,而没有复杂的算法。它通常通过维护一个可用内存区域的链表来进行分配,分配过程尽量避免使用复杂的算法和数据结构,目的是简化处理和提高效率。
3、内存回收
  • 内存回收机制:当系统启动完成后,Bootmem 分配器会将已经分配的内存交给后续的内存管理子系统(如伙伴系统或 Slab 分配器)。Bootmem 分配器会释放其管理的内存区域,确保后续的内存管理机制能够接管这些内存资源。

1.3、Bootmem 内存分配器的实现细节

在 Linux 内核中,Bootmem 内存分配器是通过 bootmem 子系统实现的,主要由以下几个核心部分组成:

1、bootmem 数据结构
  • bootmem_data:这是 Bootmem 内存分配器的核心数据结构。它用于跟踪可用内存区域的信息。
  • 内存块列表:在 bootmem_data 中,通常会维护一个链表或数组来表示可用的内存块。每次分配内存时,Bootmem 会从链表中取出一个块并返回给调用者。
2、bootmem 的分配和释放接口
  • alloc_bootmem():这是 Bootmem 内存分配器的主要接口函数,用于从 Bootmem 中分配内存。它会从可用内存区域中选择一个合适的内存块来返回。
  • free_bootmem():在系统启动完成后,Bootmem 分配的内存通常会被释放,以便交给其他内存管理机制。
3、内存区域管理
  • Bootmem 管理内存时,会通过物理地址来跟踪哪些内存区域已被分配,哪些内存区域是空闲的。这些内存区域通常以页为单位进行管理,内存区域的起始地址和大小被记录在 bootmem_data 结构中。
4、初始化与交接
  • 在系统启动过程中,内核首先使用 Bootmem 来分配内存来初始化硬件和加载内核代码。当内核的页面管理系统(如伙伴系统)初始化完成后,Bootmem 分配的内存将被释放,内存管理交由更加复杂的内存分配器(如 Slab、伙伴系统)接管。

1.4、Bootmem 与其他内存管理器的关系

Bootmem 内存分配器仅在内核启动过程中使用。随着系统的初始化完成,Bootmem 会将分配的内存交给更强大的内存管理器来接管:

  • 伙伴系统(Buddy System):伙伴系统是 Linux 内核中常用的内存分配算法,特别适用于管理大块的内存。系统启动完成后,内存分配的工作会交给伙伴系统,Bootmem 管理的内存将被伙伴系统接管。
  • Slab 分配器:Slab 分配器用于高效地分配小块内存(例如内核对象)。在启动后,Bootmem 管理的内存会交给 Slab 分配器,用于内核对象的管理。

二、Bootm初始化

memblock,其作用内核启动初期,常用的内存分配器还未被初始化而不能使用,在此期间memblock是一种用于内存管理区域的方法。然后调用page_init来完成系统分页机制的初始化工作,建立页表,从而内核可以完成虚拟地址到物理地址的映射关系。
arm架构下, 在setup_arch中通过paging_init函数初始化内核分页机制之后, 内核通过bootmem_init()开始完成内存结点和内存区域的初始化工作,其函数定义在arch/arm/mm/init.c中,如下所示。

void __init bootmem_init(void)
{
	memblock_allow_resize();---解析1

	find_limits(&min_low_pfn, &max_low_pfn, &max_pfn);

	early_memtest((phys_addr_t)min_low_pfn << PAGE_SHIFT,
		      (phys_addr_t)max_low_pfn << PAGE_SHIFT);

	/*
	 * sparse_init() tries to allocate memory from memblock, so must be
	 * done after the fixed reservations
	 */
	sparse_init();---解析2

	/*
	 * Now free the memory - free_area_init needs
	 * the sparse mem_map arrays initialized by sparse_init()
	 * for memmap_init_zone(), otherwise all PFNs are invalid.
	 */
	zone_sizes_init(min_low_pfn, max_low_pfn, max_pfn);---解析3
}
  1. 通过memblock拿到limit,DRAM的起始地址的页面号,分别为min = 0x80000, max_low = 0xa0000,内存的结束地址max_high = 0xa0000,early_memtest做内存的Memtest使用,最终会赋值给min_low_pfn(内存块的起始帧号),max_low_pfn(normal结束帧号),max_pfn(内存块的结束帧号)。
  2. 启动并运行bootmem分配器,对于ARM32位系统,该功能不支持,几乎没有做什么。
  3. zone_sizes_init()来初始化节点和管理区的一些数据项, 其中关键的是初始化了系统中各个内存域的页帧边界,保存在max_zone_pfn数组,从min_low_pfn到max_low_pfn是ZONE_NORMAL,max_low_pfn到max_pfn是ZONE_HIGHMEM。

三、Zone_sizes_init初始化
 

arch/arm/mm/init.c

static void __init zone_sizes_init(unsigned long min, unsigned long max_low,
	unsigned long max_high)
{
	unsigned long max_zone_pfn[MAX_NR_ZONES] = { 0 };

#ifdef CONFIG_ZONE_DMA
	max_zone_pfn[ZONE_DMA] = min(arm_dma_pfn_limit, max_low);
#endif
	max_zone_pfn[ZONE_NORMAL] = max_low;
#ifdef CONFIG_HIGHMEM
	max_zone_pfn[ZONE_HIGHMEM] = max_high;
#endif
	free_area_init(max_zone_pfn);
}

 统计zone_size[0]和zone_size[ZONE_HIGHMEM]的大小,zone_size[0] = 0x20000,zone_size[ZONE_HIGHMEM] = 0。

 mm/page_alloc.c

void __init free_area_init(unsigned long *max_zone_pfn)
{
	unsigned long start_pfn, end_pfn;
	int i, nid, zone;
	bool descending;

	/* Record where the zone boundaries are */
	memset(arch_zone_lowest_possible_pfn, 0,
				sizeof(arch_zone_lowest_possible_pfn));
	memset(arch_zone_highest_possible_pfn, 0,
				sizeof(arch_zone_highest_possible_pfn));

	start_pfn = PHYS_PFN(memblock_start_of_DRAM());
	descending = arch_has_descending_max_zone_pfns();

	for (i = 0; i < MAX_NR_ZONES; i++) {
		if (descending)
			zone = MAX_NR_ZONES - i - 1;
		else
			zone = i;

		if (zone == ZONE_MOVABLE)
			continue;

		end_pfn = max(max_zone_pfn[zone], start_pfn);
		arch_zone_lowest_possible_pfn[zone] = start_pfn;
		arch_zone_highest_possible_pfn[zone] = end_pfn;

		start_pfn = end_pfn;
	}

	/* Find the PFNs that ZONE_MOVABLE begins at in each node */
	memset(zone_movable_pfn, 0, sizeof(zone_movable_pfn));
	find_zone_movable_pfns_for_nodes();

	/* Print out the zone ranges */
	pr_info("Zone ranges:\n");
	for (i = 0; i < MAX_NR_ZONES; i++) {
		if (i == ZONE_MOVABLE)
			continue;
		pr_info("  %-8s ", zone_names[i]);
		if (arch_zone_lowest_possible_pfn[i] ==
				arch_zone_highest_possible_pfn[i])
			pr_cont("empty\n");
		else
			pr_cont("[mem %#018Lx-%#018Lx]\n",
				(u64)arch_zone_lowest_possible_pfn[i]
					<< PAGE_SHIFT,
				((u64)arch_zone_highest_possible_pfn[i]
					<< PAGE_SHIFT) - 1);
	}

	/* Print out the PFNs ZONE_MOVABLE begins at in each node */
	pr_info("Movable zone start for each node\n");
	for (i = 0; i < MAX_NUMNODES; i++) {
		if (zone_movable_pfn[i])
			pr_info("  Node %d: %#018Lx\n", i,
			       (u64)zone_movable_pfn[i] << PAGE_SHIFT);
	}

	/*
	 * Print out the early node map, and initialize the
	 * subsection-map relative to active online memory ranges to
	 * enable future "sub-section" extensions of the memory map.
	 */
	pr_info("Early memory node ranges\n");
	for_each_mem_pfn_range(i, MAX_NUMNODES, &start_pfn, &end_pfn, &nid) {
		pr_info("  node %3d: [mem %#018Lx-%#018Lx]\n", nid,
			(u64)start_pfn << PAGE_SHIFT,
			((u64)end_pfn << PAGE_SHIFT) - 1);
		subsection_map_init(start_pfn, end_pfn - start_pfn);
	}

	/* Initialise every node */
	mminit_verify_pageflags_layout();
	setup_nr_node_ids();
	for_each_node(nid) {
		pg_data_t *pgdat;

		if (!node_online(nid)) {
			pr_info("Initializing node %d as memoryless\n", nid);

			/* Allocator not initialized yet */
			pgdat = arch_alloc_nodedata(nid);
			if (!pgdat) {
				pr_err("Cannot allocate %zuB for node %d.\n",
						sizeof(*pgdat), nid);
				continue;
			}
			arch_refresh_nodedata(nid, pgdat);
			free_area_init_memoryless_node(nid);

			/*
			 * We do not want to confuse userspace by sysfs
			 * files/directories for node without any memory
			 * attached to it, so this node is not marked as
			 * N_MEMORY and not marked online so that no sysfs
			 * hierarchy will be created via register_one_node for
			 * it. The pgdat will get fully initialized by
			 * hotadd_init_pgdat() when memory is hotplugged into
			 * this node.
			 */
			continue;
		}

		pgdat = NODE_DATA(nid);
		free_area_init_node(nid);

		/* Any memory on that node */
		if (pgdat->node_present_pages)
			node_set_state(nid, N_MEMORY);
		check_for_memory(pgdat, nid);
	}

	memmap_init();
}
  • free_area_init_node用来针对特定的节点进行初始化。

zone_size[]数据用于保持不同ZONE类型具有的页表,zhole_size数组用于保持不同的ZONE类型具有的空洞的页数,如下图所示:在这里插入图片描述

接下来看看free_area_init_node的实现接口。

 mm/page_alloc.c 

static void __init free_area_init_node(int nid)
{
	pg_data_t *pgdat = NODE_DATA(nid);---解析1
	unsigned long start_pfn = 0;
	unsigned long end_pfn = 0;

	/* pg_data_t should be reset to zero when it's allocated */
	WARN_ON(pgdat->nr_zones || pgdat->kswapd_highest_zoneidx);

	get_pfn_range_for_nid(nid, &start_pfn, &end_pfn);

	pgdat->node_id = nid;
	pgdat->node_start_pfn = start_pfn;
	pgdat->per_cpu_nodestats = NULL;

	if (start_pfn != end_pfn) {
		pr_info("Initmem setup node %d [mem %#018Lx-%#018Lx]\n", nid,
			(u64)start_pfn << PAGE_SHIFT,
			end_pfn ? ((u64)end_pfn << PAGE_SHIFT) - 1 : 0);
	} else {
		pr_info("Initmem setup node %d as memoryless\n", nid);
	}

	calculate_node_totalpages(pgdat, start_pfn, end_pfn);---解析2

	alloc_node_mem_map(pgdat);---解析3
	pgdat_set_deferred_range(pgdat);---解析4

	free_area_init_core(pgdat);---解析5
}
  1. 在NUMA有多个节点,而每个节点内,访问内存的时间是相同的,不同的节点,访问内存的时间可以不同。而对于UMA,只有一个节点,取得该node的pg_data_t数据结构变量,每个node都有一个pg_data_t变量描述,进行初始化工作。node_id=0,node_start_fn = 0x80000, start_fn = 0x80000
  2. 对节点长度和节点总可用页面数进行初始化。calculate_node_totalpages函数是通过调用zone_spanned_pages_in_node和zone_absent_pages_in_node函数实现的,主要是为pgdat的成员变量(包括空洞在内的总页数(node_spanned_pages))和除空洞外的页数(node_present_pages)设置值
  3. alloc_node_mem_map() 初始化节点的局部映射地址,即pg_data_t->node_mem_map。在NUMA中,全局mem_map指向系统第一个节点的地址,系统中每个节点的起始地址,都对应在全局mem_map的某个位置。在UMA中,全局mem_map就是节点的node_mem_map
  4. 由于CONFIG_DEFERRED_STRUCT_PAGE_INIT未定义,该函数为空
  5. 调用free_area_init_core()来真正初始化每个struct zone中的成员,填充pgdat的ZONE结构体。

3.1、calculate_node_totalpages

对于该函数主要是用来计算每一个zone的总页数和实际页数(不包含空洞),以及内存节点的总页数和实际页数(不包含空洞),其代码实现如下:
mm/page_alloc.c

static void __init calculate_node_totalpages(struct pglist_data *pgdat,
						unsigned long node_start_pfn,
						unsigned long node_end_pfn)
{
	unsigned long realtotalpages = 0, totalpages = 0;
	enum zone_type i;

	for (i = 0; i < MAX_NR_ZONES; i++) {
		struct zone *zone = pgdat->node_zones + i;
		unsigned long zone_start_pfn, zone_end_pfn;
		unsigned long spanned, absent;
		unsigned long size, real_size;

		spanned = zone_spanned_pages_in_node(pgdat->node_id, i,
						     node_start_pfn,
						     node_end_pfn,
						     &zone_start_pfn,
						     &zone_end_pfn);
		absent = zone_absent_pages_in_node(pgdat->node_id, i,
						   node_start_pfn,
						   node_end_pfn);

		size = spanned;
		real_size = size - absent;

		if (size)
			zone->zone_start_pfn = zone_start_pfn;
		else
			zone->zone_start_pfn = 0;
		zone->spanned_pages = size;
		zone->present_pages = real_size;
#if defined(CONFIG_MEMORY_HOTPLUG)
		zone->present_early_pages = real_size;
#endif

		totalpages += size;
		realtotalpages += real_size;
	}

	pgdat->node_spanned_pages = totalpages;
	pgdat->node_present_pages = realtotalpages;
	pr_debug("On node %d totalpages: %lu\n", pgdat->node_id, realtotalpages);
}

该函数主要计算各个ZONE区的page数目,对于ZONE区,其主要有以下3个

  • ZONE_DMA,该管理区是一些设备无法使用DMA访问所有地址的范围,因此特意划分出来的一块内存,专门用于特殊DMA访问分配使用的区域。比如x86架构此区域为0-16M。本处理器该区域不存在。
  • ZONE_NORMAL:直接映射区,含有的页面数为0x20000。
  • ZONE_HIGHMEM:高端内存管理区,申请的内存,需要内核进行map后才能访问。
  • ZONE_MOVABLE:这个区域是一个特殊的存在,主要是为了支持memory hotplug功能,所以MOVABLE表示可移除,其实它也表示可迁移。

简单来说,可迁移的页面不一定都在ZONE_MOVABLE中,但是ZONE_MOVABLE中的也页面必须都是可迁移的,通过查看/proc/pagetypeinfo来看下实例:

ZONE_MOVABLE这个管理区,主要是和memory hotplug功能有关,为什么要设计内存热插拔功能,主要是为了如下考虑:

  1. 逻辑内存热插拔,对于虚拟机的支持,对于虚拟机按照需求来分配可用内存。
  2. 物理内存热插拔,对于NUMA服务器的支持,不需要的内存就设置为offline,以降低功耗。
  3. 优化内存碎片问题。

3.2、alloc_node_mem_map

在linux内核中,所有的物理内存都用struct page结构来描述,这些对象以数组形式存放,而这个数组的地址就是mem_map。内核以节点node为单位,每个node下的物理内存统一管理,也就是说在表示内存node的描述类型struct pglist_data中,有node_mem_map这个成员,其针对平坦型内存进行描述(CONFIG_FLAT_NODE_MEM_MAP)。如果系统只有一个pglist_data对象,那么此对象下的node_mem_map即为全局对象mem_map。函数alloc_remap()就是针对节点node的node_mem_map处理。

mm/page_alloc.c


#ifdef CONFIG_FLATMEM
static void __init alloc_node_mem_map(struct pglist_data *pgdat)
{
	unsigned long __maybe_unused start = 0;
	unsigned long __maybe_unused offset = 0;

	/* Skip empty nodes */
	if (!pgdat->node_spanned_pages)---解析1
		return;

	start = pgdat->node_start_pfn & ~(MAX_ORDER_NR_PAGES - 1);---解析2
	offset = pgdat->node_start_pfn - start;
	/* ia64 gets its own node_mem_map, before this, without bootmem */
	if (!pgdat->node_mem_map) {
		unsigned long size, end;
		struct page *map;

		/*
		 * The zone's endpoints aren't required to be MAX_ORDER
		 * aligned but the node_mem_map endpoints must be in order
		 * for the buddy allocator to function correctly.
		 */
		end = pgdat_end_pfn(pgdat);---解析3
		end = ALIGN(end, MAX_ORDER_NR_PAGES);
		size =  (end - start) * sizeof(struct page);---解析4
		map = memmap_alloc(size, SMP_CACHE_BYTES, MEMBLOCK_LOW_LIMIT,
				   pgdat->node_id, false);
		if (!map)
			panic("Failed to allocate %ld bytes for node %d memory map\n",
			      size, pgdat->node_id);---解析5
		pgdat->node_mem_map = map + offset;
	}
	pr_debug("%s: node %d, pgdat %08lx, node_mem_map %08lx\n",
				__func__, pgdat->node_id, (unsigned long)pgdat,
				(unsigned long)pgdat->node_mem_map);
#ifndef CONFIG_NUMA
	/*
	 * With no DISCONTIG, the global mem_map is just set as node 0's
	 */
	if (pgdat == NODE_DATA(0)) {
		mem_map = NODE_DATA(0)->node_mem_map;
		if (page_to_pfn(mem_map) != pgdat->node_start_pfn)
			mem_map -= offset;
	}
#endif
}
#else
static inline void alloc_node_mem_map(struct pglist_data *pgdat) { }
#endif /* CONFIG_FLATMEM */
  1. pgdat->node_spanned_pages此内存节点内无有效的内存,直接略过
  2. 起始地址必须对其,这个一般按照MB级别对齐即可,对齐后地址与真正开始地址之间的偏移大小,start = 0x80000,offset = 0
  3. 获取节点内结束页帧号pfn,然后对齐,end = 0xa0000,
  4. 计算需要的数组大小,需要注意end-start是页帧个数(0xa0000 - 0x80000 = 0x40000),每个页需要一个struct page对象,所以,这里是乘关系,这样得到整个node内所有以page为单位描述需要占据的内存
  5. 如果这里分配失败,则通过memblock管理算法分配内存。

3.3、free_area_init_core

mm/page_alloc.c

{
	enum zone_type j;
	int nid = pgdat->node_id;

	pgdat_init_internals(pgdat);---解析1
	pgdat->per_cpu_nodestats = &boot_nodestats;

	for (j = 0; j < MAX_NR_ZONES; j++) {---解析2
		struct zone *zone = pgdat->node_zones + j;
		unsigned long size, freesize, memmap_pages;

		size = zone->spanned_pages;
		freesize = zone->present_pages;

		/*
		 * Adjust freesize so that it accounts for how much memory
		 * is used by this zone for memmap. This affects the watermark
		 * and per-cpu initialisations
		 */
		memmap_pages = calc_memmap_size(size, freesize);
		if (!is_highmem_idx(j)) {
			if (freesize >= memmap_pages) {
				freesize -= memmap_pages;
				if (memmap_pages)
					pr_debug("  %s zone: %lu pages used for memmap\n",
						 zone_names[j], memmap_pages);
			} else
				pr_warn("  %s zone: %lu memmap pages exceeds freesize %lu\n",
					zone_names[j], memmap_pages, freesize);
		}

		/* Account for reserved pages */
		if (j == 0 && freesize > dma_reserve) {
			freesize -= dma_reserve;
			pr_debug("  %s zone: %lu pages reserved\n", zone_names[0], dma_reserve);
		}

		if (!is_highmem_idx(j))
			nr_kernel_pages += freesize;
		/* Charge for highmem memmap if there are enough kernel pages */
		else if (nr_kernel_pages > memmap_pages * 2)
			nr_kernel_pages -= memmap_pages;
		nr_all_pages += freesize;

		/*
		 * Set an approximate value for lowmem here, it will be adjusted
		 * when the bootmem allocator frees pages into the buddy system.
		 * And all highmem pages will be managed by the buddy system.
		 */
		zone_init_internals(zone, j, nid, freesize);---解析3

		if (!size)
			continue;

		set_pageblock_order();
		setup_usemap(zone);
		init_currently_empty_zone(zone, zone->zone_start_pfn, size);---解析4
	}
}
  • 主要是初始化struct pglist_data,首先初始化pgdat->node_size_lock自旋锁初始化,初始化pgdat->kswapd_wait等待队列,初始化页换出守护进程创建空闲块的大小。
  • 遍历各个zone区域,进行如下初始化:
  • 根据spanned_pages和present_pages,调用calc_memmap_size计算管理该zone所需的struct page结构所占的页面数memmap_pages。
  • zone中的freesize表示可用的区域,需要减去memmap_pages和dma_reserve的区域,如下开发板的Log打印所示:memmap使用1024页,DMA 保留0页。
  • 计算nr_kernel_pages和nr_all_pages的数量。
  • 初始化zone的其他变量和各种锁。
  • 初始化zone结构体的per_cpu_pageset结构体变量pageset,per_cpu_pageset按CPU进行管理。它不直接返回伙伴系统,为快速分配而按不同CPU持有页。
  • 初始化zone结构体的free_area结构体。

四、总结

回顾bootmem_init函数时,发现它基本上完成了linux物理内存框架的初始化,包括Node, Zone, Page Frame,以及对应的数据结构等。

4.1、内存分配流程

  1. 启动阶段: 内核启动时,bootmem 会根据 e820 提供的内存映射信息,划分出可用的物理内存区域,并将它们添加到空闲内存块链表中。此时,bootmem 分配器还未与其他内存分配器(如伙伴系统、Slab 分配器)交互,它的工作只是为启动阶段提供临时的内存支持。

  2. 分配内存: 当内核需要分配内存时,bootmem 会从空闲内存块链表中选择一个适合的内存块,并将其分配给请求的模块或功能。每次分配都会更新链表,标记已经分配的内存块,并保证不会分配已使用或不可用的内存。

  3. 内存释放: 当某个内存区域不再需要时,bootmem 会将其释放回空闲链表中,供后续内存分配使用。这通常发生在内核初始化的某些阶段。

  4. 内存管理完成: 在内核完成启动过程并初始化了其他内存分配器(如伙伴系统或 Slab 分配器)之后,bootmem 内存分配器的工作逐渐过渡给其他更加高效的内存管理系统。

4.2、内存分配器的接口

bootmem 内存分配器提供了一些接口来进行内存的分配和释放:

  • bootmem_alloc():用于从 bootmem 内存池中分配指定大小的内存块。
  • bootmem_free():释放先前分配的内存块,将内存块重新加入空闲链表中。
  • bootmem_init():初始化 bootmem 内存分配器,准备内存池和管理结构。

4.3、Bootmem 与其他内存管理系统的过渡

  • 伙伴系统:在 Linux 启动过程中的某个时刻,bootmem 分配器会将内存管理权交给更为高效的伙伴系统(Buddy System)。伙伴系统具有更高的性能和灵活性,能够处理更复杂的内存分配请求。bootmem 的内存块管理方式相对简单,无法高效地应对动态内存需求,因此会在内核完成初始化后被替换。

  • Slab 分配器bootmem 主要用于物理内存的管理,而 Slab 分配器主要用于内核对象的分配与回收,它提供了更高效的内存管理策略。bootmem 只能满足内核初始化阶段的需求,一旦系统准备好,Slab 分配器接管后续的内存管理工作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值