概览
Linux内核内存管理的一项重要工作就是如何在频繁申请释放内存的情况下,避免碎片的产生。Linux采用伙伴系统解决外部碎片的问题,采用slab解决内部碎片的问题
Buddy算法最主要的的特点任何时候区域里的空闲内存都能以2的n次方进行拆分或合并。
伙伴系统的宗旨就是用最小的内存块来满足内核的对于内存的请求。在最初,只有一个块,也就是整个内存,假如为1M大小,而允许的最小块为64K,那么当我们申请一块200K大小的内存时,就要先将1M的块分裂成两等分,各为512K,这两分之间的关系就称为伙伴,然后再将第一个512K的内存块分裂成两等分,各位256K,将第一个256K的内存块分配给内存,这样就是一个分配的过程。
算法的核心思想:把所有的空闲页框分组为11个块链表,每个块链表分别包含1、2、4、8、16、…、512、1024个连续页框。
数据结构
zone相关结构,参考内存管理(二)Zone和Watermark
kernel/msm-5.4/include/linux/mmzone.h
typedef struct pglist_data {
// 描述此node下存在几个zone
struct zone node_zones[MAX_NR_ZONES];
// 备用zone的list
// 当首选的zone去分配失败后,就会去备用zone去查找可用的page
struct zonelist node_zonelists[MAX_ZONELISTS];
// 节点中管理区的数目,不一定为3个,有的节点中可能不存在ZONE_DMA
int nr_zones;
...
// 该节点的起始页框编号
unsigned long node_start_pfn;
...
// 节点标识符,代表当前节点是系统中的第几个节点
int node_id;
// 页换出进程使用的等待队列
wait_queue_head_t kswapd_wait;
// 指向页换出进程的进程描述符
wait_queue_head_t pfmemalloc_wait;
struct task_struct *kswapd;
...
//保留的总共的page
unsigned long totalreserve_pages;
...
// 所有申请的page都会加到lru链表中,用于回收page时使用的
// LRU链表中会根据不同的LRU类型分为不同的列表
// 常见的有匿名活动页,匿名低活动页,活动的文件页,低活动的文件页等
struct lruvec lruvec;
...
}pg_data_t;
通过pglist_data结构就可以完全的描述一个内存的layout了。
- 通过pglist_data知道存在几个zone,每个zone中又存在freelist来表示各个order空闲的page,以及各个page是属于什么迁移类型
- 当申请page的时候根据zone中的水位去申请,当内存不足的时候,就会开启内核swapd来回收内存
- 每次申请的page都会挂到lru链表中,当出现内存不足的时候,就会根据lru算法找出那些page最近很少使用,然后释放
start_kernel()–>paging_init()–>zone_sizes_init()–>free_area_init_nodes()–>free_area_init_node()–>free_area_init_core()–>init_currently_empty_zone()–>zone_init_free_lists()中,free_area的相关域都被初始化
* kernel/msm-5.4/mm/page_alloc.c
static void __meminit zone_init_free_lists(struct zone *zone)
{
unsigned int order, t;
for_each_migratetype_order(order, t) {
INIT_LIST_HEAD(&zone->free_area[order].free_list[t]);
zone->free_area[order].nr_free = 0;
}
}
zone - > free_area[order] - >free_list[MIGRATE_TYPES]
分配page
Buddy分配器是按照页为单位分配和释放物理内存的,在Zone那一节文章中freearea就是通过buddy分配器来管理的。
buddy分配器将空闲页面按照order的大小分配挂到不同的order链表中。比如order为0的链表下就挂载着大小为1个page的空闲页,order=4的链表下就挂载着大小为16个page的空闲页面。
buddy分配器的算法是:
- 当分配order=n的页面的时候,首先order=n的freelist链表中去寻找对应的页,如果order=n的freelist中有空闲的页面,则直接分配
- 当order=n的freelist链表中没有可用的页面时,则去order=n+1的freelist中查找是否有对应的空闲页面
- 如果order=n+1的freelist链表中存在空闲页面,则从order=n+1的freelist中取出一个空闲页面,将其分为两个order=n的页面。
- 其中一个分配出去,另外一个挂载到order=n的空闲链表中。
- 其中刚分开的那两个空闲页面称为buddy
在真正分析分配代码之前,我们需要看一下ALLOC_开头的一些flag,kernel/msm-5.4/mm/internal.h
- ALLOC_WMARK_MIN: 从最低水位分配或者以上
- ALLOC_WMARK_LOW: 从低水位分配或者以上
- ALLOC_WMARK_HIGH: 从高水位分配或者以上
- ALLOC_NO_WATERMARKS: 分配不检查水位
- ALLOC_HARDER: 努力去分配,尽力的去分配
- ALLOC_HIGH:高优先级的分配
- ALLOC_CPUSET: 检查是否正确CPUSET配置
- ALLOC_CMA: 允许从CMA区域分配
- ALLOC_KSWAPD:允许唤醒kswapd
alloc_pages -> alloc_pages_node -> __alloc_pages_node -> __alloc_pages -> __alloc_pages_nodemask
所有的分配页面的函数最终都会落到__alloc_pages_nodemask这个函数上面,它是伙伴系统的入口。
* kernel/msm-5.4/mm/page_alloc.c
struct page *
__alloc_pages_nodemask(gfp_t gfp_mask, unsigned int order, int preferred_nid,
nodemask_t *nodemask)
{
struct page *page;
...
/* First allocation attempt */
page = get_page_from_freelist(alloc_mask, order, alloc_flags, &ac);
if (likely(page))
goto out;
/*第一次分配失败的话则会用通过一条低速路径来进行第二次分配,包括唤醒页换出守护进程等等*/
page = __alloc_pages_slowpath(alloc_mask, order, &ac);
...
return page;
}
一般通过get_page_from_freelist分配的被称作快速路径,通过__alloc_pages_slowpath分配的被称作慢速路径