书接前文,本文介绍《Memblock和Buddy System》的第二篇,第一篇见前文
伙伴系统
Mem Block向Buddy System过渡
伙伴系统便是使用页为单位对内存进行管理的方法。伙伴系统接管前,处理建立mem_section结构,也必须先从Mem Block中释放出不再使用的内存交给伙伴系统管理。本文Figure 2中略有体现,实现这个过渡的函数是memblock_free_all:
1 | void __init memblock_free_all(void) |
free_unused_memmap 释放未使用mem_map内存。
reset_all_zones_managed_pages 作用是将所有节点所有区域的managed_pages自动设置为0(managed_pages表示被伙伴系统管理的页的数量)。
_free_low_memory_core_early_主要做两个动作:
将reserve类型的memblock和明确标记为Memory None的内存对应的页做标记为reserved(PG_reserved)
将Mem block类型为memory的区域free掉,并标记为Free页面
_totalram_pages_add_增加 _totalram_pages ,用于标记系统中可用总页数。
管理方式
伙伴系统的管理方式可以参考<Understanding the Linux® Virtual Memory Manager>的图:
每个内存区域(zone),都有一个链表数组,数组元素用来存放 $2^{Order}$个页的链表。内存的分配和释放便围绕着这个表来管理。
数据结构
数据结构一文,我们已经介绍的struct page/struct zone/struct pglist_data等数据结构。我们回顾其中部分字段:
1 | //include/linux/mmzone.h |
zone
- zone_pgdat: 表示该内存区域所在的内存结点
- zone_start_pfn: 表示该内存区域的起始页帧号
- managed_pages: 表示该区域内由伙伴系统管理的页数
- spanned_pages: 表示该区域跨越的总页数
- present_pages:表示该区域内去掉内存空洞的总页数 (含系统保留页)
- free_area: 如前文,存放伙伴系统有关的可用区域。
1 | typedef struct pglist_data { |
pglist_data
- node_zones: 该数组存储该内存节点内所有的内存区域
- node_zonelists:保护所有内存节点中所有内存区域的应用
- kswapd_wait、kswapd、kswapd_order等: kswpad线程运行所需字段。
内存分配
内存分配使用alloc_pages*系列函数,其核心代码__alloc_pages代码如下:
1 | struct page *__alloc_pages(gfp_t gfp, unsigned int order, int preferred_nid, |
代码很多,但是核心部分就是下面三个函数:
- prepare_alloc_pages: 主要作用是,分配前准备页面分配的上下文,特别是选取合适的内存节点的内存区域(Zone)
1 | static inline bool prepare_alloc_pages(gfp_t gfp_mask, unsigned int order, |
- get_page_from_freelist: 作用是根据传入的分配参数不断尝试来分配内存。同样引用<Understanding the Linux® Virtual Memory Manager>的图,当所需order的页数不足时,会将更大order的free_area拆分来满足返回对应的page指针。当无法分配成功时,则返回NULL。
1 | static struct page * |
- __alloc_pages_slowpath:如果get_page_from_freelist分配页面失败,则进行慢速分配。这个函数会尝试回收内存,采用以下顺序:
- 触发kswapd尝试回收内存。
- 如果回收失败,则尝试杀掉进程回收内存。
内存释放
内存释放最后会调用到*__free_one_page *:释放过程比较容易来讲,找到可以合并的Buddy页帧号向上一级Order合并直到不能合并,将合并好的页加入到对应Order的free_area。
1 | static inline void __free_one_page(struct page *page, |
kswapd
见内存分配一节,_alloc_pages_slowpath会触发kswapd来回收内存。kswapd在每个内存节点都有一个,其定义和代码如下。其实这里就是调用了balance_pgdat进行内存回收。
1 | int kswapd_run(int nid) |
总结
本文是自己学习Linux内存管理的简单梳理,介绍了:
- Linux初始化早期的Memblock内存管理
- Linux物理内存模型
- Linux伙伴系统(Buddy Allocator)
希望也对您理解Linux的内存管理有一些帮助。
参考资料
- 内核代码. https://elixir.bootlin.com/linux/latest/source/.
- 内核手册. https://www.kernel.org/doc/html/latest/vm/index.html.
- Understanding the Linux® Virtual Memory Manager. Mel Gorman. http://ptgmedia.pearsoncmg.com/images/0131453483/downloads/gorman_book.pdf.