Linux 内存管理窥探(8):内存初始化(paging_init)

参考文档:

https://github.com/gatieme/LDD-LinuxDeviceDrivers/tree/master/study/kernel/02-memory/03-initialize/04-bootmem_init 

感谢作者的无私分享。

我们住关注 start_kernel 中关于内存管理的部分总览:

asmlinkage __visible void __init start_kernel(void)
{
.....
    setup_arch(&command_line);
    mm_init_cpumask(&init_mm);
.....
    setup_per_cpu_areas();
.....

    build_all_zonelists(NULL, NULL);
    page_alloc_init();
.....

    /*
     * These use large bootmem allocations and must precede
     * mem_init();
     * kmem_cache_init();
     */
    mm_init();
.....
    kmem_cache_init_late();
.....
	kmemleak_init();
    setup_per_cpu_pageset();
.....
    rest_init();
}
函数功能
setup_arch是一个特定于体系结构的设置函数, 其中一项任务是负责初始化自举分配器
mm_init_cpumask初始化CPU屏蔽字
setup_per_cpu_areas给每个CPU分配内存,拷贝.data.percpu段的数据. 为系统中的每个CPU的per_cpu变量申请空间
build_all_zonelists建立并初始化结点和内存域的数据结构
mm_init建立了内核的内存分配器, 停用bootmem分配器并迁移到实际的内存管理器(比如伙伴系统)然后调用kmem_cache_init函数初始化内核内部用于小块内存区的分配器
kmem_cache_init_late在kmem_cache_init之后, 完善分配器的缓存机制, 当前3个可用的内核内存分配器,slab,slob,slub都会定义此函数
kmemleak_initKmemleak工作于内核态,Kmemleak 提供了一种可选的内核泄漏检测,其方法类似于跟踪内存收集器。当独立的对象没有被释放时,其报告记录在/sys/kernel/debug/kmemleak中, Kmemcheck能够帮助定位大多数内存错误的上下文
setup_per_cpu_pageset初始化CPU高速缓存行, 为pagesets的第一个数组元素分配内存, 换句话说, 其实就是第一个系统处理器分配由于在分页情况下,每次存储器访问都要存取多级页表,这就大大降低了访问速度。所以,为了提高速度,在CPU中设置一个最近存取页面的高速缓存硬件机制,当进行存储器访问时,先检查要访问的页面是否在高速缓存中.

 

1. setup_arch函数初始化内存流程

在 setup_arch 中,设置了与处理器相关的部分,这里我们关注内存相关的部分:

void __init setup_arch(char **cmdline_p)
{
	/* 扫描内存并通过 memblock_add 函数将内存添加到 memblock 的 memory */
	setup_machine_fdt(__atags_pointer);

	/*  初始化memblock  */
	arm_memblock_init();

	/*  分页机制初始化  */
	paging_init();

	bootmem_init();
}

该函数主要执行了如下操作:

  1. 使用 arm_memblock_init 来完成memblock机制的初始化工作, 至此 memblock 分配器接受系统中系统中内存的分配工作

  2. 调用 paging_init 来完成系统分页机制的初始化工作, 建立页表, 从而内核可以完成虚拟内存的映射和转换工作

  3. 最后调用 bootmem_init 来完成实现 buddy 内存管理所需要的工作

在内存管理还没有起来之前,就要用到内存,这部分是通过早期的简易内存管理系统 memblock 来进行管理,总体的流程如下:

start_kernel()
    |---->page_address_init()
    |     考虑支持高端内存
    |     业务:初始化page_address_pool链表;
    |          将page_address_maps数组元素按索引降序插入
    |          page_address_pool链表; 
    |          初始化page_address_htable数组.
    | 
    |---->setup_arch(&command_line);
    |     初始化特定体系结构的内容
    	  |
    	  |---->arm_memblock_init();
          |     初始化引导阶段的内存分配器memblock
----------|----------------------------------------------------------------------
          |---->paging_init(); <<<<---- [当前位置] <<<<---- vv
          |     分页机制初始化  <<<<---- [当前位置] <<<<---- vv
----------|----------------------------------------------------------------------
          |
          |---->bootmem_init();   
          |     始化内存数据结构包括内存节点, 内存域和页帧page
                |
                |---->arm64_numa_init();
                |     支持numa架构
                |
                |---->zone_sizes_init(min, max);
                    来初始化节点和管理区的一些数据项
                    |
                    |---->free_area_init_node
                    |   初始化内存节点
                    |
                        |---->free_area_init_core
                            |	初始化zone
                            |
                            |---->memmap_init
                            |	初始化page页面
                |
                |---->memblock_dump_all();
                |   初始化完成, 显示memblock的保留的所有内存信息
         	   |
    |---->build_all_zonelist()
    |     为系统中的zone建立后备zone的列表.
    |     所有zone的后备列表都在
    |     pglist_data->node_zonelists[0]中;
    |
    |     期间也对per-CPU变量boot_pageset做了初始化. 
    |

关于早期的 memblock 的内存管理,参考文章:Linux 内存管理窥探(7):内存初始化(memblock)

 

2. paging_init 分页机制初始化

接下来就是分页机制的初始化:

void __init paging_init(const struct machine_desc *mdesc)
{
	void *zero_page;

	prepare_page_table();
	map_lowmem();
	memblock_set_current_limit(arm_lowmem_limit);
	dma_contiguous_remap();
	early_fixmap_shutdown();
	devicemaps_init(mdesc);
	kmap_init();
	tcm_init();

	top_pmd = pmd_off_k(0xffff0000);

	/* allocate the zero page. */
	zero_page = early_alloc(PAGE_SIZE);

	bootmem_init();

	empty_zero_page = virt_to_page(zero_page);
	__flush_dcache_page(NULL, empty_zero_page);

	/* Compute the virt/idmap offset, mostly for the sake of KVM */
	kimage_voffset = (unsigned long)&kimage_voffset - virt_to_idmap(&kimage_voffset);
}

 

2.1 prepare_page_table 清除一级页表

在内存使用之前,需要首先清理页表信息,在 prepare_page_table 函数做了这个事情:

static inline void prepare_page_table(void)
{
	unsigned long addr;
	phys_addr_t end;

	/*
	 * Clear out all the mappings below the kernel image.
	 */
	for (addr = 0; addr < MODULES_VADDR; addr += PMD_SIZE)
		pmd_clear(pmd_off_k(addr));

#ifdef CONFIG_XIP_KERNEL
	/* The XIP kernel is mapped in the module area -- skip over it */
	addr = ((unsigned long)_exiprom + PMD_SIZE - 1) & PMD_MASK;
#endif
	for ( ; addr < PAGE_OFFSET; addr += PMD_SIZE)
		pmd_clear(pmd_off_k(addr));

	/*
	 * Find the end of the first block of lowmem.
	 */
	end = memblock.memory.regions[0].base + memblock.memory.regions[0].size;
	if (end >= arm_lowmem_limit)
		end = arm_lowmem_limit;

	/*
	 * Clear out all the kernel space mappings, except for the first
	 * memory bank, up to the vmalloc region.
	 */
	for (addr = __phys_to_virt(end);
	     addr < VMALLOC_START; addr += PMD_SIZE)
		pmd_clear(pmd_off_k(addr));
}

这里对三段地址使用 pmd_clear 来清理了一级页表的内容:

  • 0 ~ MODULES_VADDR
  • MODULES_VADDR ~ PAGE_OFFSET
  • arm_lowmem_limit ~ VMALLOC_START

 

2.2 map_lowmem 页表映射

static void __init map_lowmem(void)
{
	struct memblock_region *reg;
	phys_addr_t kernel_x_start = round_down(__pa(KERNEL_START), SECTION_SIZE);
	phys_addr_t kernel_x_end = round_up(__pa(__init_end), SECTION_SIZE);

	/* Map all the lowmem memory banks. */
	for_each_memblock(memory, reg) {
		phys_addr_t start = reg->base;
		phys_addr_t end = start + reg->size;
		struct map_desc map;

		if (memblock_is_nomap(reg))
			continue;

		if (end > arm_lowmem_limit)
			end = arm_lowmem_limit;
		if (start >= end)
			break;

		if (end < kernel_x_start) {
			map.pfn = __phys_to_pfn(start);
			map.virtual = __phys_to_virt(start);
			map.length = end - start;
			map.type = MT_MEMORY_RWX;

			create_mapping(&map);
		} else if (start >= kernel_x_end) {
			map.pfn = __phys_to_pfn(start);
			map.virtual = __phys_to_virt(start);
			map.length = end - start;
			map.type = MT_MEMORY_RW;

			create_mapping(&map);
		} else {
			/* This better cover the entire kernel */
			if (start < kernel_x_start) {
				map.pfn = __phys_to_pfn(start);
				map.virtual = __phys_to_virt(start);
				map.length = kernel_x_start - start;
				map.type = MT_MEMORY_RW;

				create_mapping(&map);
			}

			map.pfn = __phys_to_pfn(kernel_x_start);
			map.virtual = __phys_to_virt(kernel_x_start);
			map.length = kernel_x_end - kernel_x_start;
			map.type = MT_MEMORY_RWX;

			create_mapping(&map);

			if (kernel_x_end < end) {
				map.pfn = __phys_to_pfn(kernel_x_end);
				map.virtual = __phys_to_virt(kernel_x_end);
				map.length = end - kernel_x_end;
				map.type = MT_MEMORY_RW;

				create_mapping(&map);
			}
		}
	}
}

为 Kernel Image 创建内存映射,同时为1G空间的低端内存创建内存映射。

Kernel 的属性为可读可写可执行,低端内存为可读可写。然后调用 create_mapping() 来创建直接的物理映射。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值