文章目录

  • Linux Memory初始化过程
  • start_kernel()
  • setup_arch()
  • boot memeory allocator初始化
  • 页表初始化
  • Zone初始化以及buddy allocator初始化
  • mem_init()
  • kmem_cache_init()


Linux Memory初始化过程

start_kernel()

首先我们来看看Linux的系统启动函数中的start_kernel()中与memory相关的处理步骤:

asmlinkage void __init start_kernel(void)
{
    ...........
 	setup_arch(&command_line);
	.......................
	build_all_zonelists();//create zone fallback order
	page_alloc_init();//register callback
    ...............
	mem_init(); //boot memory retire,create buddy allocator
	kmem_cache_init();//init slab allocator
    ...........
	anon_vma_init();//create a vma slab
    .................
}

从start_kernel()函数,memory相关的步骤,可以大致分为三大块:

  1. setup_arch(): boot memory allocator初始化 和直接映射和固定映射的页表创建,然后再 启动分页单元,再初始化每个Node下的Zone
  2. mem_init(): 将boot memory allocator退休,将已经在boot memory阶段使用的memory标示为reserve,将未使用的memory以page为单位释放给buddy allocator
  3. keme_cache_init():初始化slab allocator的原始slab:cache_cache,在此过后,slab allocator开始使用。

Memory_max_taget 修改后服务器起不来_初始化

setup_arch()

在setup_arch() 函数下,做了好多事情,下面来详细讲述下:

boot memeory allocator初始化

下面是boot memory allocator的初始化核心函数,每个node下都有一个boot memory allocator,
然后再初始化boot memory allocator结构pgdat->bdata:

node_boot_start:当前bootmem_data结构中所描述的memory块的起始物理地址。
 node_low_pfn:当前bootmem_data结构中所描述memory块的结束pfn。换句话说,就是该node中ZONE_NORMAL的结束物理地址。
 node_bootmem_map:代表page分配状态的bitmap的地址。
 last_offset:在某个page中上次分配完成之后的偏移。如果为0,则代表该page全部被分配完了。
 last_pos:上次分配所使用的page的PFN。将last_pos和last_offset联合起来,可以测试多个分配是否可以合并到一个page中进行而不是使用一个全新的page
 last_success:上次分配所对应的物理地址
static unsigned long __init init_bootmem_core (pg_data_t *pgdat,
	unsigned long mapstart, unsigned long start, unsigned long end)
{
	bootmem_data_t *bdata = pgdat->bdata;
	unsigned long mapsize = ((end - start)+7)/8;//node_bootmem_map 大小,字节对齐

	pgdat->pgdat_next = pgdat_list;//将node插入到list中
	pgdat_list = pgdat;

	mapsize = (mapsize + (sizeof(long) - 1UL)) & ~(sizeof(long) - 1UL);
	bdata->node_bootmem_map = phys_to_virt(mapstart << PAGE_SHIFT);
	bdata->node_boot_start = (start << PAGE_SHIFT);
	bdata->node_low_pfn = end;

	/*
	 * Initially all pages are reserved - setup_arch() has to
	 * register free RAM areas explicitly.
	 */
	memset(bdata->node_bootmem_map, 0xff, mapsize);

	return mapsize;
}

boot memory是使用node_bootmem_map中的每一bit代表一个page,如果bit为0,则为被分配使用,如果bit是1,则已经被分配使用。
所以boot memory被初始化之后,已经被使用的物理page被标示为reserve,比如boot memory allocator自己使用的内存,以及以及high memory不可使用。

页表初始化

当boot memory allocator初始化之后,就要开始初始化页表项,这些页表项需要用到boot memory allocator来分配内存。

void __init paging_init(void)
{
	pagetable_init();
	load_cr3(swapper_pg_dir);
    .........
	__flush_tlb_all();
	kmap_init();
	zone_sizes_init();
}
static void __init pagetable_init (void)
{
	unsigned long vaddr;
	pgd_t *pgd_base = swapper_pg_dir;
    .........
	kernel_physical_mapping_init(pgd_base); //分配直接映射的page table
	remap_numa_kva();

	/*
	 * Fixed mappings, only the page table structure has to be
	 * created - mappings will be set by set_fixmap():
	 */
	vaddr = __fix_to_virt(__end_of_fixed_addresses - 1) & PMD_MASK;
	page_table_range_init(vaddr, 0, pgd_base);//分配高端内存中固定映射需要的页表

	permanent_kmaps_init(pgd_base);
    .........
}

页表的初始化,被分为两个阶段:
第一阶段,启动初始化阶段,在内核静态代码中有静态定义swapper_pg_dir 为全局pgd,其中内核代码使用物理地址1MB ~ 9MB的这8Mb空间,因此在内核启动阶段,会生成临时内核页表项:pg0和pg1,其中pg0 对应swapper_pg_dir中的第0项和第768项,pg1位第1项和第769项,因此内核启动阶段静态代码在分页机制为启动之前就可以使用临时内核页表所映射的虚拟地址了
第二阶段,虚拟地址低端内存的部分以及虚拟地址高端内存固定映射的页表分配。
其中kernel_physical_mapping_init() 映射低端内存的页表项
而page_table_range_init()分配用用映射高端内存固定映射的部分的页表项。
其中pmd和pte的物理页框的分配用到了boot memory allocator的函数:alloc_bootmem_low_pages()

Zone初始化以及buddy allocator初始化

zone_sizes_init()函数循环初始化每一个node中的各个zone,根据配置项中的配置得知每个node的物理页框范围,然后再计算出各个node下各个zone的物理页框起始位置,和大小,再调用free_area_init_node()来初始化zone.

void __init free_area_init_node(int nid, struct pglist_data *pgdat,
		unsigned long *zones_size, unsigned long node_start_pfn,
		unsigned long *zholes_size)
{
	pgdat->node_id = nid;
	pgdat->node_start_pfn = node_start_pfn;
	calculate_zone_totalpages(pgdat, zones_size, zholes_size);

	if (!pfn_to_page(node_start_pfn))
		node_alloc_mem_map(pgdat); //如果是NUMA,则需要动态分配mem_map的空间,如果是UMA,则直接使用contig_page_data.node_mem_map静态定义的物理地址,alloc_bootmem_node()分配n个struct page大小

	free_area_init_core(pgdat, zones_size, zholes_size);
}
static void __init free_area_init_core(struct pglist_data *pgdat,
		unsigned long *zones_size, unsigned long *zholes_size)
{
	unsigned long i, j;
	const unsigned long zone_required_alignment = 1UL << (MAX_ORDER-1);
	int cpu, nid = pgdat->node_id;
	unsigned long zone_start_pfn = pgdat->node_start_pfn;

	pgdat->nr_zones = 0;
	init_waitqueue_head(&pgdat->kswapd_wait);
	
	for (j = 0; j < MAX_NR_ZONES; j++) {
		struct zone *zone = pgdat->node_zones + j;
		...........
		zone->spanned_pages = size;
		zone->present_pages = realsize;
		zone->name = zone_names[j];
		spin_lock_init(&zone->lock);
		spin_lock_init(&zone->lru_lock);
		zone->zone_pgdat = pgdat;
		zone->free_pages = 0;

		zone->temp_priority = zone->prev_priority = DEF_PRIORITY;
	    ........................
		zone->zone_mem_map = pfn_to_page(zone_start_pfn);
		zone->zone_start_pfn = zone_start_pfn;

		if ((zone_start_pfn) & (zone_required_alignment-1))
			printk("BUG: wrong zone alignment, it will crash\n");

		memmap_init(size, nid, j, zone_start_pfn);//将物理页框对应的struct page初始化
		zone_start_pfn += size;
		zone_init_free_lists(pgdat, zone, zone->spanned_pages);//初始化buddy allocator的free area数组
	}
}
free_pages:当前zone中free page的总个数
 pages_min,pages_low,page_high:该zone中的水位线,具体在下一小节进行说明。
 free_area:buddy allocator使用的free area bitmaps
 wait_table:等待某个page被释放的进程的等待队列链表。这个队列对于wait_on_page()和unlock_page()函数来说非常重要。
 wait_table_size:wait_table中等待队列的个数。
 wait_table_bits:wait_table_size == (1 << wait_table_bits)
 zone_pgdat:指向当前zone所属的node
 zone_mem_map:当前zone中的第一个page的地址,该page输入全局数组mem_map
 size:当前zone中pages 的个数

初始化每个node中mem_map数组中各个zone的struct *page,struct page中的flag字段用来记录当前page数据那个zone,初始化的时候,全部被标记为reserve,当boot memory retire的时候,会把要释放的物理frame再设置为可用状态。

void __init memmap_init_zone(unsigned long size, int nid, unsigned long zone,
		unsigned long start_pfn)
{
	struct page *start = pfn_to_page(start_pfn);
	struct page *page;

	for (page = start; page < (start + size); page++) {
		set_page_zone(page, NODEZONE(nid, zone));
		set_page_count(page, 0);
		reset_page_mapcount(page);
		SetPageReserved(page);
		INIT_LIST_HEAD(&page->lru);
#ifdef WANT_PAGE_VIRTUAL
		/* The shift won't overflow because ZONE_NORMAL is below 4G. */
		if (!is_highmem_idx(zone))
			set_page_address(page, __va(start_pfn << PAGE_SHIFT));
#endif
		start_pfn++;
	}
}
初始化每个Zone中的buddy allocator所使用的free_area[].map所需要的空间
void zone_init_free_lists(struct pglist_data *pgdat, struct zone *zone, unsigned long size)
 {
 int order;
 for (order = 0; ; order++) {
 unsigned long bitmap_size;INIT_LIST_HEAD(&zone->free_area[order].free_list);
	if (order == MAX_ORDER-1) {
		zone->free_area[order].map = NULL;
		break;
	}

	bitmap_size = pages_to_bitmap_size(order, size);
	zone->free_area[order].map =
	  (unsigned long *) alloc_bootmem_node(pgdat, bitmap_size);
}}

mem_init()

主要是调用free_all_bootmem_core()来释放low memory到buddy allocator,set_highmem_pages_init()来释放high memory到buddy allocator。
其中free_all_bootmem_core()的核心步骤如下:
(1)boot memory allocator中未分配的pages:
清除struct page中的PG_reserve的标记位
将count设置为1
调用__free_pages()将memory移交给buddy allocator来构建它的free lists
(2) 释放bitmap中使用过的pages并将这些pages移交给buddy allocator。

kmem_cache_init()

初始化slab allocator的第一个object:cache_cache