【原创】(五)Linux内存管理zone_sizes_init
背景
-
read the fucking source code!
--by 鲁迅 -
a picture is worth a thousand words.
--by 高尔基
说明:
- kernel版本:4.14
- arm64处理器,contex-a53,双核
- 使用工具:source insight 3.5, visio
1. 介绍
在(四)linux内存模型之sparse memory model中,我们分析了bootmem_init
函数的上半部分,这次让我们来到下半部分吧,下半部分主要是围绕zone_sizes_init
函数展开。
前景回顾:bootmem_init()
函数代码如下:
void __init bootmem_init(void) { unsigned long min, max; min = pfn_up(memblock_start_of_dram()); max = pfn_down(memblock_end_of_dram()); early_memtest(min << page_shift, max << page_shift); max_pfn = max_low_pfn = max; arm64_numa_init(); /* * sparsemem tries to allocate bootmem in memory_present(), so must be * done after the fixed reservations. */ arm64_memory_present(); sparse_init(); zone_sizes_init(min, max); memblock_dump_all(); }
在linux中,物理内存地址区域采用zone
来管理。不打算来太多前戏了,先上一张zone_sizes_init
的函数调用图吧:
需要再说明一点是,使用的是arm64,uma(只有一个node)
,此外,流程分析中那些没有打开的宏,相应的函数就不深入分析了。开始探索吧!
2. 数据结构
关键的结构体如上图所示。
在numa
架构下,每一个node
都会对应一个struct pglist_data
,在uma
架构中只会使用唯一的一个struct pglist_data
结构,比如我们在arm64 uma
中使用的全局变量struct pglist_data __refdata contig_page_data
。
struct pglist_data 关键字段
struct zone node_zones[]; //对应的zone区域,比如zone_dma,zone_normal等 struct zonelist_node_zonelists[]; unsigned long node_start_pfn; //节点的起始内存页面帧号 unsigned long node_present_pages; //总共可用的页面数 unsigned long node_spanned_pages; //总共的页面数,包括有空洞的区域 wait_queue_head_t kswapd_wait; //页面回收进程使用的等待队列 struct task_struct *kswapd; //页面回收进程
struct zone 关键字段
unsigned long watermark[]; //水位值,wmark_min/wmark_lov/wmark_high,页面分配器和kswapd页面回收中会用到 long lowmem_reserved[]; //zone中预留的内存 struct pglist_data *zone_pgdat; //执行所属的pglist_data struct per_cpu_pageset *pageset; //per-cpu上的页面,减少自旋锁的争用 unsigned long zone_start_pfn; //zone的起始内存页面帧号 unsigned long managed_pages; //被buddy system管理的页面数量 unsigned long spanned_pages; //zone中总共的页面数,包含空洞的区域 unsigned long present_pages; //zone里实际管理的页面数量 struct frea_area free_area[]; //管理空闲页面的列表
宏观点的描述:struct pglist_data
描述单个node的内存(uma
架构中的所有内存),然后内存又分成不同的zone
区域,zone
描述区域内的不同页面,包括空闲页面,buddy system
管理的页面等。
3. zone
上个代码吧:
enum zone_type { #ifdef config_zone_dma /* * zone_dma is used when there are devices that are not able * to do dma to all of addressable memory (zone_normal). then we * carve out the portion of memory that is needed for these devices. * the range is arch specific. * * some examples * * architecture limit * --------------------------- * parisc, ia64, sparc <4g * s390 <2g * arm various * alpha unlimited or 0-16mb. * * i386, x86_64 and multiple other arches * <16m. */ zone_dma, #endif #ifdef config_zone_dma32 /* * x86_64 needs two zone_dmas because it supports devices that are * only able to do dma to the lower 16m but also 32 bit devices that * can only do dma areas below 4g. */ zone_dma32, #endif /* * normal addressable memory is in zone_normal. dma operations can be * performed on pages in zone_normal if the dma devices support * transfers to all addressable memory. */ zone_normal, #ifdef config_highmem /* * a memory area that is only addressable by the kernel through * mapping portions into its own address space. this is for example * used by i386 to allow the kernel to address the memory beyond * 900mb. the kernel will set up special mappings (page * table entries on i386) for each page that the kernel needs to * access. */ zone_highmem, #endif zone_movable, #ifdef config_zone_device zone_device, #endif __max_nr_zones };
通用内存管理要应对各种不同的架构,x86,arm,mips...,为了减少复杂度,只需要挑自己架构相关的。目前我使用的平台,只配置了zone_dma
和zone_normal
。log输出如下图:
为什么没有zone_normal
区域内,跟踪一通代码发现,zone_dma
区域设置的大小是从起始内存开始的4g区域并且不能超过4g边界区域,而我使用的内存为512m,所以都在这个区域内了。
从上述结构体中可以看到,zone_dma
是由宏定义的,zone_normal
才是所有架构都有的区域,那么为什么需要一个zone_dma
区域内,来张图:
所以,如果所有设备的寻址范围都是在内存的区域内的话,那么一个zone_normal
是够用的。
4. calculate_node_totalpages
这个从名字看就很容易知道是为了统计node
中的页面数,一张图片解释所有:
- 前边的文章分析过,物理内存由
memblock
维护,整个内存区域,是有可能存在空洞区域,也就是图中的hole
部分; - 针对每个类型的
zone
区域,分别会去统计跨越的page frame
,以及可能存在的空洞,并计算实际可用的页面present_pages
; -
node
管理各个zone
,它的spanned_pages
和present_pages
是统计各个zone
相应页面之和。
这个过程计算完,基本就把页框的信息纳入管理了。
5. free_area_init_core
简单来说,free_area_init_core
函数主要完成struct pglist_data
结构中的字段初始化,并初始化它所管理的各个zone
,看一下代码吧:
/* * set up the zone data structures: * - mark all pages reserved * - mark all memory queues empty * - clear the memory bitmaps * * note: pgdat should get zeroed by caller. */ static void __paginginit free_area_init_core(struct pglist_data *pgdat) { enum zone_type j; int nid = pgdat->node_id; 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 #ifdef config_transparent_hugepage spin_lock_init(&pgdat->split_queue_lock); init_list_head(&pgdat->split_queue); pgdat->split_queue_len = 0; #endif init_waitqueue_head(&pgdat->kswapd_wait); init_waitqueue_head(&pgdat->pfmemalloc_wait); #ifdef config_compaction init_waitqueue_head(&pgdat->kcompactd_wait); #endif pgdat_page_ext_init(pgdat); spin_lock_init(&pgdat->lru_lock); lruvec_init(node_lruvec(pgdat)); pgdat->per_cpu_nodestats = &boot_nodestats; for (j = 0; j < max_nr_zones; j++) { struct zone *zone = pgdat->node_zones + j; unsigned long size, realsize, freesize, memmap_pages; unsigned long zone_start_pfn = zone->zone_start_pfn; size = zone->spanned_pages; realsize = 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, realsize); if (!is_highmem_idx(j)) { 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 pr_warn(" %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_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->managed_pages = is_highmem_idx(j) ? realsize : freesize; #ifdef config_numa zone->node = nid; #endif zone->name = zone_names[j]; zone->zone_pgdat = pgdat; spin_lock_init(&zone->lock); zone_seqlock_init(zone); zone_pcp_init(zone); if (!size) continue; set_pageblock_order(); setup_usemap(pgdat, zone, zone_start_pfn, size); init_currently_empty_zone(zone, zone_start_pfn, size); memmap_init(size, nid, j, zone_start_pfn); } }
- 初始化
struct pglist_data
内部使用的锁和队列;
遍历各个zone
区域,进行如下初始化:
根据
zone
的spanned_pages
和present_pages
,调用calc_memmap_size
计算管理该zone
所需的struct page
结构所占的页面数memmap_pages
;zone
中的freesize
表示可用的区域,需要减去memmap_pages
和dma_reserve
的区域,如下图在开发板的log打印所示:memmap
使用2048
页,dma
保留0页;计算
nr_kernel_pages
和nr_all_pages
的数量,为了说明这两个参数和页面的关系,来一张图(由于我使用的平台只有一个zone_dma
区域,且arm64
没有zone_highmem
区域,不具备典型性,故以arm32
为例):初始化
zone
使用的各类锁;分配和初始化
usemap
,初始化buddy system
中使用的free_area[]
,lruvec
,pcp
等;memmap_init()->memmap_init_zone()
,该函数主要是根据pfn
,通过pfn_to_page
找到对应的struct page
结构,并将该结构进行初始化处理,并设置migrate_movable
标志,表明可移动;
最后,当我们回顾bootmem_init
函数时,发现它基本上完成了linux物理内存框架的初始化,包括node
, zone
, page frame
,以及对应的数据结构等。
结合上篇文章(四)linux内存模型之sparse memory model阅读,效果会更佳噢!
持续中...
下一篇: 叫个高级技师
推荐阅读
-
详解Linux内核内存管理架构
-
【原创】(十)Linux内存管理 - zoned page frame allocator - 5
-
【原创】(十四)Linux内存管理之page fault处理
-
【原创】(二)Linux物理内存初始化
-
【原创】(八)Linux内存管理 - zoned page frame allocator - 3
-
Linux内核设备驱动之内存管理笔记整理
-
【原创】(十二)Linux内存管理之vmap与vmalloc
-
【原创】(九)Linux内存管理 - zoned page frame allocator - 4
-
【原创】(四)Linux内存模型之Sparse Memory Model
-
Linux系统基本的内存管理知识讲解