Linux内存初始化(4)——伙伴系统(buddy)

10 篇文章 0 订阅

说明

Kernel版本:4.0.0
ARM处理器,Contex-A9,QEMU平台
  上文完成了zone的初始化工作,接下来需要初始化伙伴系统(Buddy系统)。

mm_init

  mm_init函数,主要对内核内存分配器进行初始化。
start_kernel->mm_init

/*
 * Set up kernel memory allocators
 */
static void __init mm_init(void)
{
	/*
	 * page_ext requires contiguous pages,
	 * bigger than MAX_ORDER unless SPARSEMEM.
	 */
	page_ext_init_flatmem();
	mem_init();     //初始化buddy系统
	kmem_cache_init();  //slab(slub)初始化,kmalloc初始化。
	percpu_init_late();
	pgtable_init();
	vmalloc_init();  //vmalloc初始化
}

  各个函数功能已经大概说明,下面来看mem_init。这部分代码也是和体系结构(arch)相关的。

mem_init

start_kernel->mm_init->mem_init

void __init mem_init(void)
{
	......

	/* this will put all unused low memory onto the freelists */
	free_unused_memmap();
	free_all_bootmem();                (1)

	......
	free_highpages();

	mem_init_print_info(NULL);         (2)

#define MLK(b, t) b, t, ((t) - (b)) >> 10
#define MLM(b, t) b, t, ((t) - (b)) >> 20
#define MLK_ROUNDUP(b, t) b, t, DIV_ROUND_UP(((t) - (b)), SZ_1K)

	pr_notice("Virtual kernel memory layout:\n"    (3)
			"    vector  : 0x%08lx - 0x%08lx   (%4ld kB)\n"
#ifdef CONFIG_HAVE_TCM
			"    DTCM    : 0x%08lx - 0x%08lx   (%4ld kB)\n"
			"    ITCM    : 0x%08lx - 0x%08lx   (%4ld kB)\n"
#endif
			"    fixmap  : 0x%08lx - 0x%08lx   (%4ld kB)\n"
			"    vmalloc : 0x%08lx - 0x%08lx   (%4ld MB)\n"
			"    lowmem  : 0x%08lx - 0x%08lx   (%4ld MB)\n"
#ifdef CONFIG_HIGHMEM
			"    pkmap   : 0x%08lx - 0x%08lx   (%4ld MB)\n"
#endif
#ifdef CONFIG_MODULES
			"    modules : 0x%08lx - 0x%08lx   (%4ld MB)\n"
#endif
			"      .text : 0x%p" " - 0x%p" "   (%4td kB)\n"
			"      .init : 0x%p" " - 0x%p" "   (%4td kB)\n"
			"      .data : 0x%p" " - 0x%p" "   (%4td kB)\n"
			"       .bss : 0x%p" " - 0x%p" "   (%4td kB)\n",

			MLK(UL(CONFIG_VECTORS_BASE), UL(CONFIG_VECTORS_BASE) +
				(PAGE_SIZE)),
#ifdef CONFIG_HAVE_TCM
			MLK(DTCM_OFFSET, (unsigned long) dtcm_end),
			MLK(ITCM_OFFSET, (unsigned long) itcm_end),
#endif
			MLK(FIXADDR_START, FIXADDR_END),
			MLM(VMALLOC_START, VMALLOC_END),
			MLM(PAGE_OFFSET, (unsigned long)high_memory),
#ifdef CONFIG_HIGHMEM
			MLM(PKMAP_BASE, (PKMAP_BASE) + (LAST_PKMAP) *
				(PAGE_SIZE)),
#endif
#ifdef CONFIG_MODULES
			MLM(MODULES_VADDR, MODULES_END),
#endif

			MLK_ROUNDUP(_text, _etext),
			MLK_ROUNDUP(__init_begin, __init_end),
			MLK_ROUNDUP(_sdata, _edata),
			MLK_ROUNDUP(__bss_start, __bss_stop));
	......
}

  先讲一下(2)(3)的含义,然后再详细讲(1)吧
(2)打印一些内存相关统计信息,即启动后的Memory: 763140K/778240K available (4779K kernel code, 156K rwdata, 1364K rodata, 1444K init, 148K bss, 15100K reserved, 0K cma-reserved)打印。
(3)打印虚拟内存各个段,即内核启动后如下信息。

Virtual kernel memory layout:
    vector  : 0xffff0000 - 0xffff1000   (   4 kB)
    fixmap  : 0xffc00000 - 0xfff00000   (3072 kB)
    vmalloc : 0xf0000000 - 0xff000000   ( 240 MB)
    lowmem  : 0xc0000000 - 0xef800000   ( 760 MB)
    modules : 0xbf000000 - 0xc0000000   (  16 MB)
      .text : 0xc0008000 - 0xc0608138   (6145 kB)
      .init : 0xc0609000 - 0xc0772000   (1444 kB)
      .data : 0xc0772000 - 0xc0799000   ( 156 kB)
       .bss : 0xc0799000 - 0xc07be138   ( 149 kB)

(1)free_all_bootmem把bootmem释放给buddy系统。注意:其实没有用到bootmem,所以走的都是nobootmem.c中的函数。

free_all_bootmem

start_kernel->mm_init->mem_init->free_all_bootmem

unsigned long __init free_all_bootmem(void)
{
	unsigned long pages;

	reset_all_zones_managed_pages();

	/*
	 * We need to use NUMA_NO_NODE instead of NODE_DATA(0)->node_id
	 *  because in some case like Node0 doesn't have RAM installed
	 *  low ram will be on Node1
	 */
	pages = free_low_memory_core_early();
	totalram_pages += pages;

	return pages;
}

start_kernel->mm_init->mem_init->free_all_bootmem->free_low_memory_core_early
  释放low mem给buddy系统

static unsigned long __init free_low_memory_core_early(void)
{
	unsigned long count = 0;
	phys_addr_t start, end;
	u64 i;

	memblock_clear_hotplug(0, -1);

	for_each_free_mem_range(i, NUMA_NO_NODE, &start, &end, NULL)
		count += __free_memory_core(start, end);

	return count;
}

  把内存块传递到__free_pages_memory函数中,该函数定义如下:
start_kernel->mm_init->mem_init->free_all_bootmem->free_low_memory_core_early->__free_memory_core->__free_pages_memory

static inline unsigned long __ffs(unsigned long x)
{
	return ffs(x) - 1;
}

static void __init __free_pages_memory(unsigned long start, unsigned long end)
{
	int order;

	while (start < end) {
		order = min(MAX_ORDER - 1UL, __ffs(start));

		while (start + (1UL << order) > end)
			order--;
		printk("start = %#x, ffs = %#x\n", start, order);
		__free_pages_bootmem(pfn_to_page(start), order);

		start += (1UL << order);
	}
}

  start和end指页帧号,while循环一直从开始页帧号start遍历到end,循环的步长和order有关。首先计算order的大小,取MAX_ORDER-1和__ffs(start)的最小值。__ffs(start)函数计算,start中第一个bit为1的位置。注意__ffs()=ffs()-1。因为伙伴系统的链表都是2的n次幂,最大的链表是2的10次方。也就是1024,即0x400.所以通过ffs可以很方便计算出对齐边界。如start等于0x63000,那么__ffs(0x63300)等于8,这里可以order选用8。
  得到order后,我们可以把这块内存通过__free_pages_bootmem()函数添加到伙伴系统。
  需要注意的是,low mem中间存在空洞,即bootmem预留的内存。示例如下。中间空了十几个page,所以ffs需要重新计算,并逐阶上升:

[16:35:08:972]start = 0x67000, ffs = 0xa
[16:35:08:973]start = 0x67400, ffs = 0xa
[16:35:08:973]start = 0x67800, ffs = 0xa
[16:35:08:973]start = 0x67c00, ffs = 0xa
[16:35:08:973]start = 0x6800c, ffs = 0x2
[16:35:08:973]start = 0x68010, ffs = 0x4
[16:35:08:973]start = 0x68020, ffs = 0x5
[16:35:08:974]start = 0x68040, ffs = 0x6
[16:35:08:974]start = 0x68080, ffs = 0x7
[16:35:08:974]start = 0x68100, ffs = 0x8
[16:35:08:974]start = 0x68200, ffs = 0x9
[16:35:08:974]start = 0x68400, ffs = 0xa
[16:35:08:974]start = 0x68800, ffs = 0xa
[16:35:08:975]start = 0x68c00, ffs = 0xa
[16:35:08:975]start = 0x69000, ffs = 0xa
[16:35:08:975]start = 0x69400, ffs = 0xa

start_kernel->mm_init->mem_init->free_all_bootmem->free_low_memory_core_early->__free_memory_core->__free_pages_memory->__free_pages_bootmem->__free_pages_bootmem

void __init __free_pages_bootmem(struct page *page, unsigned int order)
{
	unsigned int nr_pages = 1 << order;
	struct page *p = page;
	unsigned int loop;

	prefetchw(p);
	for (loop = 0; loop < (nr_pages - 1); loop++, p++) {
		prefetchw(p + 1);
		__ClearPageReserved(p);
		set_page_count(p, 0);    (1)
	}
	__ClearPageReserved(p);
	set_page_count(p, 0);

	page_zone(page)->managed_pages += nr_pages;    (2)
	set_page_refcounted(page);                     (3)
	__free_pages(page, order);                     (4)
}

(1)逐页对page数据结构,page count清零。
(2)page所在zone,管理的page数量增加nr_pages。
(3)设置page count为1,即页面正在使用。
(4)buddy系统释放页面接口,与正常使用释放接口一致。上述(3)中先假定页面被使用了,然后使用接口释放。
  __free_pages是buddy释放接口,这里就不详细展开了,可以直接度娘,或者有机会展开分析一下。
  mem_init函数中,除了free_all_bootmem外,还有free_highpages。区别在于前者释放的是低端内存,后者释放的是高端内存。high mem虽然未进行页表映射,但是物理页面是真实存在的,所以每个page也需要struct page进行管理。也需要buddy系统进行分配。buddy分配内存时,优先从高端内存开始分配。当然在一些嵌入式产品中,内存限制,会使能highmem,但是物理内存往往到不了那么大。此种情况下当执行free_highpages没有对应物理页面。分配内存时同理,没有高端内存可以分配,那么首选项就是低端内存了。

相关文章

Linux内存初始化(1)——memblock初始化
Linux内存初始化(2)——paging_init初始化
Linux内存初始化(3)——pglist_data/zone初始化
Linux内存初始化(4)——伙伴系统(buddy)

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
伙伴堆(Buddy Heap)是 Linux 系统中一种内存资源管理算法,用于管理系统内存的分配和释放。伙伴堆算法将系统内存划分为若干固定大小的块,每个块大小都是 2 的幂次方,例如 1KB、2KB、4KB 等等。每个块都是一块连续的内存空间,可以被分配给进程使用。具体的设计数据结构和算法如下: 1. 内存块数据结构 伙伴堆算法将系统内存划分为若干固定大小的块,每个块大小都是 2 的幂次方。对于一个大小为 2^k 的内存块,可以用一个双向链表来管理这个块的所有状态。该链表包含多个节点,每个节点的数据结构如下: ``` struct buddy { int order; // 该内存块的大小,即2的幂次方 char *address; // 该内存块的起始地址 struct buddy *prev; // 指向前一个内存块 struct buddy *next; // 指向后一个内存块 int free; // 该内存块的状态,0表示已分配,1表示空闲 }; ``` 2. 内存块分配算法 当需要分配一个大小为 size 的内存块时,伙伴堆算法会找到一个大小为 2^k(k>=0)的空闲块,然后将其分裂为两个大小为 2^(k-1) 的伙伴块。如果找不到大小为 2^k 的空闲块,则继续向更大的块中寻找,直到找到一个足够大的块为止。具体的分配算法如下: 1. 首先找到一个大小为 2^k 的空闲块,如果没有则继续向更大的块中寻找。 2. 将该块标记为已分配状态,并返回该块的起始地址。 3. 如果该块大小为 2^k,则分配结束,否则将该块分裂为两个大小为 2^(k-1) 的伙伴块,将其中一个块标记为空闲状态,并加入到大小为 2^(k-1) 的空闲块链表中。 4. 重复上述步骤,直到所有需要分配的内存块都已被分配完成。 3. 内存块释放算法 当需要释放一个内存块时,伙伴堆算法会将该块标记为空闲状态,并与其伙伴块合并成一个更大的块。具体的释放算法如下: 1. 将该内存块标记为空闲状态,并将该块加入到对应大小的空闲块链表中。 2. 检查该块的伙伴块是否为空闲状态,如果是,则将这两个块合并成一个更大的块,并将其标记为空闲状态,加入到更大的空闲块链表中。 3. 重复上述步骤,直到不能再合并为止。 伙伴堆算法通过将内存块划分为若干固定大小的块,实现了高效的内存分配和释放。该算法的优点在于:实现简单,分配和释放速度快,内存碎片小,适用于高并发、大量小内存的场景。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值