linux内存管理 (四) 5 内存管理机制 第二阶段 迈向 第三阶段的过程

前言

界限规定
第二阶段是个时间点 : bootmem 完成建立
第三阶段是个时间点 : buddy 完成建立(A)(主要讨论这个) 或者 linux所有内存管理系统完成建立(B)

第二阶段到 第三阶段 是个过程, 该过程中  bootmem 消亡,buddy 完成建立 

(bootmem 消亡史和buddy建立史 从大得时间格局来看
	是同时进行的)
(bootmem 消亡史和buddy建立史 从小的时间格局来看
	bootmem的消亡史是一瞬间的(free_all_bootmem_core执行完成时)
	buddy  的建立史则是一个过程,(start_kernel -> setup_arch -> paging_init -> bootmem_init -> arm_bootmem_free)(start_kernel -> mm_init -> mem_init)

综述

前言 对buddy的理解
在 一步一步写嵌入式操作系统--ARM编程的方法与实践.pdf 中 写了一个 leeos

leeos 中的 buddy 是 这么做的

struct list_head page_buddy 是一个9个成员的数组,每个数组中有一个链表头
第一个链表头 链 块大小为 2^0*4KB 大小的内存块,可以插入无数个
第二个链表头 链 块大小为 2^1*4KB 大小的内存块,可以插入无数个
...
第九个链表头 链 块大小为 2^8*4KB 大小的内存块,可以插入无数个


其实链的东西不是内存块,而是2^N个 page 结构体变量.结构体类型为struct page



linux 中的配置 为 CONFIG_FORCE_MAX_ZONEORDER=11

错误言论: leeos-buddy 的链表头数组A page_buddy 其实 对应了 linux-buddy架构中的 
contig_page_data -> node_zones[ZONE_NORMAL] -> free_area 

不同的是
	1. 链表头数组成员数量不同
		leeos-buddy 的链表头数组A 有 9 个成员
		linux-buddy 的链表头数组B 有 CONFIG_FORCE_MAX_ZONEORDER(11)个成员
	2. 链表头数组成员数据类型不同
		leeos_buddy 链表头数组A内容为 9个链表头成员
		linux-buddy 链表头数组B内容为 11个链表头数组C , 在 C 中 对 同 order 的 page 根据 MIGRATE_TYPES 分类,5.
			// 意思是 在 leeos 中,同order的page会被挂到一个链表(page_buddy[order])中.
			// 如果 order 为0 ,则会被挂到 page_buddy[0]
			
			// 意思是 在 linux 中,同order的page会根据MIGRATE_TYPES 挂到不同的链表中. 
				// 如果 	1. 只有一个NODE
				// 且 	2. order为0
				// 且 	3. ZONE为 ZONE_NORMAL 
				// 且 	4. MIGRATE_TYPE为MIGRATE_MOVABLE , 则会被挂到
				// contig_page_data.node_zones[ZONE_NORMAL].free_area[0].free_list[MIGRATE_MOVABLE]


leeos-buddy 的链表头数组A page_buddy[0] 其实 对应了 linux-buddy架构中的 
contig_page_data.node_zones[ZONE_NORMAL].free_area[0].free_list[MIGRATE_MOVABLE]

在 leeos-buddy 中, buddy 初始化的最主要工作是 	往这个链表头数组中插入 struct page 结构体.
在 linux-buddy 中, buddy 初始化的最主要工作也是 往这个链表头数组中插入 struct page 结构体.


contig_page_data -> node_zones[ZONE_NORMAL] -> free_area -> free_list 这四个变量的结构体分别是

struct pglist_data(pg_data_t) -> struct zone[MAX_NR_ZONES] -> struct free_area[MAX_ORDER] -> struct list_head[MIGRATE_TYPES]
1. buddy 何时建立
  • buddy何时建立
是从这个函数开始的
start_kernel -> setup_arch -> 
	paging_init -> bootmem_init -> 
		arm_bootmem_free

完成建立的时间点
start_kernel -> mm_init -> mem_init(这个函数返回时)
  • bootmem 何时消亡
开始准备消亡是从下面开始的(这时候开始初始化buddy相关变量)
	start_kernel -> setup_arch -> 
		paging_init -> bootmem_init -> 
			arm_bootmem_free

消亡的时间点 是 
	start_kernel -> mm_init -> mem_init -> free_all_bootmem -> free_all_bootmem_core(这个函数返回时)
A. buddy 的建立过程的第一部分(arm_bootmem_free)
--------------------------------------------------------------------------------------------------------------
			buddy 的建立过程中的第一部分,buddy建立过程中还使用了 bootmem 分配器
--------------------------------------------------------------------------------------------------------------

根据三个主要的结构体来看 buddy
	struct pglist_data(pg_data_t)
	struct zone
	struct free_area

内核调用流程
start_kernel 
	-> setup_arch 
		-> paging_init 
			-> bootmem_init 
				-> arm_bootmem_free
					-> free_area_init_node (设置 struct pglist_data) 				//--------------------------------1
						-> 设置 contig_page_data 成员 (node_id node_start_pfn node_spanned_pages node_present_pages node_mem_map)  // ----------A
						-> free_area_init_core (设置 struct zone)					//--------------------------------2
							-> 设置 node_zones 成员 (pageset)
							-> init_currently_empty_zone
								-> zone_init_free_lists (设置 struct free_area) 	//--------------------------------3
									-> INIT_LIST_HEAD(&zone->free_area[order].free_list[t]);  //--------------------------------4
							-> memmap_init // memmap_init_zone
								-> SetPageReserved(page);  //--------------------------------5											// ----------B


// 1 初始化了 struct pglist_data(pg_data_t)
// 2 初始化了 struct zone 
// 3 初始化了 struct free_area
// 4 初始化了 struct list_head free_list 
// 5 将每一个page 结构体 设置为保留状态(已经使用) // 所以只需要 在 mem_init 中 将 需要 没被使用过的 的 free 一下就行了.

// A 初始化了 node_mem_map , 也是 mem_map, 是调用 函数 alloc_node_mem_map(pgdat); 做的,申请的空间用来存储所有的page结构体
// B 初始化 保存在全局变量 mem_map 中的物理内存的struct page实例 :(memmap_init)memmap_init_zone // 空闲页的数目free_area.nr_free当前仍然规定为0,直至停用bootmem分配器、普通的伙伴系统分配器生效时,才会设置正确的数值

  • ok6410 实例
zone_size[0]:0x10000
zone_size[1]:0x0
zhole_size[0]:0x0
zhole_size[1]:0x0
pgdat->node_id:0x0
pgdat->node_start_pfn:0x50000
pgdat->node_spanned_pages:0x10000
pgdat->node_present_pages:0x10000

alloc_node_mem_map
	// 在第三阶段中所有的物理内存都用struct page结构来描述,这些对象以数组形式存放,而这个数组的地址就是 pgdat->node_mem_map(如果只有一个NODE,数组地址也存储到 mem_map)
	pgdat->node_mem_map:0xc08ce000(对应 508C E000)
		// 此次 struct page结构体数组的申请是用的 bootmem分配器,也是 bootmem分配器 第一次被使用的 场景
		// start = pgdat->node_start_pfn & ~(MAX_ORDER_NR_PAGES - 1);
		// end = pgdat->node_start_pfn + pgdat->node_spanned_pages;
		// end = ALIGN(end, MAX_ORDER_NR_PAGES);
		// size =  (end - start) * sizeof(struct page);
		// map = alloc_remap(pgdat->node_id, size); // 没有配置CONFIG_HAVE_ARCH_ALLOC_REMAP,该函数返回NULL
		// if (!map)
		// 		map = alloc_bootmem_node_nopanic(pgdat, size);
		// pgdat->node_mem_map = map + (pgdat->node_start_pfn - start);
	mem_map:0xc08ce000

alloc_node_mem_map
	暂时不分析

high_memory:0xd0000000
max_low_pfn:0x10000
max_pfn:0x10000

为第2部分准备的东西 都在 contig_page_data 中 // 其实bootmem 也用 contig_page_data 中的成员描述
包括
	1. pgdat->node_mem_map
		第三阶段中所有的物理内存都用struct page结构来描述,这些对象以数组形式存放
		这个数组的地址就是 pgdat->node_mem_map(如果只有一个NODE,数组地址也存储到 mem_map)
		且这些page 都已经被 reserved
	2. 其他(待填充)

B. buddy 的建立过程的第二部分(mem_init)(该部分也可被称作bootmem消亡史)

start_kernel 
	-> mm_init 
		-> mem_init
			-> free_unused_memmap(&meminfo); // 物理上不存在地址(hole)对应的页已经存在,此时调用kmemleak_free_part将这些页释放
			-> free_all_bootmem(); // 释放 bootmem 管理的 lowmem (包括bootmem位图数据) 到 buddy (buddy 指的是 contig_page_data->node_zones[1]->pageset 和 contig_page_data->node_zones[3个]->free_area[11个]->free_list[5个])
				-> free_all_bootmem_core
					-> __free_pages_bootmem
						-> __free_pages
			-> free_highpages(); // 释放 memblock 管理的 highmem 到 buddy
				-> free_area
					-> __free_page // #define __free_page(page) __free_pages((page), 0)


	__free_pages // 妇科考虑释放到 pcp 还是 buddy
		// 释放一个页,则先尝试添加到pcp中,超过pcp限制再往buddy系统中添加
		-> free_hot_cold_page(page, 0); 
			struct zone *zone = page_zone(page);
			struct per_cpu_pages *pcp;
		    pcp = &this_cpu_ptr(zone->pageset)->pcp;
		    list_add(&page->lru, &pcp->lists[migratetype]);
	    // 释放多个页走这个
	    // 释放一个页也可走这个???
		-> __free_pages_ok(page, order);
			 free_one_page(page_zone(page), page, order,get_pageblock_migratetype(page)) -> __free_one_page(page, zone, order, migratetype);
				list_add(&page->lru, &zone->free_area[order].free_list[migratetype]);


  • ok6410 现状
如何调试 : bootargs 中 加入 bootmem_debug

Memory: 256MB = 256MB total
Memory: 196548k/196548k available, 65596k reserved, 0K highmem
Virtual kernel memory layout:
    vector  : 0xffff0000 - 0xffff1000   (   4 kB)
    fixmap  : 0xfff00000 - 0xfffe0000   ( 896 kB)
    DMA     : 0xff600000 - 0xffe00000   (   8 MB)
    vmalloc : 0xd0800000 - 0xf4000000   ( 568 MB)
    lowmem  : 0xc0000000 - 0xd0000000   ( 256 MB)
    pkmap   : 0xbfe00000 - 0xc0000000   (   2 MB)
    modules : 0xbf000000 - 0xbfe00000   (  14 MB)
      .init : 0xc0008000 - 0xc0036000   ( 184 kB)
      .text : 0xc0036000 - 0xc0800888   (7979 kB)
      .data : 0xc0802000 - 0xc084af30   ( 292 kB)
       .bss : 0xc084af54 - 0xc08cd554   ( 522 kB)

linux最终的内存管理系统

包括 buddy内存管理系统 和 slab内存管理系统 , 基于 struct page 管理

page 结构 与 物理页相关 ,与 虚拟页无关.

内核用这个结构来管理系统中所有的页, 这个结构的目的在于描述物理内存本身



体系结构中定义了页的大小(体系结构中定义的页用于虚拟内存管理).

内核中定义的page ,用于将所有的物理内存分成N个物理页,一个物理页用一个page结构体描述

page 数目 为 总内存大小/体系结构中页的大小(一般为4KB或8KB)

总的page 占的大小 为  sizeof(struct page) * 总内存大小/体系结构中页的大小(一般为4KB或8KB)
1. slab
  • slab 何时建立
start_kernel -> mm_init -> kmem_cache_init(这个函数返回时)
  • 过程

kmem_cache_init(slab.c)
	kmem_cache_create
		kmem_cache_zalloc
			kmem_cache_alloc
				slab_alloc
					__slab_alloc
					new_slab
						allocate_slab
							alloc_slab_page
								alloc_pages


kmem_cache_init(slub.c)
	kmem_cache = kmem_cache_alloc(kmem_cache, GFP_NOWAIT);
		slab_alloc
	kmalloc_caches[1] = create_kmalloc_cache
		kmem_cache_alloc
			slab_alloc
	kmalloc_caches[2] 


  • 提供的api
kmalloc
2. 其他内存管理系统
  • percpu_init_late
start_kernel----> mm_init---->percpu_init_late

前面pcpu_first_chunk->map pcpu_reserved_chunk->map是静态定义的数组
	因为当时slub机制还没有建立起来不能进行小块内存分配。


percpu_init_late 执行的时候,slub已经建立起来,在函数percpu_init_late中为pcpu_first_chunk->map pcpu_reserved_chunk->map 重新分配内存。
	

  • vmalloc_init
提供的api : vmalloc
基于 slub (kmalloc)

其他

  • per cpu pageset

问题: __free_pages   针对 order 为 0 的 page ,是将其放入了哪里?
思考:
	好像是pcp,pcp是什么?
	list_add(&page->lru, &pcp->lists[migratetype]);

per-cpu的冷热页链表

 struct zone结构有一个pageset[]成员:

    struct zone {
       ......
    struct per_cpu_pageset  pageset[NR_CPUS];
    ......
    }____cacheline_internodealigned_in_smp;
     
    struct per_cpu_pageset {
       struct per_cpu_pages pcp;
    } ____cacheline_aligned_in_smp;
     
    struct per_cpu_pages {
       int count;    /* number ofpages in the list */
       int high;     /* highwatermark, emptying needed */
       int batch;    /* chunk sizefor buddy add/remove */
       struct list_head list;   /*the list of pages */
    };

为了方便,我下文将per cpu pageset简称为pcp。

pageset[]数组用于存放per cpu的冷热页。

当CPU释放一个页时,如果这个页仍在高速缓存中,就认为它是热的,之后很可能又很快被访问,于是将它放到pageset列表中,其他的认为是冷页。
pageset中的冷热页链表元素数量是有限制的,由per_cpu_pages的high成员控制,毕竟如果热页太多,实际上最早加进来的页已经不热了。

在CPU释放一个页的时候,不会急着释放到buddy系统中,而是会先试图将页作为热页或冷页放到pcp链表中,直到超出数量限制。
而释放多个页时则直接释放到buddy系统中。

per_cpu_pages的count成员表示链表中页的数量。
batch表示有时需要从伙伴系统中拿一些页放到冷热页链表中时,一次拿多少个页。
list成员是冷热页链表,越靠近表头的越热。

一般情况下,当内核想申请一个页的内存时,就先从CPU的冷热页链表中申请。
但是,有时直接申请冷页会更合理一些,因为有时cache中的页肯定是无效的,所以内核在申请内存页时提供了一个标记GPF_COLD来指明要申请冷页。

注意,冷热页分配只针对分配和回收一个页的时候,多个页则直接操作buddy。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值