zone初始化

内核采用区块zone的方式管理内存。

通常情况下内核的zone分为ZONE_DMA,ZONE_DMA32,ZONE_NORMAL,ZONE_HIGHMEM

vexpress,linux3.16上面我验了一下,其实就只有zone_type:ZONE_NORMAL,ZONE_MOVABLE

因此__MAX_NR_ZONES=2

enum zone_type {
#ifdef CONFIG_ZONE_DMA
    ZONE_DMA,
#endif
#ifdef CONFIG_ZONE_DMA32
    ZONE_DMA32,
#endif
    ZONE_NORMAL,
#ifdef CONFIG_HIGHMEM
    ZONE_HIGHMEM,
#endif
    ZONE_MOVABLE,
    __MAX_NR_ZONES
};

zone初始化函数在bootmem_init中完成,因此需要确定每个zone的范围。在find_limits函数中会计算出min_low_pfn,max_low_pfn和max_pfn3个值。

 同样也是通过memblock子系统(这个时候是通过memblock管理内存)获得起始地址、结束地址的page number

static void __init find_limits(unsigned long *min, unsigned long *max_low,
			       unsigned long *max_high)
{
	*max_low = PFN_DOWN(memblock_get_current_limit());
	*min = PFN_UP(memblock_start_of_DRAM());
	*max_high = PFN_DOWN(memblock_end_of_DRAM());
}
phys_addr_t __init_memblock memblock_get_current_limit(void)
{
	return memblock.current_limit;
}

其中min_low_pfn是内存块的开始地址页帧号(0x60000000/0x1000,即物理内存起始地址为0x60000000,4k为1页,则对应的物理页帧号为0x60000);

max_low_pfn表示ZONE_NORMAL区域的结束页帧号(我这里是0xa0000),书上说这个值是根据arm_lowmem_limit而来的。那ZONE_NORMAL是对应了低端内存嘛?

;max_pfn是内存块的的结束地址页帧号(0xa0000)

(min_low_pfn,max_low_pfn)这个范围的物理内存是zone_normal(属于低端内存,低端内存包含了zone_dma和zone_normal)

(max_low_pfn,max_pfn)是属于高端内存,zone_highmem。只不过在本例中高端内存的长度为0

	min_low_pfn = min;//系统中可用的最小页帧号
	max_low_pfn = max_low;//低端内存标识的最大页帧号
	max_pfn = max_high;//系统中可以的最大页帧号

paging_init-->bootmem_init

在setup_arch中通过paging_init函数初始化内核分页机制之后, 内核通过bootmem_init()开始完成内存结点和内存区域的初始化工作

void __init bootmem_init(void)
{
	unsigned long min, max_low, max_high;
	memblock_allow_resize();
	max_low = max_high = 0;
    /* 通过memblock拿到limit,DRAM的起始地址的页面号 */
	find_limits(&min, &max_low, &max_high);
	printk(KERN_EMERG "\r\nmin %lx, max_low %lx, max_high %lx\n", \
	min, max_low, max_high);
	arm_memory_present();
	sparse_init();
    /* zone_sizes_init()来初始化节点和管理区的一些数据项 */
	zone_sizes_init(min, max_low, max_high);
	min_low_pfn = min;//系统中可用的最小页帧号
	max_low_pfn = max_low;//低端内存标识的最大页帧号
	max_pfn = max_high;//系统中可以的最大页帧号
}

低端内存,高端内存和zone_type的关系

低端内存包含了zone_dma和zone_normal;高端内存则对应zone_highmem。参考链接https://www.jianshu.com/p/f964da9036a0

  • ZONE_DMA:范围是0 ~ 16M,该区域的物理页面专门供I/O设备的DMA使用。之所以需要单独管理DMA的物理页面,是因为DMA使用物理地址访问内存,不经过MMU,并且需要连续的缓冲区,所以为了能够提供物理上连续的缓冲区,必须从物理地址空间专门划分一段区域用于DMA。
  • ZONE_NORMAL:范围是16M ~ 896M,该区域的物理页面是内核能够直接使用的。这部分的物理内存是启动的时候经过线性映射,映射完成的
  • ZONE_HIGHMEM:范围是896M ~ 结束,该区域即为高端内存,内核不能直接使用。在使用的时候进行动态映射。

 由于我这边版本只包含了ZONE_NORMAL,ZONE_MOVABLE。因此上图的DMA和zone_higmem是没有的。

ZONE_NORMAL在虚拟地址空间的范围是从0x80000000-0xc0000000

PAGE_OFFSET:0X80000000(内核虚拟地址空间起始地址??)

PHY_OFFSET:0x60000000(未进行验证。是因为物理内存起始地址不是从0开始嘛?) 

由于未定义 CONFIG_ZONE_DMA,CONFIG_ZONE_DMA32等。最后函数zone_sizes_init简化如下:

paging_init-->bootmem_init-->zone_sizes_init

传入参数min=0x60000,max_low=0xa0000, max_high=0xa0000

static void __init zone_sizes_init(unsigned long min, unsigned long max_low,
	unsigned long max_high)
{
	unsigned long zone_size[MAX_NR_ZONES], zhole_size[MAX_NR_ZONES];
	struct memblock_region *reg;
	printk(KERN_EMERG "\r\n MAX_NR_ZONES is %u\n", MAX_NR_ZONES);

	memset(zone_size, 0, sizeof(zone_size));
    /*
     看来zone_size是保存各个zone的page数量
    从传入参数看zone_size[0] = 0xa0000 - 0x60000 = 0x40000
     */
	zone_size[0] = max_low - min;

	memcpy(zhole_size, zone_size, sizeof(zhole_size));
	for_each_memblock(memory, reg) {
		unsigned long start = memblock_region_memory_base_pfn(reg);
		unsigned long end = memblock_region_memory_end_pfn(reg);
        printk(KERN_EMERG "\r\n %s: start is %lx, end is %lx\n", \
			__FUNCTION__, start, end);
		if (start < max_low) {
			unsigned long low_end = min(end, max_low);
            /* 从这里可以看到起始zhole_size[0] = 0 */
			zhole_size[0] -= low_end - start;
		}
	}
    /* 
    用来针对特定的节点进行初始化
	0:UMA是有一个节点
	min:zone_normal的起始页帧号pfn
	*/
	free_area_init_node(0, zone_size, min, zhole_size);
}

 可以看到MAX_NR_ZONES其实就对应了__MAX_NR_ZONES(应该是哪里赋值了)

那么对于数组zone_size[MAX_NR_ZONES], zone_size[0]就对应了zone_normal.

memblock子系统里面的动态内存只有一个,即dts内的memory节点,因此只循环一次。该节点的起始页帧号为0x60000,结束页帧号为0xa0000.

free_area_init_node传入参数nid = 0(因为对于UMA只存在一个节点) zones_size[2] = {0x40000,0}

node_start_pfn = 0x60000, zholes_size[2] = {0,0}

void __paginginit free_area_init_node(int nid, unsigned long *zones_size,
		unsigned long node_start_pfn, unsigned long *zholes_size)
{
	/*
	对于NUMA系统包含了多个节点。对同一节点内存的访问,时间是相同的
	不同节点的内存访问,则所需时间是不同的
	对于UMA系统,也会存在一个节点,其node id = 0
	其中节点使用pg_data_t
	*/
	pg_data_t *pgdat = NODE_DATA(nid);
	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->classzone_idx);

	pgdat->node_id = nid;
	/* 初始化时node_start_pfn=0x60000 */
	pgdat->node_start_pfn = node_start_pfn;

	/* 对节点长度和可用page数进行初始化 */
	calculate_node_totalpages(pgdat, start_pfn, end_pfn,
				  zones_size, zholes_size);

	alloc_node_mem_map(pgdat);
#ifdef CONFIG_FLAT_NODE_MEM_MAP
	printk(KERN_DEBUG "free_area_init_node: node %d, pgdat %08lx, node_mem_map %08lx\n",
		nid, (unsigned long)pgdat,
		(unsigned long)pgdat->node_mem_map);
#endif
	/* 真正初始化zone,填充节点pgdat的Zone */
	free_area_init_core(pgdat, start_pfn, end_pfn,
			    zones_size, zholes_size);
}

 计算每一个zone的总页数和实际页数(不包含空洞),以及内存节点的总页数和实际页数(不包含空洞),因此每个节点都是有各自的Zone

/* 用来计算每一个zone的总页数和实际页数(不包含空洞),以及内存节点的总页数和实际页数(不包含空洞) */
static void __meminit calculate_node_totalpages(struct pglist_data *pgdat,
						unsigned long node_start_pfn,
						unsigned long node_end_pfn,
						unsigned long *zones_size,
						unsigned long *zholes_size)
{
	unsigned long realtotalpages, totalpages = 0;
	enum zone_type i;

	for (i = 0; i < MAX_NR_ZONES; i++)
	{
		totalpages += zone_spanned_pages_in_node(pgdat->node_id, i,
							 node_start_pfn,
							 node_end_pfn,
							 zones_size);
		printk(KERN_EMERG "calculate_node_totalpages: zones_size[%u]= %lx, totalpages:%lx \n", i,zones_size[i], totalpages);
	}
    /* 网上说这个是node里面不连续的page数量 */
	pgdat->node_spanned_pages = totalpages;

	realtotalpages = totalpages;
	for (i = 0; i < MAX_NR_ZONES; i++)
	{
		realtotalpages -=
			zone_absent_pages_in_node(pgdat->node_id, i,
						  node_start_pfn, node_end_pfn,
						  zholes_size);
		printk(KERN_EMERG "calculate_node_totalpages: zholes_size[%u]= %lx, realtotalpages:%lx \n", i,zholes_size[i], realtotalpages);	
	}
    /* 这个是真实可用的page数量 */
	pgdat->node_present_pages = realtotalpages;
	printk(KERN_EMERG "On node %d totalpages: %lu\n", pgdat->node_id,
							realtotalpages);
}

 可以看到该节点没有空洞区域,总共包含了0x40000=262144个page。

在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_node_mem_map)
我理解的是mem_map包含了所有的物理内存,自然每个node所包含的内存也在里面。因此每个node的成员node_mem_map指向了数组的一部分(我以为直接是数组下标或者对应下标的地址之类的)。但是看函数实现,感觉不是这样的。。。但是我也不知道怎么和全局变量mem_map联系上去的。。。

另外这个node_mem_map是如何和4k的物理内存建立关系的内?明天继续看看

还是说这根本就是两个不同的东西管理内存的不同方式啊?内核有一个全局变量struct page * mem_map管理所有的物理内存。而node有一个struct page* node_mem_map管理该node对应的物理内存。

alloc_node_mem_map只是把管理page的数组分配了出来,但是还没有为他们建立对应的映射关系

static void __init_refok alloc_node_mem_map(struct pglist_data *pgdat)
{
	/* Skip empty nodes */
	if (!pgdat->node_spanned_pages)
		return;

#ifdef CONFIG_FLAT_NODE_MEM_MAP
	/* ia64 gets its own node_mem_map, before this, without bootmem */
	if (!pgdat->node_mem_map) {
		unsigned long size, start, 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.
		 */
		 /* 向下取整 MAX_ORDER_NR_PAGES 打印出来是1024*/
		printk(KERN_EMERG "MAX_ORDER_NR_PAGES %lu, ", MAX_ORDER_NR_PAGES);
		start = pgdat->node_start_pfn & ~(MAX_ORDER_NR_PAGES - 1);
		end = pgdat_end_pfn(pgdat);
		end = ALIGN(end, MAX_ORDER_NR_PAGES);
		/* 数组map是用来存放struct page的结构,数组大小就是page个数*page结构体大小 */
		size =  (end - start) * sizeof(struct page);
		map = alloc_remap(pgdat->node_id, size);
		if (!map)
			map = memblock_virt_alloc_node_nopanic(size,
							       pgdat->node_id);
		/* 对齐之后可能map最开始的部分可能是无效的 */
		pgdat->node_mem_map = map + (pgdat->node_start_pfn - start);
	}
#ifndef CONFIG_NEED_MULTIPLE_NODES
	/*
	 * 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;
#ifdef CONFIG_HAVE_MEMBLOCK_NODE_MAP
		if (page_to_pfn(mem_map) != pgdat->node_start_pfn)
			mem_map -= (pgdat->node_start_pfn - ARCH_PFN_OFFSET);
#endif /* CONFIG_HAVE_MEMBLOCK_NODE_MAP */
	}
#endif
#endif /* CONFIG_FLAT_NODE_MEM_MAP */
}

感觉是当前节点在对齐之后,包含了size个page,因此需要分配size*sizeof(struct page)的内存来管理这么多的page。 

 当前环境是UMA,没有CONFIG_NEED_MULTIPLE_NODES,只有一个node 0。可以看到对应UMA,全局变量mem_map就是node 0的node_mem_map。原来这句话(如果系统只有一个pglist_data对象,那么此对象下的node_mem_map即为全局对象mem_map。)是这个意思。

 

 free_area_init_core:对当前节点的node(pgdat->node_zones + j)遍历各个zone进行初始化:

        1、计算zone的总共的page数量size, realsize,管理该zone所有page,所需要的struct page的page数memmap_pages,空闲page数freesize

        freesize = 总的page数-memmap_pages-dma_reserve区域

        

         2、初始化zone的其他变量和锁等等

static void __paginginit free_area_init_core(struct pglist_data *pgdat,
		unsigned long node_start_pfn, unsigned long node_end_pfn,
		unsigned long *zones_size, unsigned long *zholes_size)
{
	enum zone_type j;
	int nid = pgdat->node_id;
	unsigned long zone_start_pfn = pgdat->node_start_pfn;
	int ret;

	pgdat_resize_init(pgdat);
#ifdef CONFIG_NUMA_BALANCING
	spin_lock_init(&pgdat->numabalancing_migrate_lock);
	pgdat->numabalancing_migrate_nr_pages = 0;
	pgdat->numabalancing_migrate_next_window = jiffies;
#endif
	init_waitqueue_head(&pgdat->kswapd_wait);
	init_waitqueue_head(&pgdat->pfmemalloc_wait);
	pgdat_page_cgroup_init(pgdat);

	for (j = 0; j < MAX_NR_ZONES; j++) {
		struct zone *zone = pgdat->node_zones + j;
		/* 
		感觉size和realsize是该zone可用的page数量
		freesize是当的空闲page数量
		memmap_pages是需要多少个struct page来管理该zone的所有page
		*/
		unsigned long size, realsize, freesize, memmap_pages;
		size = zone_spanned_pages_in_node(nid, j, node_start_pfn,
						  node_end_pfn, zones_size);
		realsize = freesize = size - zone_absent_pages_in_node(nid, j,
								node_start_pfn,
								node_end_pfn,
								zholes_size);

		/*
		 * 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, realsize);
		if (freesize >= memmap_pages) {
			freesize -= memmap_pages;
			if (memmap_pages)
				printk(KERN_DEBUG
				       "  %s zone: %lu pages used for memmap\n",
				       zone_names[j], memmap_pages);
		} else
			printk(KERN_WARNING
				"  %s zone: %lu pages exceeds freesize %lu\n",
				zone_names[j], memmap_pages, freesize);

		/* Account for reserved pages */
		if (j == 0 && freesize > dma_reserve) {
			freesize -= dma_reserve;
			printk(KERN_EMERG "  %s zone: %lu pages reserved\n",
					zone_names[0], dma_reserve);
		}
		printk(KERN_EMERG "\r\n %s zone: size %lu, realsize, %lu, freesize %lu, memmap_pages %lu\n", \
				zone_names[j], size, realsize, freesize, memmap_pages);
		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;
		printk(KERN_EMERG "\r\n nr_all_pages %lu, nr_kernel_pages %lu\n", \
				nr_all_pages, nr_kernel_pages);
		zone->spanned_pages = size;
		zone->present_pages = realsize;
		/*
		 * 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->managed_pages = is_highmem_idx(j) ? realsize : freesize;
#ifdef CONFIG_NUMA
		zone->node = nid;
		zone->min_unmapped_pages = (freesize*sysctl_min_unmapped_ratio)
						/ 100;
		zone->min_slab_pages = (freesize * sysctl_min_slab_ratio) / 100;
#endif
		zone->name = zone_names[j];
		spin_lock_init(&zone->lock);
		spin_lock_init(&zone->lru_lock);
		zone_seqlock_init(zone);
		zone->zone_pgdat = pgdat;
		zone_pcp_init(zone);

		/* For bootup, initialized properly in watermark setup */
		mod_zone_page_state(zone, NR_ALLOC_BATCH, zone->managed_pages);

		lruvec_init(&zone->lruvec);
		if (!size)
			continue;

		set_pageblock_order();
		setup_usemap(pgdat, zone, zone_start_pfn, size);
		ret = init_currently_empty_zone(zone, zone_start_pfn,
						size, MEMMAP_EARLY);
		BUG_ON(ret);
		memmap_init(size, nid, j, zone_start_pfn);
		zone_start_pfn += size;
	}
}

和之前一样,node里面包含了两个区域分布是zone_normal,zone_movable

对于zone_normal总共包含了262144个page(262144*4096=1G,dts里面也是1G),1G的物理内存需要2048个page来管理。

在当前环境中sizeof(struct page) = 32,总共有262144个page, 那么也需要262144个struct page来管理这么多物理页。因此所需要的字节数是262144 * 32 = 8388608 = 2048个物理页(2048*4096)

setup_usemap:计算和分配pageblock_flags所需大小

static void __init setup_usemap(struct pglist_data *pgdat,
				struct zone *zone,
				unsigned long zone_start_pfn,
				unsigned long zonesize)
{
	printk(KERN_EMERG "func %s, line %d: start_pfn %lx, zonesize %lx\n", __FUNCTION__, __LINE__,
		zone_start_pfn, zonesize);
	unsigned long usemapsize = usemap_size(zone_start_pfn, zonesize);
	printk(KERN_EMERG "func %s, line %d: usemapsize %lx\n", __FUNCTION__, __LINE__,
		 usemapsize);
	zone->pageblock_flags = NULL;
	if (usemapsize)
		zone->pageblock_flags =
			memblock_virt_alloc_node_nopanic(usemapsize,
							 pgdat->node_id);
}

zone_start_pfn=0x60000,zone_size=0x40000

static unsigned long __init usemap_size(unsigned long zone_start_pfn, unsigned long zonesize)
{
	unsigned long usemapsize;
	printk(KERN_EMERG "func %s, line %d: pageblock_nr_pages %lu, pageblock_order %lu, NR_PAGEBLOCK_BITS %lu\n",\
		__FUNCTION__, __LINE__,pageblock_nr_pages, pageblock_order, NR_PAGEBLOCK_BITS);
	zonesize += zone_start_pfn & (pageblock_nr_pages-1);
	usemapsize = roundup(zonesize, pageblock_nr_pages);
	usemapsize = usemapsize >> pageblock_order;
	usemapsize *= NR_PAGEBLOCK_BITS;
	usemapsize = roundup(usemapsize, 8 * sizeof(unsigned long));

	return usemapsize / 8;
}

因此pageblock_flags所需大小为0x80=128字节。当前环境里面没有高端内存,全部都是低端内存,总共1024M。128字节=128*8bit。一个pageblock用4bit表示,一个pageblock大小4M(pageblock_nr_pages= 1024 * 4K = 4M)。那么总共有128*8/4=256个pageblock。256个pageblock所表示的内存为256*4=1024M

free_area_init_core->memmap_init->memmap_init_zone

void __meminit memmap_init_zone(unsigned long size, int nid, unsigned long zone,
		unsigned long start_pfn, enum memmap_context context)
{
	struct page *page;
	unsigned long end_pfn = start_pfn + size;
	unsigned long pfn;
	struct zone *z;

	if (highest_memmap_pfn < end_pfn - 1)
		highest_memmap_pfn = end_pfn - 1;

	z = &NODE_DATA(nid)->node_zones[zone];
	for (pfn = start_pfn; pfn < end_pfn; pfn++) {

		if (context == MEMMAP_EARLY) {
			if (!early_pfn_valid(pfn))
				continue;
			if (!early_pfn_in_nid(pfn, nid))
				continue;
		}
		page = pfn_to_page(pfn);
		set_page_links(page, zone, nid, pfn);
		mminit_verify_page_links(page, zone, nid, pfn);
		init_page_count(page);
		page_mapcount_reset(page);
		page_cpupid_reset_last(page);
		SetPageReserved(page);
		
		if ((z->zone_start_pfn <= pfn)
		    && (pfn < zone_end_pfn(z))
		    && !(pfn & (pageblock_nr_pages - 1)))
			set_pageblock_migratetype(page, MIGRATE_MOVABLE);

		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(pfn << PAGE_SHIFT));
#endif
	}
}

目前上述的过程,可以看到在该过程中free_area链表都被初始化空,里面并没有page被加入到链表中。

page被加入到free_area的过程可以参考伙伴系统杂记_这个我好像学过的博客-CSDN博客

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值