几个数据结构
- pg_data_t
这个数据结构代表每一个NUMA节点,关于NUMA可以稍微看看这篇了解下 - zonelist
内存域的列表,按照内存域的优先级排列。 - zone
内存域,内存域常见类型有几种:
- page
代表一个物理内存页,页帧。 - free_area
这个跟伙伴内存分配相关。
放一个自己画的图
从上面的图里面大致可以反映出代码的结构,对照代码看的话更清晰一些。
初始化内存
直接贴书上的图吧
- setup_arch: 函数内部会去获取机器内存信息,这个跟特定的机器相关,我看到好多都是e820,关于e820可以参照WiKi 和 这篇博客。
- setup_per_cpu_areas:拷贝一些变量到每个CPU
- build_all_zonelists: 构建每个内存区域的优先级,分配内存的时候按照这个优先级去zone里面找。
- setup_per_cpu_pageset: 初始化每个CPU的pageset
伙伴系统
初始化
把系统的可用内存跟数据结构关联起来的函数是:free_area_init_nodes
对于每个NUMA节点,调用了free_area_init_node,这个函数里面调用calculate_node_totalpages计算这个节点页的总量,调用alloc_node_mem_map来初始化struct page所需要的内存并初始化mem_map。最后再调用free_area_init_core,这个函数里面需要关注下面三个函数:
- zone_pcp_init:初始化这个内存域的per-CPU缓存
- init_currently_empty_zone:初始化free_area
- memmap_init这个是个宏,后面的函数是memmap_init_zone
- memmap_init_zone:这个函数里面会调用:
- pfn_to_page:物理页面序号转换成页帧
- set_page_links:设置这个页属于哪个内存域,属于哪个NUMA节点。
- SetPageReserved:设置这个页成Reserved。
伙伴系统分配
所有的函数最后都归到alloc_pages_node这个函数,这个函数又会调用__alloc_pages_nodemask这个函数,这个函数就是核心分配了,内部主要调用了两个函数:
get_page_from_freelist
__alloc_pages_slowpath
第一个函数先检查内存水位线,再尝试分配合适的内存,如果可以分配内存,那么调用buffered_rmqueue这个函数获取页,然后就结束分配。
如果第一次尝试不成功的话,会调用下面那个函数,下面这个就相当于进入了分配的“slowpath”,slowpath里面会先唤醒kswapd进程看看有没有能交换到swap的页,然后再调用第一个函数去分配,这次会比第一次积极一些,会放宽一些条件限制。
buffered_rmqueue这个函数会检查这些可分配的页是否是连续的,然后再移除这些页并重排内存区。
__rmqueue这个函数会调用 __rmqueue_smallest来获取指定节点,指定内存域,指定阶数,指定迁移类型的页。最后调用expand函数重排内存区。
好了,经过这一系列的函数,已经获取了特定数量的页了。
slab/slub/slob
通过malloc分配用户空间内存的时候,页单位太大,需要把页分成更小的单位来管理。
slab:默认分配器
slub:针对大型计算机的分配器
slab里面关键的概念就是“缓存”和“对象”。
- 缓存
对应的数据结构是kmem_cache,每种对应大小的对象都对应一个kmem_cache,相当于一个篮子。 - 对象
篮子里面的果子。
分配对象步骤
这篇博文很好。
总结
获取内存布局[e820],并初始化页帧 --> 页的分配[伙伴系统] --> 小对象的分配[slab系列]