背景
Read the fucking source code!
--By 鲁迅
A picture is worth a thousand words.
--By 高尔基
说明:
Kernel 版本: 4.14
ARM64 处理器, Contex-A53, 双核
使用工具: Source Insight 3.5, Visio
1. 介绍
之前的系列内存管理文章基本上描述的是物理页面的初始化过程, 以及虚拟页面到物理页面的映射建立过程, 从这篇文章开始, 真正要涉及到页面的分配了. 接下来的文章会围绕着分区页框分配器 (zoned page frame allocator) 来展开, 其中会包含大家熟知的 Buddy System 分析.
本文会先围绕着涉及到的数据结构, 以及大体的流程做一个整体的分析, 后续会针对这个流程中的细节进行更详细的拆解, 我已经迫不及待了.
2. 数据结构
2.1 概述
先回顾一下(五)Linux 内存管理 zone_sizes_init 的数据结构图:
上述的结构体, 描述的是下面这张图:
Node ---> ZONE ---> Page
的组织关系, 其中 Buddy System 中, 页面都是以 2 的次幂来组织成链表, 比如 free_area[0], 对应的是 1 个 page 链表, 其中又根据不同的 MIGRATE_xxxx 类型来组织, 如下图:
ARM64 中 MAX_ORDER 默认值为 11,PAGE_SIZE=4K, 因此总共有 0 ~ 1011 个链表数组, 链表中的连续的页面为 2^0 ~ 2^10, 对应大小为 4K ~ 4M.
可以通过
cat /proc/pagetypeinfo
来查看下系统的页面信息, 如下图:
可以通过 cat /proc/zoneinfo 来查看 Node 的 ZONE 计数信息:
2.2 Migrate 类型
从上边的图中可以看到 MIGRATE_xxx 不同的迁移类型, 表明页面的移动属性, 并在可能的情况下通过将相同属性的页面分组在一起来抑制内存的连续碎片.
- enum migratetype {
- MIGRATE_UNMOVABLE,
- MIGRATE_MOVABLE,
- MIGRATE_RECLAIMABLE,
- MIGRATE_PCPTYPES, /* the number of types on the pcp lists */
- MIGRATE_HIGHATOMIC = MIGRATE_PCPTYPES,
- #ifdef CONFIG_CMA
- /*
- * MIGRATE_CMA migration type is designed to mimic the way
- * ZONE_MOVABLE works. Only movable pages can be allocated
- * from MIGRATE_CMA pageblocks and page allocator never
- * implicitly change migration type of MIGRATE_CMA pageblock.
- *
- * The way to use it is to change migratetype of a range of
- * pageblocks to MIGRATE_CMA which can be done by
- * __free_pageblock_cma() function. What is important though
- * is that a range of pageblocks must be aligned to
- * MAX_ORDER_NR_PAGES should biggest page be bigger then
- * a single pageblock.
- */
- MIGRATE_CMA,
- #endif
- #ifdef CONFIG_MEMORY_ISOLATION
- MIGRATE_ISOLATE, /* can't allocate from here */
- #endif
- MIGRATE_TYPES
- };
MIGRATE_UNMOVABLE: 无法移动和检索的类型, 用于内核分配的页面, I/O 缓冲区, 内核堆栈等;
MIGRATE_MOVABLE: 当需要大的连续内存时, 通过移动当前使用的页面来尽可能防止碎片, 用于分配用户内存;
MIGRATE_RECLAIMABLE
: 当没有可用内存时使用此类型;
MIGRATE_HIGHATOMIC: 减少原子分配请求无法进行高阶页面分配的可能, 内核会提前准备一个页面块;
MIGRATE_CMA: 页面类型由 CMA 内存分配器单独管理;
MIGRATE_ISOLATE: 内核会暂时更改为这种类型, 以迁移使用中的系列活动页面;
2.3 __GFP_xxx 请求标志(gfp_mask)
__GFP_xxx 为内部使用的标志, 在 include/Linux/gfp.h 文件中, 外部不应该使用这些 Flag, 这些标志在页面申请的时候使用, 其中 GFP 表示 get free page.
罗列部分如下:
__GFP_DMA: 请求在 ZONE_DMA 区域中分配页面;
__GFP_HIGHMEM: 请求在 ZONE_HIGHMEM 区域中分配页面;
__GFP_MOVABLE:ZONE_MOVALBE 可用时在该区域分配页面, 同时表示页面分配后可以在内存压缩时进行迁移, 也能进行回收;
__GFP_RECLAIMABLE: 请求分配到可恢复页面;
__GFP_HIGH: 高优先级处理请求;
__GFP_IO: 请求在分配期间进行 I/O 操作;
__GFP_FS: 请求在分配期间进行文件系统调用;
__GFP_ZERO: 请求将分配的区域初始化为 0;
__GFP_NOFAIL: 不允许请求失败, 会无限重试;
__GFP_NORETRY: 请求不重试内存分配请求;
2.4 ALLOC_xxxx 分配标志(alloc_flags)
分配标志定义在 mm/internal.h 文件中, 在页面的分配函数中与 gfp_mask 分开使用, 这些标志时用于内部函数的分配.
ALLOC_WMARK_MIN: 仅在最小水位 water mark 及以上限制页面分配;
ALLOC_WMARK_LOW: 仅在低水位 water mark 及以上限制页面分配;
ALLOC_WMARK_HIGH: 仅在高水位 water mark 及以上限制页面分配;
ALLOC_HARDER: 努力分配, 一般在 gfp_mask 设置了__GFP_ATOMIC 时会使用;
ALLOC_HIGH: 高优先级分配, 一般在 gfp_mask 设置了__GFP_HIGH 时使用;
ALLOC_CPUSET: 检查是否为正确的 cpuset;
ALLOC_CMA: 允许从 CMA 区域进行分配;
2.5 struct alloc_context
在页面分配的过程中, 有一个结构叫 struct alloc_context, 这个结构用于存储各个函数之间传递的参数. 这种思想在平时的 coding 中是可以去借鉴的, 比如有些人写代码很喜欢用全局变量, 改成这种 context 的形式, 在各个函数之间传递显得更为优雅. 直接看代码吧:
/* * Structure for holding the mostly immutable allocation parameters passed * between functions involved in allocations, including the alloc_pages* * family of functions. * * nodemask, migratetype and high_zoneidx are initialized only once in * __alloc_pages_nodemask() and then never change. * * zonelist, preferred_zone and classzone_idx are set first in * __alloc_pages_nodemask() for the fast path, and might be later changed * in __alloc_pages_slowpath(). All other functions pass the whole strucure * by a const pointer. */ struct alloc_context { struct zonelist *zonelist; nodemask_t *nodemask; struct zoneref *preferred_zoneref; int migratetype; enum zone_type high_zoneidx; bool spread_dirty_pages; };
zonelist: 用于分配页面的区域列表;
nodemask: 指定 Node, 如果没有指定, 则在所有节点中进行分配;
preferred_zone: 指定要在快速路径中首先分配的区域, 在慢路径中指定了 zonelist 中的第一个可用区域;
migratetype: 要分配的迁移页面类型;
high_zoneidx: 将分配限制为小于区域列表中指定的高区域;
spread_dirty_pages: 脏区平衡相关;
3. build_all_zonelists
在上篇文章中描述到各个 zone, 实际上各个 zone 最终组织起来是在
build_all_zonelists
函数中实现的:
整体完成的工作也比较简单, 将所有 Node 中可用的 zone 全部添加到各个 Node 中的 zonelist 中, 也就是对应的 struct pglist_data 结构体中的 struct zonelist node_zonelists 字段.
这一步之后, 准备工作基本就绪, 进行页面申请的工作就可以开始了.
4. alloc_pages
下面的流程开始真正的页面申请了, 在内部的实现中通过__alloc_pages 来实现的:
在页面分配时, 有两种路径可以选择, 如果在快速路径中分配成功了, 则直接返回分配的页面; 快速路径分配失败则选择慢速路径来进行分配.
4.1 Fast Path
快速路径分配, 是通过
get_page_from_freelist
来完成的, 具体的流程及分析如下图所示:
4.2 Slow Path
慢速路径分配, 最终也会调用
get_page_from_freelist
, 流程分析如下:
来源: https://www.cnblogs.com/LoyenWang/p/11626237.html