前言
界限规定
第二阶段是个时间点 : 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-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 何时建立
是从这个函数开始的
start_kernel -> setup_arch ->
paging_init -> bootmem_init ->
arm_bootmem_free
完成建立的时间点
start_kernel -> mm_init -> mem_init(这个函数返回时)
开始准备消亡是从下面开始的(这时候开始初始化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)
-> 设置 contig_page_data 成员 (node_id node_start_pfn node_spanned_pages node_present_pages node_mem_map)
-> free_area_init_core (设置 struct zone)
-> 设置 node_zones 成员 (pageset)
-> init_currently_empty_zone
-> zone_init_free_lists (设置 struct free_area)
-> INIT_LIST_HEAD(&zone->free_area[order].free_list[t]);
-> memmap_init
-> SetPageReserved(page);
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 中
包括
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);
-> free_all_bootmem();
-> free_all_bootmem_core
-> __free_pages_bootmem
-> __free_pages
-> free_highpages();
-> free_area
-> __free_page
__free_pages
-> 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]);
如何调试 : 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
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]
kmalloc
2. 其他内存管理系统
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 重新分配内存。
提供的api : vmalloc
基于 slub (kmalloc)
其他
问题: __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;
int high;
int batch;
struct list_head list;
};
为了方便,我下文将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。