linux 源代码分析3:伙伴系统内存分配
- 伙伴系统内存分配
- 下面是几个分配标志位的说明:
- 我们将伙伴系统内存的分配分为为三个阶段
- 第一阶段
- 第二阶段
- __alloc_pages_nodemask函数
- first_zones_zonelist函数
- zref_in_nodemask函数
- __alloc_pages_nodemask函数:
- get_page_from_freelist函数
- for_each_zone_zonelist_nodemask宏定义如下:
- zlc_zone_worth_trying函数
- zonelist_cache结构定义如下
- get_page_from_freelist函数
- zone_watermark_ok函数
- __zone_watermark_ok函数
- get_page_from_freelist函数
- zlc_setup函数
- get_page_from_freelist函数
- 第三阶段
- 第四阶段 伙伴系统内存释放
伙伴系统内存分配
伙伴系统通过指定分配阶order和分配标记位gfp_mask来进行内存分配。分配阶是分配指的是分配2^order个页面。分配标志位是一些位的组合。这些标志位定义在include/linux/gfp.h中,迁移类型和水位线是在分配标志位中指定的。
下面是几个分配标志位的说明:
最常用的GFP_KERNEL,他表示内存分配(最终总是调用get_free_pages来实现实际的分配,这就是GFP前缀的由来)是代表运行在内核空间的进程执行的。使用GFP_KERNEL容许表示在内存分配过程中可以等待。因此这时分配函数必须是可重入的,如果在进程上下文之外如:中断处理程序、tasklet以及内核定时器中这种情况下current进程不该睡眠,驱动程序该使用GFP_ATOMIC.
GFP_ATOMIC 用来从中断处理和进程上下文之外的其他代码中分配内存. 从不睡眠.
GFP_KERNEL 内核内存的正常分配. 可能睡眠.
GFP_USER 用来为用户空间页来分配内存; 它可能睡眠.
GFP_HIGHUSER 如同 GFP_USER, 但是从高端内存分配, 如果有. 高端内存在下一个子节描述.
GFP_NOIO,GFP_NOFS 这个标志功能如同 GFP_KERNEL, 但是它们增加限制到内核能做的来满足请求. 一个 GFP_NOFS 分配不允许进行任何文件系统调用, 而 GFP_NOIO 根本不允许任何 I/O 初始化. 它们主要地用在文件系统和虚拟内存代码, 那里允许一个分配睡眠, 但是递归的文件系统调用会是一个坏注意.
上面列出的这些分配标志可以是下列标志的相或来作为参数, 这些标志改变这些分配如何进行:
__GFP_DMA 这个标志要求分配在能够 DMA 的内存区. 确切的含义是平台依赖的并且在下面章节来解释.
__GFP_HIGHMEM 这个标志指示分配的内存可以位于高端内存.
__GFP_COLD 正常地, 内存分配器尽力返回"缓冲热"的页 -- 可能在处理器缓冲中找到的页. 相反, 这个标志请求一个"冷"页, 它在一段时间没被使用. 它对分配页作 DMA 读是有用的, 此时在处理器缓冲中出现是无用的. 一个完整的对如何分配 DMA 缓存的讨论看"直接内存存取"一节在第 1 章.
__GFP_NOWARN 这个很少用到的标志阻止内核来发出警告(使用 printk ), 当一个分配无法满足.
__GFP_HIGH 这个标志标识了一个高优先级请求, 它被允许来消耗甚至被内核保留给紧急状况的最后的内存页.
__GFP_REPEAT, __GFP_NOFAIL ,__GFP_NORETRY,这些标志修改分配器如何动作, 当它有困难满足一个分配.__GFP_REPEAT 意思是" 更尽力些尝试" 通过重复尝试 -- 但是分配可能仍然失败. __GFP_NOFAIL 标志告诉分配器不要失败; 它尽最大努力来满足要求. 使用 __GFP_NOFAIL 是强烈不推荐的; 可能从不会有有效的理由在一个设备驱动中使用它. 最后, __GFP_NORETRY 告知分配器立即放弃如果得不到请求的内存.
我们将伙伴系统内存的分配分为为三个阶段
第一个阶段主要是根据传入参数和系统策略来获取区域列表和节点掩码。内存分配的第二阶段是从用节点掩码和最大区域类型作为条件来扫描区域列表里面满足条件的区域,在对区域做一些判断,通过判断在当前扫描的选择当前扫描的区域分配内存。最大分配类型的原因是:如在x86系统上,我们要分配用于dma用到内存,只能在ZONE_DMA中分配,但一些普通应用程序申请的分配的内存也可以在ZONE_DMA中分配,最高区域类型由分配者指定,包含在分配标志位中。第三阶段根据在第二阶段扫描到的区域中进行内存分配,分配方法是根据阶和迁移类型来找到对应的空闲链表,在空闲链表中摘除一项,返回这项管理的物理地址,这一阶段也可能会进行内存迁移和块的。
第一阶段
第一阶段的主要任务是确定区域列表和节点掩码。策略分为几部分,1:即哪些节点可用于分配(这是用nodemask_t结构实现,对应位为1表示节点可用,空nodemask_t指针表示所有节点可用),2:怎样从可用的节点里面选择一个节点来进行本次分配(如总是使用一个节点,交错使用每个节点),3:用节点上的哪个列表,4:以及在策略里面指定节点掩码。
Mempolicy结构
策略结构中include/linux/mempolicy.c头文件定义:
struct mempolicy {
atomic_trefcnt; //引用计数
unsignedshort mode; /* See MPOL_* above */ // 节点选择模式
unsignedshort flags; /* See set_mempolicy()MPOL_F_* above */ //节点选择模式辅助标记位
union{
short preferred_node; /* preferred */ //首选的节点
nodemask_t nodes; /*interleave/bind */ //节点掩码,对应位为1表示相应节点可用。
/*undefined for default */
}v;
union{
nodemask_tcpuset_mems_allowed; /* relative tothese nodes */
nodemask_tuser_nodemask; /* nodemask passedby user */
}w;
};
在分配标志为有一位__GFP_THISNODE用来表示在本地节点分配内存。并定义了一个枚举变量表示几种节点选择方法。
enum{
MPOL_DEFAULT,
MPOL_PREFERRED, //使用分配策略结构的首选节点号
MPOL_BIND, //绑定一个节点
MPOL_INTERLEAVE, //交错使用节点
MPOL_MAX, /* always last member of enum */ //策略数目
};
};
Linux伙伴系统的内存分配的入口函数是get_free_pages
get_free_pages 原型是 unsigned long __get_free_pages(gfp_t gfp_mask,unsigned int order)
参数gfp_mask是分配标记位,下后面分析中会逐渐分析到标志位的作用,order是分配阶,即分配2^order个页面, __get_free_pages函数直接调用alloc_pages进行分配,分配成功则调用page_address把物理地址转换为逻辑地址。
__get_free_pages函数
在mm/page_alloc.c里定义,代码如下
2482unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order)
2483 {
2484 struct page *page;
2485
2486 /*
2487 * __get_free_pages() returns a 32-bitaddress, which cannot represent
2488 * a highmem page
2489 */
2490 VM_BUG_ON((gfp_mask &__GFP_HIGHMEM) != 0);
2491
2492 page = alloc_pages(gfp_mask, order);
2493 if (!page)
2494 return 0;
2495 return (unsigned long)page_address(page);
2496 }
page_address函数就不分析了,下面分析alloc_pages在include/linux/gfp.h里面定义
staticinline struct page *
alloc_pages(gfp_tgfp_mask, unsigned int order)
{
return alloc_pages_current(gfp_mask,order);
}
alloc_pages_current函数
内部也是直接对alloc_pages_current函数的调用,alloc_pages_current在mm/mempolicy.c里面定义,alloc_pages_current的实现代码如下:
1910 struct page *alloc_pages_current(gfp_t gfp, unsigned order)
1911 {
1912 struct mempolicy*pol = current->mempolicy;
1913 struct page*page;
1914 unsigned intcpuset_mems_cookie;
1915
1916 if (!pol ||in_interrupt() || (gfp & __GFP_THISNODE))
1917 pol =&default_policy;
1918
1919 retry_cpuset:
1920 cpuset_mems_cookie = get_mems_allowed();
1921
1922 /*
1923 * No referencecounting needed for current->mempolicy
1924 * nor systemdefault_policy
1925 */
1926 if (pol->mode== MPOL_INTERLEAVE)
1927 page =alloc_page_interleave(gfp, order, interleave_nodes(pol));
1928 else
1929 page =__alloc_pages_nodemask(gfp, order,
1930 policy_zonelist(gfp, pol, numa_node_id()),
1931 policy_nodemask(gfp, pol));
1932
1933 if(unlikely(!put_mems_allowed(cpuset_mems_cookie) && !page))
1934 gotoretry_cpuset;
1935
1936 return page;
1937 }
1912行把策略初始化为本进程的策略,1916行进行判断,如果本进程没有设置内存策略,在中断上下文,或者分配标记指定在本节点分配,则使用系统默认策略。
1920行获取内存访问标记,在进行一次失败的分配后如果发现策略以及改变,则再尝试分配,因为策略改变了,再次分配可能会有不同的结果。
interleave_nodes函数
1926行判断如果分配策略制定了交错模式,则调用interleave_nodes函数求得节点号,再调用alloc_page_interleave进行内存分配,interleave_nodes也是在mm/mempolicy.c里实现,代码如下:
1563 static unsigned interleave_nodes(struct mempolicy *policy)
1564 {
1565 unsigned nid,next;
1566 structtask_struct *me = current;
1567
1568 nid =me->il_next;
1569 next =next_node(nid, policy->v.nodes);
1570 if (next >=MAX_NUMNODES)
1571 next =first_node(policy->v.nodes);
1572 if (next <MAX_NUMNODES)
1573 me->il_next = next;
1574 return nid;
1575 }
函数定义局部变量nid为本次分配的节点号,next为下次分配的节点号,从task_struct结构的il_next变量获取本次进行分配的节点号,并把下次要进行分配的节点号存入il_next变量。
Mempolicy结构里面联合变量v的成员nodes保存了一个位图数组,位为1,表示相应节点可以,1569行在nodes结构里面的第nid位开始位查找第一个能用的位
1570行判断大于等于最大节点数,则调用first_node函数,查找节点掩码第一能用位的索引。满足条件1571行把first_node的返回值赋给next
1572行判断next小于最大节点数,把next存入task_struct结构的il_next变量,下次可以用这个节点进行分配
interleave_nodes函数
interleave_nodes函数的返回值做为alloc_page_interleave的参数,alloc_page_interleave函数也在mm/mempolicy.c里实现,代码如下:
1809 static struct page *alloc_page_interleave(gfp_t gfp, unsignedorder,
1810 unsigned nid)
1811 {
1812 struct zonelist*zl;
1813 struct page*page;
1814
1815 zl =node_zonelist(nid, gfp);
1816 page =__alloc_pages(gfp, order, zl);
1817 if (page&& page_zone(page) == zonelist_zone(&zl->_zonerefs[0]))
1818 inc_zone_page_state(page, NUMA_INTERLEAVE_HIT);
1819 return page;
1820 }
1815行调用函数node_zonelist获取zonelist结构指针,在节点数据结构里面包含一个zonelist数组,第零个zonelist里面所有的区域,第一个zonelist包含本地节点的区域,在分配标记指定在本节点并且numa开启情况下node_zonelist函数会返回节点的第一个zonelist结构地址,否则返回第零个zonelist结构地址。
在1816行把zonelist结构指针作为参数,调用__alloc_pages函数进行分配。__alloc_pages以空节点掩码调用__alloc_pages_nodemask函数进行分配,__alloc_pages_nodemask在内存分配第二阶段进行分析。
alloc_pages_current函数
现在回到alloc_pages_current函数:
1929 page =__alloc_pages_nodemask(gfp, order,
1930 policy_zonelist(gfp, pol, numa_node_id()),
1931 policy_nodemask(gfp,pol));
1932
1933 if(unlikely(!put_mems_allowed(cpuset_mems_cookie) && !page))
1934 gotoretry_cpuset;
1935
1936 return page;
1937 }
policy_zonelist函数
在1930行调用policy_zonelist函数求得zonelist结构指针,并调用policy_nodemask获取节点掩码。policy_zonelist在mm/mempolicy.c里实现代码如下:
1537 static struct zonelist *policy_zonelist(gfp_t gfp, structmempolicy *policy,
1538 int nd)
1539 {
1540 switch(policy->mode) {
1541 case MPOL_PREFERRED:
1542 if(!(policy->flags & MPOL_F_LOCAL))
1543 nd = policy->v.preferred_node;
1544 break;
1545 case MPOL_BIND:
1546 /*
1547 *Normally, MPOL_BIND allocations are node-local within the
1548 *allowed nodemask. However, if__GFP_THISNODE is set and the
1549 *current node isn't part of the mask, we use the zonelist for
1550 * thefirst node in the mask instead.
1551 */
1552 if(unlikely(gfp & __GFP_THISNODE) &&
1553 unlikely(!node_isset(nd, policy->v.nodes)))
1554 nd = first_node(policy->v.nodes);
1555 break;
1556 default:
1557 BUG();
1558 }
1559 returnnode_zonelist(nd, gfp);
1560 }
根据不同的策略模式,mempolicy结构的联合成员变量v有不同的意义,在首选模式下,v的成员preferred_node保存了首选节点的id好,在其他模式下v的成员nodes是一个节点掩码,用来表示那些节点可用。
1541行当分配策略指定了首选模式时,并且分配选项没有指定本地选项,则把节设置为首选节点。
1545行当分配策略指定了绑定模式时,如果分配标记指定了在本地节点分配,并且传入的节点号在节点掩码里表示是不可用,则在节点掩码里面找到第一个能用的节点。
1559确定节点后用参数节点号和分配标志位,调用node_zonelist函数获得zonelist结构指针,zonelist在上面已经分析过,这里就不分析了。
alloc_pages_current函数
回到alloc_pages_current函数,在1931行调用policy_nodemask函数获取节点掩码。
policy_nodemask函数
policy_nodemask在mm/mempolicy.c里实现,代码如下:
1525 static nodemask_t *policy_nodemask(gfp_t gfp, struct mempolicy*policy)
1526 {
1527 /* Lower zonesdon't get a nodemask applied for MPOL_BIND */
1528 if(unlikely(policy->mode == MPOL_BIND) &&
1529 gfp_zone(gfp) >= policy_zone &&
1530 cpuset_nodemask_valid_mems_allowed(&policy->v.nodes))
1531 return&policy->v.nodes;
1532
1533 return NULL;
1534 }
在第一阶段实现了两个任务,求得zonelist结构指针和节点掩码指针。节点掩码的获取和区域的类型有关系,在系统里面定义有个变量policy_zone,表示系统策略适用的最低的区域类型。还有cpuset里面有一个节点掩码结构。
1528行判断,如果系统策略是绑定模式,区域类型大于等于系统策略制定的最低区域,
并且cpuset_nodemask_valid_mems_allowed函数也返回真的时候,返回mempolicy结构里面的成员v的成员nodes的地址,否则返回空地址,返回空地址的情况下等价于节点每个为都为1。
cpuset_nodemask_valid_mems_allowed函数
cpuset_nodemask_valid_mems_allowed函数在kernel/cpuset.c里面实现代码如下:
/**
* cpuset_nodemask_valid_mems_allowed - checknodemask vs. curremt mems_allowed
* @nodemask: the nodemask to be checked
*
* Are any of the nodes in the nodemask allowedin current->mems_allowed?
*/
intcpuset_nodemask_valid_mems_allowed(nodemask_t *nodemask)
{
return nodes_intersects(*nodemask,current->mems_allowed);
}
cpuset_nodemask_valid_mems_allowed的逻辑比较简单,在进程结构里面有个mems_allowed成员,也是一个节点掩码,参数传进来一个节点掩码,如注释所说,这个函数判断了两个节点掩码是不是相交。
在policy_zonelist函数和policy_nodemask函数返回后,alloc_pages_current函数在1929调用__alloc_pages_nodemask进行分配,__alloc_pages_nodemask函数中第二阶段进行分析。
第二阶段
第二阶段的关键任务是确定区域。第一阶段已经取得了区域列表,和节点掩码,在第二阶段节点掩码和第一阶段有不同的作用,在第一阶段节点掩码用来表示节点是否可用,在第二阶段,节点掩码用来表示哪个区域可用。
__alloc_pages_nodemask函数
第二阶段的入口函数是__alloc_pages_nodemask,在page_alloc.c里面实现,代码如下:
2417 struct page *
2418 __alloc_pages_nodemask(gfp_t gfp_mask, unsigned int order,
2419 struct zonelist *zonelist, nodemask_t *nodemask)
2420 {
2421 enum zone_typehigh_zoneidx = gfp_zone(gfp_mask);
2422 struct zone*preferred_zone;
2423 struct page *page= NULL;
2424 int migratetype =allocflags_to_migratetype(gfp_mask);
2425 unsigned intcpuset_mems_cookie;
2426
2427 gfp_mask &=gfp_allowed_mask;
2428
2429 lockdep_trace_alloc(gfp_mask);
2430
2431 might_sleep_if(gfp_mask & __GFP_WAIT);
2432
2433 if(should_fail_alloc_page(gfp_mask, order))
2434 returnNULL;
2435
2436 /*
2437 * Check thezones suitable for the gfp_mask contain at least one
2438 * valid zone.It's possible to have an empty zonelist as a result
2439 * of GFP_THISNODEand a memoryless node
2440 */
2441 if(unlikely(!zonelist->_zonerefs->zone))
2442 returnNULL;
2443
2444 retry_cpuset:
2445 cpuset_mems_cookie = get_mems_allowed();
2446
2447 /* The preferredzone is used for statistics later */
2448 first_zones_zonelist(zonelist, high_zoneidx,
2449 nodemask ? : &cpuset_current_mems_allowed,
2450 &preferred_zone);
内存分配的时候允许指定最大的区域类型,如某些驱动分配的内存只能在DMA区域,这样可以在分配标记指定位ZONE_DMA,2421行调用函数gfp_zone求得的最大的区域类型。gfp_zone实现比较简单,这里不作分析。
在分配标记里面也可以知道迁移类型,2424行调用allocflags_to_migratetype把分配标准转换为迁移类型。
在系统里面定义了一个全局变量gfp_allowed_mask,用来过滤一些分配标记选项,2427行实现这个功能。
2429行是调试代码,这里不作分析。
2431行也是调试代码,如果当前上下为允许睡眠,但分配选项指定了睡眠选项,给出警告信息。
2441行检查区域列表是否包含区域,如果第一个区域索引指向的区域是空,直接返回空。
2445行获取策略回环锁
first_zones_zonelist函数
2448行调用first_zones_zonelist函数,first_zones_zonelist函数在include/linux/mmzone.h定义,代码如下:
static inline struct zoneref *first_zones_zonelist(struct zonelist*zonelist,
enum zone_typehighest_zoneidx,
nodemask_t*nodes,
structzone **zone)
{
returnnext_zones_zonelist(zonelist->_zonerefs, highest_zoneidx, nodes,
zone);
}
first_zones_zonelist函数
first_zones_zonelist函数以zonelist的成员_zonerefs,为参数直接调用next_zones_zonelist
next_zones_zonelist函数在mm/mmzone.c中实现,代码如下:
55 struct zoneref *next_zones_zonelist(struct zoneref *z,
56 enumzone_type highest_zoneidx,
57 nodemask_t *nodes,
58 structzone **zone)
59 {
60 /*
61 * Find the next suitable zone to usefor the allocation.
62 * Only filter based on nodemask ifit's set
63 */
64 if (likely(nodes == NULL))
65 while (zonelist_zone_idx(z)> highest_zoneidx)
66 z++;
67 else
68 while (zonelist_zone_idx(z)> highest_zoneidx ||
69 (z->zone&& !zref_in_nodemask(z, nodes)))
70 z++;
71
72 *zone = zonelist_zone(z);
73 return z;
74 }
next_zones_zonelist函数的参数z是区域索引数组,highest_zoneidx是最大的区域类型,nodes是节点掩码,zone用来传递找到的区域地址,此函数实现了在区域索引数组中满足区域类型小于等于highest_zoneidx,并且在节点掩码里对应位是1的下一个索引数组。
代码并不复杂,分两种情况处理,在节点掩码地址为空的情况,第65行的一个循环找到满足,区域类型小于等于highest_zoneidx的区域索引的地址,这等价与空节点掩码表示所有区域是有效的。
zref_in_nodemask函数
第二中情况是节点掩码不为空,只是在循环里面加了个,区域索引索引的区域有效和在节点掩码对应是有效的,判断区域对应的节点掩码有效的函数是zref_in_nodemask,代码也是在mm/mmzone.c中实现,代码如下:
45 static inline int zref_in_nodemask(struct zoneref *zref,nodemask_t *nodes)
46 {
47 #ifdef CONFIG_NUMA
48 returnnode_isset(zonelist_node_idx(zref), *nodes);
49 #else
50 return 1;
51 #endif /* CONFIG_NUMA */
52 }
在开启numa的情况下运行48行的代码,首选调用zonelist_node_idx获得区域索引指向的区域的id,zonelist_node_idx在include/linux/mmzone.h中实现,代码如下:
static inline intzonelist_node_idx(struct zoneref *zoneref)
{
#ifdefCONFIG_NUMA
/* zone_to_nid not available in thiscontext */
return zoneref->zone->node;
#else
return 0;
#endif /*CONFIG_NUMA */
}
直接从区域索引指向的区域的的成员node获取,每个区域的id和区域类型是相等的,区域id在每个节点中是唯一的。
node_isset是一个宏,定义如下:
#define node_isset(node, nodemask)test_bit((node), (nodemask).bits)
测试节点掩码对应为是否为1
__alloc_pages_nodemask函数:
2451 if(!preferred_zone)
2452 goto out;
2453
2454 /* Firstallocation attempt */
2455 page =get_page_from_freelist(gfp_mask|__GFP_HARDWALL, nodemask, order,
2456 zonelist, high_zoneidx, ALLOC_WMARK_LOW|ALLOC_CPUSET,
2457 preferred_zone, migratetype);
2458 if(unlikely(!page))
2451行判断首选节点是否为空,如果为空调到标号out处。
因为在numa系统下,节点包含一个节点列表数组,数组有两项,第零个全局列表,包含系统所有区域,第一个包含本地区域。本地址列表的初始化顺序是ZONE_DMA,ZONE_NORMAL,ZONE_HIGHMEM。全局列表的初始化是,对每个节点的初始化顺序和本地节点一样,对节点的初始化顺序是先初始化本节点,然后增加节点号,到底最大节点号后归零,包含每个节点的区域一次。
所以preferred_zone实际返回的应该是包含本地节点,区域类型小于等于high_zoneidx的第一区域
get_page_from_freelist函数
2455行调用函数get_page_from_freelist,get_page_from_freelist在mm/page_alloc.c中实现,代码如下:
1738 static struct page *
1739 get_page_from_freelist(gfp_t gfp_mask, nodemask_t *nodemask,unsigned int order,
1740 structzonelist *zonelist, int high_zoneidx, int alloc_flags,
1741 structzone *preferred_zone, int migratetype)
1742 {
1743 struct zoneref*z;
1744 struct page *page= NULL;
1745 intclasszone_idx;
1746 struct zone*zone;
1747 nodemask_t*allowednodes = NULL;/* zonelist_cache approximation */
1748 int zlc_active =0; /* set if usingzonelist_cache */
1749 int did_zlc_setup= 0; /* just call zlc_setup()one time */
1750
1751 classzone_idx =zone_idx(preferred_zone);
1752 zonelist_scan:
1753 /*
1754 * Scan zonelist,looking for a zone with enough free.
1755 * See alsocpuset_zone_allowed() comment in kernel/cpuset.c.
1756 */
1757 for_each_zone_zonelist_nodemask(zone, z, zonelist,
1758 high_zoneidx, nodemask) {
1759 if(NUMA_BUILD && zlc_active &&
1760 !zlc_zone_worth_trying(zonelist, z, allowednodes))
1761 continue;
1751行获取区域id,区域所在节点的区域数组的索引。
1757行用一个宏for_each_zone_zonelist_nodemask来实现遍历满足区域类型小于等于high_zoneidx,并且在节点掩码nodemask中可用的在区域列表zonelist中的所有区域,zone用来传递目前扫描的区域,z用来传递目前扫描的节点索引。
for_each_zone_zonelist_nodemask宏定义如下:
#define for_each_zone_zonelist_nodemask(zone, z, zlist, highidx,nodemask) \
for (z =first_zones_zonelist(zlist, highidx, nodemask, &zone); \
zone; \
z =next_zones_zonelist(++z, highidx, nodemask, &zone)) \
循环开始找到调用first_zones_zonelist找到满足类型小于等于zoneidx,并且在节点掩码nodemask中可用的第一个区域,如果区域不空则继续,next_zones_zonelist查找类型小于等于zoneidx,并且在节点掩码nodemask中可用的下一个区域,first_zones_zonelist函数和next_zones_zonelist函数上面已经分析过,这里不再分析。
从1759行开始是对具体的区域做判断,如果满足掉进就在当前区域进行分配。
zlc_zone_worth_trying函数
1759行判断如果开启了numa,并且zlc_active置位,则调用zlc_zone_worth_trying对区域内存是否充足做判断,lc_zone_worth_trying在page_alloc.c里面实现,代码如下
1660 static int zlc_zone_worth_trying(struct zonelist *zonelist,struct zoneref *z,
1661 nodemask_t *allowednodes)
1662 {
1663 structzonelist_cache *zlc; /* cachedzonelist speedup info */
1664 int i; /* index of *z inzonelist zones */
1665 int n; /* node that zone *zis on */
1666
1667 zlc =zonelist->zlcache_ptr;
1668 if (!zlc)
1669 return 1;
1670
1671 i = z -zonelist->_zonerefs;
1672 n =zlc->z_to_n[i];
1673
1674 /* This zone isworth trying if it is allowed but not full */
1675 returnnode_isset(n, *allowednodes) && !test_bit(i, zlc->fullzones);
1676 }
zonelist_cache结构定义如下
struct zonelist_cache {
unsigned shortz_to_n[MAX_ZONES_PER_ZONELIST]; /*zone->nid */
DECLARE_BITMAP(fullzones,MAX_ZONES_PER_ZONELIST); /* zonefull? */
unsigned longlast_full_zap; /* when lastzap'd (jiffies) */
};
Fullzones是一个位数组,位为一表示相应区域内存充足。
short z_to_n用来从区域列表里面的区域索引数组的下标到区域id的转换的
1667行获得short z_to_n结构指针
1671行获得z的下标
1672行获得区域编号
1675行调用node_isset测试区域中节点掩码中是否可用,test_bit测试区域内存是否充足,在区域可用,内存不充足的情况下返回真,否则返回假。
get_page_from_freelist函数
返回get_page_from_freelist函数
1762 if ((alloc_flags &ALLOC_CPUSET) &&
1763 !cpuset_zone_allowed_softwall(zone, gfp_mask))
1764 continue;
1762行判断如果在分配标准位指定了ALLOC_CPUSET,并且不允许在这个区域分配内存,则继续扫描下一个区域
继续分析get_page_from_freelist函数
1791 if ((alloc_flags &ALLOC_WMARK_LOW) &&
1792 (gfp_mask &__GFP_WRITE) && !zone_dirty_ok(zone))
1793 goto this_zone_full;
1791行判断分配标志位是否设置了ALLOC_WMARK_LOW位
1792行判断分配标志位是否设置了__GFP_WRITE,设置__GFP_WRITE标记位是向分配器提供信息,分配的页很可能会变脏,也就是会往这页写数据。这行可以平衡个区域脏页的数量。
1795 BUILD_BUG_ON(ALLOC_NO_WATERMARKS < NR_WMARK);
1796 if(!(alloc_flags & ALLOC_NO_WATERMARKS)) {
1797 unsigned long mark;
1798 int ret;
1799
1800 mark = zone->watermark[alloc_flags & ALLOC_WMARK_MASK];
1801 if (zone_watermark_ok(zone, order, mark,
1802 classzone_idx, alloc_flags))
1803 goto try_this_zone;
1804
1805 if (NUMA_BUILD && !did_zlc_setup && nr_online_nodes >1) {
1806 /*
1807 * we do zlc_setup if there are multiple nodes
1808 * and before considering the first zone allowed
1809 * by the cpuset.
1810 */
1811 allowednodes = zlc_setup(zonelist, alloc_flags);
1812 zlc_active = 1;
1813 did_zlc_setup = 1;
1814 }
1815
1816 if (zone_reclaim_mode == 0)
1817 goto this_zone_full;
1818
1819 /*
1820 * As we may have just activated ZLC, check if the first
1821 * eligible zone has failedzone_reclaim recently.
1822 */
1823 if (NUMA_BUILD && zlc_active &&
1824 !zlc_zone_worth_trying(zonelist, z, allowednodes))
1825 continue;
1826
1827 ret = zone_reclaim(zone, gfp_mask, order);
1828 switch (ret) {
1829 case ZONE_RECLAIM_NOSCAN:
1830 /* did not scan */
1831 continue;
1832 case ZONE_RECLAIM_FULL:
1833 /* scanned but unreclaimable */
1834 continue;
1795是调试代码
1796判断是否设置了不作水位判断的标记位
1800获取水位,在每个区域都包含若干水位线,用来表示区域的内存紧张程度,在分配标志位可以知道使用哪个水位线。
1801行调用zone_watermark_ok函数来判断区域的水位线是否满足,满足则在这个区域进行分配。
zone_watermark_ok函数
zone_watermark_ok在page_alloc.c中实现,代码如下:
bool zone_watermark_ok(struct zone *z, int order, unsigned longmark,
int classzone_idx, int alloc_flags)
{
return__zone_watermark_ok(z, order, mark, classzone_idx, alloc_flags,
zone_page_state(z,NR_FREE_PAGES));
}
__zone_watermark_ok函数
直接是对__zone_watermark_ok的调用,__zone_watermark_ok在page_alloc.c中实现,代码如下:
1548 static bool __zone_watermark_ok(struct zone *z, int order,unsigned long mark,
1549 intclasszone_idx, int alloc_flags, long free_pages)
1550 {
1551 /* free_pages mygo negative - that's OK */
1552 long min = mark;
1553 int o;
1554
1555 free_pages -= (1<< order) - 1;
1556 if (alloc_flags& ALLOC_HIGH)
1557 min -=min / 2;
1558 if (alloc_flags& ALLOC_HARDER)
1559 min -=min / 4;
1560
1561 if (free_pages<= min + z->lowmem_reserve[classzone_idx])
1562 returnfalse;
1563 for (o = 0; o< order; o++) {
1564 /* At thenext order, this order's pages become unavailable */
1565 free_pages-= z->free_area[o].nr_free << o;
1566
1567 /*Require fewer higher order pages to be free */
1568 min>>= 1;
1569
1570 if(free_pages <= min)
1571 return false;
1572 }
1573 return true;
1574 }
对一个区域水位判断的影响有分配标记,一个区域包含若干水位线,分配标准可以指定使用那条水位线。分配标记还可以知道分配的难度的标志ALLOC_HARDER。高端内存的分配标志ALLOC_HIGH也可以影响水位判断,毕竟有高端内存意味着较大的物理内存,还有“低端内存”,保留少些内存对系统的较小。
变量min代表最少应该保留的页数
1555行减去即将要分配的内存页数
1556,1557行如果分配的是高端内存,把水位减半
1558,1559行如果指定了高难度内存分配,再减去水位线的1/4
1561行如果最受应该保留的页数加上区域应该保留的页面数大于等于空闲页数,返回水位判断失败
1563-1572的循环减去阶小于oeder的页面,如果剩余页面数低于最少应该保留的页面数,返回水位判断失败。
1573返回水位判断成功。
get_page_from_freelist函数
回到get_page_from_freelist函数,
1805 if (NUMA_BUILD && !did_zlc_setup && nr_online_nodes >1) {
1806 /*
1807 * we do zlc_setup if there are multiple nodes
1808 * and before considering the first zone allowed
1809 * by the cpuset.
1810 */
1811 allowednodes = zlc_setup(zonelist, alloc_flags);
1812 zlc_active = 1;
1813 did_zlc_setup = 1;
1814 }
1805行判断如果开启了numa,did_zlc_setup配置没有启用,在线节点数大于1
1811行调用zlc_setup设置区域缓存信息,1812行设置zlc_active标志,1813行设置did_zlc_setup,这样在一次get_page_from_freelist函数调用中只会调用一次zlc_setup函数。在get_page_from_freelist函数中可能会进行几次分配,但只会进入1806-1813的代码一次,这样保证了只会对区域进行一次内存充足检查。
zlc_setup函数
zlc_setup在page_alloc.c中实现,代码如下:
1618 static nodemask_t *zlc_setup(struct zonelist *zonelist, intalloc_flags)
1619 {
1620 structzonelist_cache *zlc; /* cachedzonelist speedup info */
1621 nodemask_t*allowednodes; /* zonelist_cacheapproximation */
1622
1623 zlc =zonelist->zlcache_ptr;
1624 if (!zlc)
1625 returnNULL;
1626
1627 if(time_after(jiffies, zlc->last_full_zap + HZ)) {
1628 bitmap_zero(zlc->fullzones, MAX_ZONES_PER_ZONELIST);
1629 zlc->last_full_zap = jiffies;
1630 }
1631
1632 allowednodes =!in_interrupt() && (alloc_flags & ALLOC_CPUSET) ?
1633 &cpuset_current_mems_allowed :
1634 &node_states[N_HIGH_MEMORY];
1635 returnallowednodes;
1636 }
zonelist_cache有个成员last_full_zap,用来保存最后进行区域缓存信息设置的时间,1623行判断区域列表指向的区域缓存结构是否为空,为空返回空节点掩码。
1627判断在现在是不是最近一次设置的一秒之后,避免频繁进行设置。
1628行把zlc->fullzones全部清零,即表示每个区域内存都不充足。
1629更新最后设置时间
zlc_setup函数还要返回一个节点掩码地址,这分两种情况,如果不再中断中而且分配选项指定了ALLOC_CPUSET,则返回cpuset_current_mems_allowed的地址,否则返回&node_states[N_HIGH_MEMORY],node_states如下定义:
nodemask_tnode_states[NR_NODE_STATES] __read_mostly = {
[N_POSSIBLE] =NODE_MASK_ALL,
[N_ONLINE] = { { [0] =1UL } },
#ifndef CONFIG_NUMA
[N_NORMAL_MEMORY] = {{ [0] = 1UL } },
#ifdef CONFIG_HIGHMEM
[N_HIGH_MEMORY] = { {[0] = 1UL } },
#endif
[N_CPU] = { { [0] =1UL } },
#endif /* NUMA */
};
这样区域缓存设置通过两个方面影响了内存分配,一是内存内存是否充足信息,二是会改名节点掩码。
get_page_from_freelist函数
回到get_page_from_freelist函数
1816 if (zone_reclaim_mode == 0)
1817 goto this_zone_full;
1818
1819 /*
1820 * As we may have just activated ZLC, check if the first
1821 * eligible zone has failed zone_reclaim recently.
1822 */
1823 if (NUMA_BUILD && zlc_active &&
1824 !zlc_zone_worth_trying(zonelist, z, allowednodes))
1825 continue;
1826
1827 ret = zone_reclaim(zone, gfp_mask, order);
1828 switch (ret) {
1829 case ZONE_RECLAIM_NOSCAN:
1830 /* did not scan */
1831 continue;
1832 case ZONE_RECLAIM_FULL:
1833 /* scanned but unreclaimable */
1834 continue;
1835 default:
1836 /* did we reclaim enough */
1837 if (!zone_watermark_ok(zone, order, mark,
1838 classzone_idx, alloc_flags))
1839 gotothis_zone_full;
1840 }
在系统中定义了一个变量zone_reclaim_mode,如果变量的值为假,则不进行内存回收,直接跳到标号try_this_zone进行内存分配。1816-1817实现了这个功能。
1823行进行了区域缓存信息的检查和1759行的代码是一样的,即判断区域是否内存充足
1827行是内存回收的代码,不同的情况会返回不同的结果,内存回收的代码中内存交换部分进行分析。1829行是没有进行内存的情况,1832行是没有回收到内存,如不不是这两种情况,表示已经回收了一些内存,在1837行再进行一次水位线判断,如果通过水位线判断,则在本区域进行分配。
下面是get_page_from_freelist函数的最后一部分代码
1843 try_this_zone:
1844 page =buffered_rmqueue(preferred_zone, zone, order,
1845 gfp_mask, migratetype);
1846 if (page)
1847 break;
1848 this_zone_full:
1849 if(NUMA_BUILD)
1850 zlc_mark_zone_full(zonelist, z);
1851 }
1852
1853 if(unlikely(NUMA_BUILD && page == NULL && zlc_active)) {
1854 /*Disable zlc cache for second zonelist scan */
1855 zlc_active = 0;
1856 goto zonelist_scan;
1857 }
1858 return page;
1859 }
1844行调用buffered_rmqueue函数进行伙伴系统分配。1846-1847行如果分配到了内存则退出循环。
如果开始了numa选项,通过上面一些判断,总是把区域设置为内存充足,这是调用函数zlc_mark_zone_full实现的。zlc_mark_zone_full的代码比较简单,这里就不分析了。
1853-1857行的作用是:如果开启了numa选项,本次分配失败,并且本次分配实现了区域缓存信息,则关闭区域缓存信息,不在不进行内存是否充足的情况下再进行一次分配。
1858行返回分配结果。
第三阶段
buffered_rmqueue函数
第三阶段的分析从buffered_rmqueue开始,参数preferred_zone是首选区域,一般是在本地节点上的区域,这里传过来主要用来做统计,一般如果preferred_zone区域内存足够,会在preferred_zone区域分配,因为preferred_zone是第一个被扫描的区域。zone 是本次要进行分配的区域;order 是分配阶,gfp_flags 是分配标志位;migratetype是迁移类型。
buffered_rmqueue在page_alloc.c里面实现,代码如下:
1386 static inline
1387 struct page *buffered_rmqueue(struct zone *preferred_zone,
1388 struct zone *zone, int order, gfp_t gfp_flags,
1389 int migratetype)
1390 {
1391 unsigned longflags;
1392 struct page*page;
1393 int cold =!!(gfp_flags & __GFP_COLD);
1394
1395 again:
1396 if (likely(order== 0)) {
1397 structper_cpu_pages *pcp;
1398 structlist_head *list;
1399
1400 local_irq_save(flags);
1401 pcp =&this_cpu_ptr(zone->pageset)->pcp;
1402 list =&pcp->lists[migratetype];
1403 if (list_empty(list)){
1404 pcp->count += rmqueue_bulk(zone, 0,
1405 pcp->batch, list,
1406 migratetype, cold);
1393行定义了变量cold用作冷热标记,热页的访问效率要高些,冷热标记的意思是该页的内存是否已经加载进cpu缓存,冷页很可能现在没有被加载进cpu缓存,热页现在很可能已经加载进cpu缓存,一般最近被访问的页加载进cpu缓存的可能性要大些。在每个cpu都有一个cpu内存页缓存结构,定义如下:
struct per_cpu_pages {
int count; 列表包含的总页面数
int high; /* high watermark, emptying needed*/
int batch; 但本cpu缓存分配完后,会从伙伴系统分配一定数量的页到cpu缓存链表里,这个变量代表每次分配的页面数。
/* Lists of pages, oneper migrate type stored on the pcp-lists */
struct list_headlists[MIGRATE_PCPTYPES]; //列表数组,每种迁移类型一个链表。
};
1396-1417但分配阶为0,也就是分配一页内存的时候进入这段代码,这段代码的逻辑是,1400行关中断,1401行获得每cpu内存页缓存结构,1402行获得相应的迁移类型链表。1403如果链表为空,则运行1404行代码,调用rmqueue_bulk函数从伙伴系统分配每cpu内存页缓存结构成员batch数据的页数到刚才获得的链表list。
rmqueue_bulk函数
rmqueue_bulk函数在page_alloc.c里面实现,代码如下:
1069 static int rmqueue_bulk(struct zone *zone, unsigned int order,
1070 unsigned long count, struct list_head *list,
1071 int migratetype, intcold)
1072 {
1073 int i;
1074
1075 spin_lock(&zone->lock);
1076 for (i = 0; i< count; ++i) {
1077 structpage *page = __rmqueue(zone, order, migratetype);
1078 if (unlikely(page == NULL))
1079 break;
1080
1081 /*
1082 * Splitbuddy pages returned by expand() are received here
1083 * inphysical page order. The page is added to the callers and
1084 * listand the list head then moves forward. From the callers
1085 *perspective, the linked list is ordered by page number in
1086 * someconditions. This is useful for IO devices that can
1087 * mergeIO requests if the physical pages are ordered
1088 *properly.
1089 */
1090 if(likely(cold == 0))
1091 list_add(&page->lru, list);
1092 else
1093 list_add_tail(&page->lru, list);
1094 set_page_private(page, migratetype);
1095 list =&page->lru;
1096 }
1097 __mod_zone_page_state(zone, NR_FREE_PAGES, -(i << order));
1098 spin_unlock(&zone->lock);
1099 return i;
1100 }
整个代码的逻辑比较简单,1075获得区域保护链表的自旋锁
1076进入一个循环
1077调用__rmqueue函数在分配页面,__rmqueue函数中后面进行分析
1078-1079 如果分配失败,退出循环
1090-1091如冷标志为假,把分配到的页面加入到链表头。
1092-1093是冷标志为真的情况,把分配到的页面加入到链表尾。
1094设置页面迁移类型。
1095把链表头设置为本页面的lru链表。
1097设置空闲页面数据信息。
1098行释放自旋锁。
1099行返回获得的块数。
buffered_rmqueue函数
1407 if (unlikely(list_empty(list)))
1408 goto failed;
1409 }
1410
1411 if (cold)
1412 page = list_entry(list->prev, struct page, lru);
1413 else
1414 page = list_entry(list->next, struct page, lru);
1415
1416 list_del(&page->lru);
1417 pcp->count--;
1418 } else {
1411-1412行如果冷标志为真,则从链表头获得页面,否则1413-1414行长链表尾获得页面.
1416行把页面从lru链表摘除。
1417行减少pcp变量的块计数。
1419 if(unlikely(gfp_flags & __GFP_NOFAIL)) {
1420 /*
1421 * __GFP_NOFAIL is not to be used in new code.
1422 *
1423 * All __GFP_NOFAILcallers should be fixed so that they
1424 * properly detect and handle allocation failures.
1425 *
1426 * We most definitely don't want callers attempting to
1427 * allocate greater than order-1 page units with
1428 * __GFP_NOFAIL.
1429 */
1430 WARN_ON_ONCE(order > 1);
1431 }
1432 spin_lock_irqsave(&zone->lock, flags);
1433 page =__rmqueue(zone, order, migratetype);
1434 spin_unlock(&zone->lock);
1435 if(!page)
1436 goto failed;
1437 __mod_zone_page_state(zone,NR_FREE_PAGES, -(1 << order));
1438 }
1419-1431是调试代码,这里不做分析
1432行获得区域的链表的自旋锁,1433行调用__rmqueue函数进行分配,先分析完buffered_rmqueue函数,在分析完buffered_rmqueue函数后再分析__rmqueue函数。
1437行因为刚才分配出去了2^order个页面,把空闲的页面计数减少2^order个,是调用__mod_zone_page_state函数实现的,__mod_zone_page_state代码比较简单,这里不分析了。
1440 __count_zone_vm_events(PGALLOC,zone, 1 << order);
1441 zone_statistics(preferred_zone, zone, gfp_flags);
1442 local_irq_restore(flags);
1443
1444 VM_BUG_ON(bad_range(zone, page));
1440行增加分配事件计数,这里会记录每个cpu上总共分配的页面数
1441调用zone_statistics函数记录统计信息,在zone_statistics函数里主要记录了,在首选区域分配内存次数(NUMA_HIT),不是在首选区域分配内存次数(NUMA_MISS),在外部区域分配内存次数(NUMA_FOREIGN),在本地节点分配内存次数(NUMA_LOCAL),在其他节点分配次数(NUMA_OTHER),zone_statistics函数代码比较简单,这里不做分析。
1442行释放自旋锁。
1444行VM_BUG_ON是一个调试宏,bad_range对页面进行检查。
bad_range函数
bad_range在page_alloc.c里面实现,代码如下:
263 static int bad_range(struct zone *zone, struct page *page)
264 {
265 if (page_outside_zone_boundaries(zone,page))
266 return 1;
265行调用page_outside_zone_boundaries函数。
page_outside_zone_boundaries函数
page_outside_zone_boundaries函数也在page_alloc.c里面实现,代码如下:
234 static int page_outside_zone_boundaries(struct zone *zone,struct page *page)
235 {
236 int ret = 0;
237 unsigned seq;
238 unsigned long pfn = page_to_pfn(page);
239
240 do {
241 seq =zone_span_seqbegin(zone);
242 if (pfn >=zone->zone_start_pfn + zone->spanned_pages)
243 ret = 1;
244 else if (pfn <zone->zone_start_pfn)
245 ret = 1;
246 } while (zone_span_seqretry(zone,seq));
247
248 return ret;
249 }
每个区域结构有个变量zone_start_pfn表示本区域的第一个页面的帧号,spanned_pages表示区域页面的总数。238行获得页面的帧号,241获得区域顺序锁,顺序锁避免在检测期间,zone的数据被改变,被改名则需要总线做一次检测,242-245行检测页帧是不是在区域的范围内,不是把ret变量值为真,246行释放区域顺序锁,在获得顺序锁期间,如果顺序锁没有被写者获取,则zone_span_seqretry会返回0,表示读成功,否则表示在读取数据期间数据被写者重写了,要重新检测一次。
bad_range函数
267 if(!page_is_consistent(zone, page))
268 return 1;
269
270 return 0;
271 }
267行调用page_is_consistent函数判断页面是否容许的。不过是不允许的,返回1
270行返回1
page_is_consistent函数
page_is_consistent函数在page_alloc.c里面实现,参数zone是区域,page是页面几个指针,代码如下:
251 static int page_is_consistent(struct zone *zone, struct page*page)
252 {
253 if(!pfn_valid_within(page_to_pfn(page)))
254 return 0;
255 if (zone != page_zone(page))
256 return 0;
257
258 return 1;
259 }
253行判断页帧是不是合法的,在page_outside_zone_boundaries函数是判断页帧在区域中,在里是分配页帧在整个系统是不是合法的,pfn_valid_within是一个宏,定义为,pfn_valid,pfn_valid也是一个宏,定义为
#define pfn_valid(pfn) ((pfn)>= ARCH_PFN_OFFSET && ((pfn) - ARCH_PFN_OFFSET) < max_mapnr)
ARCH_PFN_OFFSET是一个平台的最小页面便宜,max_mapnr是最大页面数。
255行调用page_zone函数判断page所在的区域是不是zone。
page_zone函数
page_zone函数在include/linux/mm.h中定义,代码如下:
static inline struct zone *page_zone(conststruct page *page)
{
return&NODE_DATA(page_to_nid(page))->node_zones[page_zonenum(page)];
}
为了节省内存,在page结构的变量flags中用若干位来保存节点号,另外用若干位保存区域类型。这里调用page_to_nid函数获得节点号码,并用宏NODE_DATA转换为节点结构,在节点结构里包含node_zones变量是一个区域数组,再用page_zonenum函数获得区域类型,这些函数都比较简单,这里不做分析。
buffered_rmqueue函数
1445 if(prep_new_page(page, order, gfp_flags))
1446 goto again;
1445行调用prep_new_page函数为新分配的页做些早期准备工作,如果准备工作是否,则调到again标号出重新进行一次分配。
prep_new_page函数
prep_new_page函数在mm/page_alloc.c中实现,代码如下:
817 static int prep_new_page(struct page *page, int order, gfp_tgfp_flags)
818 {
819 int i;
820
821 for (i = 0; i < (1 << order);i++) {
822 struct page *p = page + i;
823 if(unlikely(check_new_page(p)))
824 return 1;
825 }
826
821-825行一个循环,调用check_new_page函数对块中的每个页面进行检查,如果一个页面没有通过检查,则返回真,表示准备新页面出错。
check_new_page函数
check_new_page函数在mm/page_alloc.c中实现,代码如下:
804 static inline int check_new_page(structpage *page)
805{
806 if (unlikely(page_mapcount(page) |
807 (page->mapping !=NULL) |
808 (atomic_read(&page->_count) != 0) |
809 (page->flags &PAGE_FLAGS_CHECK_AT_PREP) |
810 (mem_cgroup_bad_page_check(page)))) {
811 bad_page(page);
812 return 1;
813 }
814 return 0;
815}
check_new_page函数对页面进行一些检查,确定页面是否被使用,如不被使用返回真值,表示检查的页面已经被使用。806行调用page_mapcount函数计算页面映射计数,如果映射计数不为零表示页面还行还有使用则,807行判断页面的映射指针是否为空,不为空表示页面正作为匿名页面或文件缓存使用,808行读取引用计数,如果引用计算不为零,表示页面还被应用,809判断页面的标志位有没有被设置,810判断页面是不是被mem cgroup使用,如果以上条件的或成立,则调用bad_page打印一些调试信息,并且重置映射计数。
prep_new_page函数
827 set_page_private(page, 0);
828 set_page_refcounted(page);
829
830 arch_alloc_page(page, order);
831 kernel_map_pages(page, 1 <<order, 1);
832
833 if (gfp_flags & __GFP_ZERO)
834 prep_zero_page(page, order, gfp_flags);
835
836 if (order && (gfp_flags &__GFP_COMP))
837 prep_compound_page(page,order);
838
839 return 0;
840 }
827行设置page页面的private成员为0
828行把页面的引用计算设置为1,刚分配的页面只有一个使用则
830行arch_alloc_page是个空函数,什么也不做
833-834行如果分配标记设置了__GFP_ZERO,把页面清零。
836-837行,如果设置了大页面标记(__GFP_COMP),并且分配阶不为0(因为大页面比较要用第零个页面之后的页面来保存一些数据,索引最少要保护两个页面,也就是分配阶不能为零。
prep_compound_page函数:
prep_compound_page函数在mm/page_alloc.c中实现,代码如下:
343 void prep_compound_page(struct page *page, unsigned long order)
344 {
345 int i;
346 int nr_pages = 1 << order;
347
348 set_compound_page_dtor(page,free_compound_page);
349 set_compound_order(page, order);
350 __SetPageHead(page);
351 for (i = 1; i < nr_pages; i++) {
352 struct page *p = page + i;
353 __SetPageTail(p);
354 set_page_count(p, 0);
355 p->first_page = page;
356 }
357 }
大页面在第零个页面只后,也就是第一个页面的lru结构的next成员保存一个函数地址,在释放页面的时候会调用,在第一个页面的lru结构的prev结构保存了分配阶,在释放的时候用来获得阶。
353行对第一个页面到最后一个页面设置PG_head_tail_mask标记,表示是大页面页面,但不是大页面的第零个页面。
354行设置引用计数为0
355 行把大页面的后面页面的first_page指针指向第零个页面。
buffered_rmqueue函数
1447 return page;
1448
1449 failed:
1450 local_irq_restore(flags);
1451 return NULL;
1452 }
buffered_rmqueue函数的最后一部分代码比较简单,返回分配结果,failed标记后开中断,返回空指针。
下面分析__rmqueue函数。
__rmqueue函数
1038 static struct page *__rmqueue(struct zone *zone, unsigned intorder,
1039 int migratetype)
1040 {
1041 struct page*page;
1042
1043 retry_reserve:
1044 page =__rmqueue_smallest(zone, order, migratetype);
1045
1046 if(unlikely(!page) && migratetype != MIGRATE_RESERVE) {
1047 page =__rmqueue_fallback(zone, order, migratetype);
1048
1049 /*
1050 * UseMIGRATE_RESERVE rather than fail an allocation. goto
1051 * isused because __rmqueue_smallest is an inline function
1052 * and wewant just one call site
1053 */
1054 if(!page) {
1055 migratetype = MIGRATE_RESERVE;
1056 goto retry_reserve;
1057 }
1058 }
1059
1060 trace_mm_page_alloc_zone_locked(page, order, migratetype);
1061 return page;
1062 }
__rmqueue函数有两个主要执行流程,两个流程的主要区别是是分配的时候是否要进行内存迁移。1044行调用__rmqueue_smallest函数进行分配,__rmqueue_smallest函数分配的时候不进行内存迁移。
__rmqueue_smallest函数
__rmqueue_smallest函数在mm/page_alloc.c中实现,代码如下:
846 static inline
847 struct page*__rmqueue_smallest(struct zone *zone, unsigned int order,
848 int migratetype)
849 {
850 unsigned int current_order;
851 struct free_area * area;
852 struct page *page;
853
854 /* Find a page of the appropriate sizein the preferred list */
855 for (current_order = order;current_order < MAX_ORDER; ++current_order) {
856 area =&(zone->free_area[current_order]);
857 if(list_empty(&area->free_list[migratetype]))
858 continue;
859
860 page =list_entry(area->free_list[migratetype].next,
861 struct page, lru);
862 list_del(&page->lru);
863 rmv_page_order(page);
864 area->nr_free--;
865 expand(zone, page, order,current_order, area, migratetype);
866 return page;
867 }
868
869 return NULL;
870 }
855行从申请分配的阶开始直到系统最大的允许的阶,进行一个循环扫描。
每个区域包含一个free_area成员是free_area结构数组,856行以order为数组下标,获得一个free_area结构指针。
free_area结构包含一个成员free_list,是一个链表数组,每种迁移类型对应一个链表,链表中的项是页面块。860行以迁移类型为下标,area结构的成员free_list的链表中获得next项的page结构指针。
862行把获得的块从page摘除,863行清除分配阶。Page结构包含一个成员private,在块的第一个页,伙伴系统用来保存分配阶。rmv_page_order函数做了两项工作,把映射计数置位,在把分配阶设置为0,也就是吧page的成员private设置为0.
因为分配了一块,864行对空闲块计数减1
865行调用expand函数对分配到的块进行分割,在分析完__rmqueue_smallest函数后再对expand函数进行分析。
866行后面的代码只是返回分配结果。
在上面我们提出来块的概念,所谓块是在一个区域中连续的若干页,包含2^order个页面。在每个系统里,order会有个最大值MAX_ORDER -1。如果一个块A的阶是order(order<MAX_ORDER -1),第一个页面的页帧为pfn,则另一个阶也是order,并且首页帧是(pfn^(1<<order))的块B是块A的伙伴。
伙伴分配系统分配的时候并不一定会刚好分配到我们所需要的页的阶大小,可能会分配到比我们所需要的阶大的块,这时分配到的页面是我们所申请分配大小的2^n(n是自然数)次大小。这时候我们需要把页展开,所谓展开把一个块分成两个伙伴块,把其中的一块归还到相应的空闲链表,如果剩余的一块已经是我们申请分配的大小,则展开已经完成,否则循环进行这样过程。这个循环总会结束,因为每次循环剩余块的大小就会减半。
展开这个过程是由expand函数完成的。
expand函数
expand函数在mm/page_alloc.c中实现,代码如下:
767static inline void expand(struct zone *zone, struct page *page,
768 int low, int high, struct free_area *area,
769 int migratetype)
770{
771 unsigned long size = 1 << high;
772
773 while (high > low) {
774 area--;
775 high--;
776 size >>= 1;
777 VM_BUG_ON(bad_range(zone,&page[size]));
778
779#ifdef CONFIG_DEBUG_PAGEALLOC
780 if (high <debug_guardpage_minorder()) {
781 /*
782 * Mark as guard pages(or page), that will allow to
783 * merge back toallocator when buddy will be freed.
784 * Corresponding pagetable entries will not be touched,
785 * pages will stay notpresent in virtual address space
786 */
787 INIT_LIST_HEAD(&page[size].lru);
788 set_page_guard_flag(&page[size]);
789 set_page_private(&page[size], high);
790 /* Guard pages are notavailable for any usage */
791 __mod_zone_page_state(zone, NR_FREE_PAGES, -(1 << high));
792 continue;
793 }
794#endif
795 list_add(&page[size].lru,&area->free_list[migratetype]);
796 area->nr_free++;
797 set_page_order(&page[size], high);
798 }
799}
771行获得块包含的页面数。
773行是一个循环,high是被展开的页面的阶,low是要展开成的页面的阶,high>low这个循环就继续。
area传进来时候是指向high的空闲区域结构指针,每次进入循环都代码吧一个块分成两个伙伴块,所以447行area要自减,775行high也要自减,776包含页面数也要右移一位,相当于除以2。777行bad_range前面已经分析过。
779-794是调试代码,这里不分析。
795行,把分开的两个伙伴块帧帧大的一块归还到空闲区域中,796行增加空闲区域的空闲块计算,797行设置阶,每个块,阶是保存在第零个页的page结构的成员private中。
__rmqueue_fallback函数
在一个区域(zone)中包含若干空闲区域(free_area),每个空闲区域,包含若干迁移类型。每个迁移类型都有一个空闲链表,链表里面的每项都管理空闲块。伙伴系统内存分配的最终就是从一个空闲链表里面摘除一项并返回摘除项对应的物理内存地址给使用者。
在分配的时候对一个迁移类型,我们并不总是在这个迁移类型的空闲链表里去分配内存,我们也可以到其他迁移类型去进行分配,除非一个迁移类型(MIGRATE_RESERVE)不允许把内存分配给其他迁移类型。__rmqueue_fallback在分配内存的时候使用这个方法。
__rmqueue_fallback函数中mm/page_alloc.c中实现,代码如下:
965 staticinline struct page *
966 __rmqueue_fallback(struct zone *zone, intorder, int start_migratetype)
967 {
968 struct free_area * area;
969 int current_order;
970 struct page *page;
971 int migratetype, i;
972
973 /* Find the largest possible block of pages in the other list */
974 for (current_order = MAX_ORDER-1; current_order >= order;
975 --current_order) {
976 for (i = 0; i <MIGRATE_TYPES - 1; i++) {
977 migratetype =fallbacks[start_migratetype][i];
978
979 /* MIGRATE_RESERVEhandled later if necessary */
980 if (migratetype ==MIGRATE_RESERVE)
981 continue;
982
983 area =&(zone->free_area[current_order]);
984 if(list_empty(&area->free_list[migratetype]))
985 continue;
986
987 page =list_entry(area->free_list[migratetype].next,
988 structpage, lru);
989 area->nr_free--;
974行的循环是从最大分配阶开始循环的,这是为了避免在新的迁移类型中引入碎片。为什么?现在假如A类型内存不足,向B类型求援,假设只从B中分配一块最适合的小块,OK,那么过会儿又请求分配A类型内存,又得向B类型求援,这样来来回回从B类型中一点一点的分配内存将会导致B类型的内存四处都散布碎片。
976行循环MIGRATE_TYPES – 1次
在系统中定义了一个二维迁移数组,为每个迁移类型指定了从其他迁移类型去借内存的循环顺序。977行获得本次循环的迁移类型,就说这次循环从那个迁移类型去“借”内存。
如果迁移类型为MIGRATE_RESERVE,这不能从这个迁移类型借内存。
983行获得空闲区域指针,984-985对判断空闲区域当前扫描的迁移类型链表是否为空。为空扫描继续一个循环。
987行依据空闲区和迁移类型的链表获得page结构地址。
987行自减空闲区域空闲块计数
990
991 /*
992 * If breaking a largeblock of pages, move all free
993 * pages to thepreferred allocation list. If falling
994 * back for areclaimable kernel allocation, be more
995 * aggressive abouttaking ownership of free pages
996 */
997 if(unlikely(current_order >= (pageblock_order >> 1)) ||
998 start_migratetype == MIGRATE_RECLAIMABLE ||
999 page_group_by_mobility_disabled){
1000 unsigned longpages;
1001 pages =move_freepages_block(zone, page,
1002 start_migratetype);
1003
1004 /* Claim thewhole block if over half of it is free */
1005 if (pages>= (1 << (pageblock_order-1)) ||
1006 page_group_by_mobility_disabled)
1007 set_pageblock_migratetype(page,
1008 start_migratetype);
1009
1010 migratetype =start_migratetype;
1011 }
1012
1013 /* Remove the pagefrom the freelists */
1014 list_del(&page->lru);
1015 rmv_page_order(page);
1016
1017 /* Take ownership fororders >= pageblock_order */
1018 if (current_order>= pageblock_order)
1019 change_pageblock_range(page, current_order,
1020 start_migratetype);
1021
1022 expand(zone, page,order, current_order, area, migratetype);
1023
1024 trace_mm_page_alloc_extfrag(page, order, current_order,
1025 start_migratetype, migratetype);
1026
1027 return page;
1028 }
1029 }
在搬迁工作中,如果分配到的是一个较大块,则意味着被借的迁移类型内存比较充足。另外有一种迁移类型(MIGRATE_RECLAIMABLE),在别去其他迁移类型借的会多借一些过来。在系统中有关变量(page_group_by_mobility_disabled),当这个变量为真值时,没次进行迁移都会把迁移类型设置成MIGRATE_RECLAIMABLE,也就是不可迁移。
997-999是判断分配的页够不够大,迁移的源类型是不是MIGRATE_RECLAIMABLE,以及变量page_group_by_mobility_disabled的值。
1001行调用move_freepages_block函数进行空闲链表迁移。
move_freepages_block函数
move_freepages_block函数在mm/page_alloc.c中实现,代码如下:
932 static int move_freepages_block(structzone *zone, struct page *page,
933 intmigratetype)
934{
935 unsigned long start_pfn, end_pfn;
936 struct page *start_page, *end_page;
937
938 start_pfn = page_to_pfn(page);
939 start_pfn = start_pfn & ~(pageblock_nr_pages-1);
940 start_page = pfn_to_page(start_pfn);
941 end_page = start_page + pageblock_nr_pages - 1;
942 end_pfn = start_pfn + pageblock_nr_pages - 1;
943
944 /* Do not cross zone boundaries */
945 if (start_pfn < zone->zone_start_pfn)
946 start_page = page;
947 if (end_pfn >= zone->zone_start_pfn + zone->spanned_pages)
948 return 0;
949
950 return move_freepages(zone, start_page, end_page, migratetype);
951}
move_freepages_block函数对一个范围内的页面进行迁移。这样范围的第一个页面由参数page传进来,页面数由宏pageblock_nr_pages定义。pageblock_nr_pages的值分两种情况,在配置在大页选项的情况下pageblock_nr_pages等于一个大页包含的页面数,否则等于最大空闲页包含的页面数。
这个函数只是求出第一个要迁移的页面page结构指针,最后一个要迁移的页面page结构指针,调用move_freepages函数进行迁移。
move_freepages函数
move_freepages把一个范围的页面迁移到一个区域的迁移类型空闲链表中。参数zone是要进行迁移到区域,迁移只能在一起区域内进行,不能把一个区域的页面迁移到另一个区域。start_page 是要迁移到第一个页面结构地址,end_page是最后一个要迁移到页面结构地址。migratetype 是要迁移到的迁移类型。move_freepages在mm/page_alloc.c中实现,代码如下:
889static int move_freepages(struct zone *zone,
890 struct page*start_page, struct page *end_page,
891 int migratetype)
892{
893 struct page *page;
894 unsigned long order;
895 int pages_moved = 0;
896
897#ifndef CONFIG_HOLES_IN_ZONE
898 /*
899 * page_zone is not safe to call in this context when
900 * CONFIG_HOLES_IN_ZONE is set. This bug check is probably redundant
901 * anyway as we check zone boundaries in move_freepages_block().
902 * Remove at a later date when no bug reports exist related to
903 * grouping pages by mobility
904 */
905 BUG_ON(page_zone(start_page) != page_zone(end_page));
906#endif
907
908 for (page = start_page; page <= end_page;) {
909 /* Make sure we are notinadvertently changing nodes */
910 VM_BUG_ON(page_to_nid(page) !=zone_to_nid(zone));
911
912 if(!pfn_valid_within(page_to_pfn(page))) {
913 page++;
914 continue;
915 }
916
917 if (!PageBuddy(page)) {
918 page++;
919 continue;
920 }
921
922 order = page_order(page);
923 list_move(&page->lru,
924 &zone->free_area[order].free_list[migratetype]);
925 page += 1 << order;
926 pages_moved += 1 <<order;
927 }
928
929 return pages_moved;
930}
895行定义实际迁移的页面数量并初始化为0。
908行从第一个页面开始进行循环,值到最后一个页面。
910行对节点号进行检查。
912行对页帧号进行检查。
917行检查页面是不是buddy系统的页面。
922行获得阶。
923-924行进行空闲链表迁移。
925行对增加page地址。
926行对迁移页面数加1 << order
929返回迁移的页面数量。
第四阶段 伙伴系统内存释放
free_pages函数
伙伴系统释放页面的主函数是free_pages,在mm/page_alloc.c中实现,代码如下:
2517 void free_pages(unsigned long addr,unsigned int order)
2518 {
2519 if (addr != 0) {
2520 VM_BUG_ON(!virt_addr_valid((void *)addr));
2521 __free_pages(virt_to_page((void *)addr), order);
2522 }
2523 }
free_pages函数只是在地址不为零的情况下简单调用virt_to_page函数从物理地址得到该地址的管理结构page的地址,然后调用__free_pages函数释放内存。
virt_to_page宏
virt_to_page宏在arch/x86/include/asm/page.h中定义:
#definevirt_to_page(kaddr) pfn_to_page(__pa(kaddr) >> PAGE_SHIFT)
__pa是一个宏,在arch/x86/include/asm/page.h中定义:
36 #define __pa(x) __phys_addr((unsigned long)(x))
__phys_addr是一个宏,在arch/x86/include/asm/page_32.h中定义
16 #define __phys_addr(x) __phys_addr_nodebug (x)
__phys_addr_nodebug也是一个宏,在arch/x86/include/asm/page_32.h中定义
#define __phys_addr_nodebug(x) ((x) - PAGE_OFFSET)
在x86上,PAGE_OFFSET的值是0
一层一层拨开后__pa(x)也就是返回x的值
pfn_to_page也是一个宏,在include/asm-generic/memory_model.h中定义
30 #define __pfn_to_page(pfn) (mem_map + ((pfn) - ARCH_PFN_OFFSET))
ARCH_PFN_OFFSET也是个宏,在include/asm-generic/memory_model.h中定义
8 #ifndef ARCH_PFN_OFFSET
9#define ARCH_PFN_OFFSET (0UL)
10#endif
pfn_to_page只是在全局变量mem_map加上页帧号获得该页帧的page管理结构。
virt_to_page通过宏__pa返回物理地址,物理地址右移PAGE_SHIFT位后得到页帧,页帧号加上全局变量mem_map就得到地址所对应的page管理结构地址。
__free_pages函数
2505void __free_pages(struct page *page, unsigned int order)
2506 {
2507 if (put_page_testzero(page)) {
2508 if (order == 0)
2509 free_hot_cold_page(page, 0);
2510 else
2511 __free_pages_ok(page,order);
2512 }
2513 }
__free_pages函数2507行调用函数put_page_testzero在include/linux/mm.h中定义
275 static inline intput_page_testzero(struct page *page)
276{
277 VM_BUG_ON(atomic_read(&page->_count) == 0);
278 return atomic_dec_and_test(&page->_count);
279}
atomic_dec_and_test函数对原子类型的变量原子地减1,并判断结果是否为0,如果为0,返回真,否则返回假。
put_page_testzero函数的就是自减引用计算,并返回结构。
__free_pages函数2507行自减页面引用计数并返回当然自减后测试结果,如果自减后的引用计数为0,表面没有使用则,则分两种情况释放页面,阶为零的情况调用free_hot_cold_page函数释放页面,否则调用__free_pages_ok释放页面。 free_hot_cold_page函数中伙伴系统的内存迁移一节一节分析过。
free_pages_prepare函数
free_pages_prepare函数主要做释放内存的前期工作,在mm/page_alloc.c中实现,代码如下:
690static bool free_pages_prepare(struct page *page, unsigned int order)
691{
692 int i;
693 int bad = 0;
694
695 trace_mm_page_free(page, order);
696 kmemcheck_free_shadow(page, order);
697
698 if (PageAnon(page))
699 page->mapping = NULL;
700 for (i = 0; i < (1 << order); i++)
701 bad += free_pages_check(page +i);
702 if (bad)
703 return false;
704
705 if (!PageHighMem(page)) {
706 debug_check_no_locks_freed(page_address(page),PAGE_SIZE<<order);
707 debug_check_no_obj_freed(page_address(page),
708 PAGE_SIZE << order);
709 }
710 arch_free_page(page, order);
711 kernel_map_pages(page, 1 << order, 0);
712
713 return true;
714}
695行是调试代码,696行是关于kmemcheck内存调试的代码。
701行调用free_pages_check函数对要释放的页面做检查,free_pages_check在前面已经分析过
710行的arch_free_page函数是空函数
如果不打开内存调试宏kernel_map_pages也是个空函数。
__free_pages_ok函数
__free_pages_ok在mm/page_alloc.c中实现,代码如下:
716 static void __free_pages_ok(struct page*page, unsigned int order)
717{
718 unsigned long flags;
719 int wasMlocked = __TestClearPageMlocked(page);
720
721 if (!free_pages_prepare(page, order))
722 return;
723
724 local_irq_save(flags);
725 if (unlikely(wasMlocked))
726 free_page_mlock(page);
727 __count_vm_events(PGFREE, 1 << order);
728 free_one_page(page_zone(page), page, order,
729 get_pageblock_migratetype(page));
730 local_irq_restore(flags);
731}
__free_pages_ok函数721行调用free_pages_prepare函数做释放前的准备工作
724行关中断
725-726行并对mlock页调用free_page_mlock函数。
727行增加释放页面的统计计数。
728-729行调用free_one_page函数释放内存。获得迁移类型的函数get_pageblock_migratetype在伙伴系统的迁移类型一节已经分析过。
730恢复中断。
free_one_page函数
free_one_page函数在mm/page_alloc.c中实现,代码如下:
678static void free_one_page(struct zone *zone, struct page *page, int order,
679 intmigratetype)
680{
681 spin_lock(&zone->lock);
682 zone->all_unreclaimable = 0;
683 zone->pages_scanned = 0;
684
685 __free_one_page(page, zone, order, migratetype);
686 __mod_zone_page_state(zone, NR_FREE_PAGES, 1 << order);
687 spin_unlock(&zone->lock);
688}
681获得回环锁
682-683行是因为向伙伴系统释放了内存,通知内存交换系统这样区域已经可以进行回收和需要再次扫描。
685行调用__free_one_page释放函数,因为进入free_one_page函数的时候已经关中断,681行也获得了回旋锁,所以__free_one_page是指原子上下文中执行,不会不中断,页不会被其他cpu打扰。
686行增加区域的空闲页计数。
687释放回旋锁。
__free_one_page函数
伙伴系统的内存释放工作是在__free_one_page函数中完成的,进入__free_one_page函数的时候已经关了中断,并且也已经获取了区域的自锁锁,这样保证__free_one_page执行是原子的。
__free_one_page函数参数page是要释放的块的首页的page结构地址;zone是要把空闲块释放到的区域结构指针;order是要释放的块的阶,migratetype是要释放的块的迁移类型。__free_one_page函数在mm/page_alloc.c中实现,代码如下:
524 static inline void __free_one_page(structpage *page,
525 struct zone *zone, unsignedint order,
526 int migratetype)
527{
528 unsigned long page_idx;
529 unsigned long combined_idx;
530 unsigned long uninitialized_var(buddy_idx);
531 struct page *buddy;
532
533 if (unlikely(PageCompound(page)))
534 if(unlikely(destroy_compound_page(page, order)))
535 return;
536
537 VM_BUG_ON(migratetype == -1);
538
539 page_idx = page_to_pfn(page) & ((1<< MAX_ORDER) - 1);
540
541 VM_BUG_ON(page_idx & ((1 << order) - 1));
542 VM_BUG_ON(bad_range(zone, page));
543
544 while (order < MAX_ORDER-1) {
545 buddy_idx = __find_buddy_index(page_idx,order);
546 buddy = page + (buddy_idx -page_idx);
547 if (!page_is_buddy(page,buddy, order))
548 break;
549 /*
550 * Our buddy is free or it isCONFIG_DEBUG_PAGEALLOC guard page,
551 * merge with it and move upone order.
552 */
553 if (page_is_guard(buddy)) {
554 clear_page_guard_flag(buddy);
555 set_page_private(page, 0);
556 __mod_zone_page_state(zone, NR_FREE_PAGES, 1 << order);
557 } else {
558 list_del(&buddy->lru);
559 zone->free_area[order].nr_free--;
560 rmv_page_order(buddy);
561 }
562 combined_idx = buddy_idx &page_idx;
563 page = page + (combined_idx -page_idx);
564 page_idx = combined_idx;
565 order++;
566 }
567 set_page_order(page, order);
568
569 /*
570 * If this is not the largest possible page, check if the buddy
571 * of the next-highest order is free. If it is, it's possible
572 * that pages are being freed that will coalesce soon. In case,
573 * that is happening, add the free page to the tail of the list
574 * so it's less likely to be used soon and more likely to be merged
575 * as a higher order page
576 */
577 if ((order < MAX_ORDER-2) &&pfn_valid_within(page_to_pfn(buddy))) {
578 struct page *higher_page,*higher_buddy;
579 combined_idx = buddy_idx &page_idx;
580 higher_page = page +(combined_idx - page_idx);
581 buddy_idx =__find_buddy_index(combined_idx, order + 1);
582 higher_buddy = page +(buddy_idx - combined_idx);
583 if (page_is_buddy(higher_page,higher_buddy, order + 1)) {
584 list_add_tail(&page->lru,
585 &zone->free_area[order].free_list[migratetype]);
586 goto out;
587 }
588 }
589
590 list_add(&page->lru,&zone->free_area[order].free_list[migratetype]);
591out:
592 zone->free_area[order].nr_free++;
593}
对伙伴系统的每个空闲块,都有一个对应阶order,阶的最大值是MAX_ORDER-1。对阶不是最大阶的块,都有一个伙伴块,伙伴块的阶也是order。如果把阶是最大值MAX_ORDER-1,起始页帧号是2^ (MAX_ORDER-1)的倍数的块叫做最大块。则伙伴块都在同一最大块中,也就是说伙伴块的页帧号大于MAX_ORDER-1的位是相同的。在迁移类型一节中我们知道,同一最大块的迁移类型是相同的。
伙伴系统内存释放就是要把伙伴块合并成一个更大的块,直到块已经是最大块或者当前要释放的块的伙伴块不是空闲块为止。这样可以防止内存碎片。
对一个页的页帧号,我们把0到MAX_ORDER-1位的部分叫内部页帧号,两个伙伴块的页帧号不同只在内存页帧号。
局部变量page_idx是当前要释放的块的内部页帧号,page是其首页的page管理结构地址。buddy_idx是当前要释放的页的伙伴的页帧号,buddy是其首页的page管理结构地址。
539行获得当前要释放块的首页内部帧号。
545行获取伙伴块的首页帧号。一个阶为的order块的首页帧号和伙伴块的首页帧号只是在order为不同而已,__find_buddy_index函数把当前页号的onder位做异或就得到了伙伴块的首页帧号。
546获得伙伴块的首页的page结构地址。
547-548判断两个块是不是伙伴块。不是则退出循环。
553-557是用于调试的代码。
558-560行把伙伴块从所在的空闲链表中摘除,减少所在的空闲区域的空闲块计数,并清除阶性息,因为合并后的首页可能不再是这一页。
562获得是当前块和伙伴或的内部页帧号中较小的一个,也就是合并后的块的首页帧号。
563-564从新设置当前块首页的page管理结构地址和当前块的首页内部页帧号。
567保存阶,块的阶保存在块的首页的page管理结构的成员private中。
577-588如果合并后的当前块还不是最大块,则把当前块和当前块的伙伴块作为一个更大的块与上一阶阶的伙伴块尝试进行一次合并。如果合并是允许的,把当前块释放到空闲链表的末尾,使其更加晚的分配出去,因为分配的时候总是先分配链表头的块。因为这意味着如果当前块的伙伴块被释放的话,就可以合并成更大的块。使可能合成更大块的块更晚的分配出去,有防止内存碎片的作用。
590把合并后的块链接到空闲链表中。
592自增空闲区域空闲块计数。
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 ancjf@163.com
文章标题:linux 源代码分析3:伙伴系统内存分配
本文作者:ancjf
发布时间:2013-05-20, 19:29:04
最后更新:2020-04-18, 15:57:30
原始链接:http://ancjf.com/2013/05/20/linux-3-4-10-%E5%86%85%E6%A0%B8%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86%E6%BA%90%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%903-%E4%BC%99%E4%BC%B4%E7%B3%BB%E7%BB%9F%E5%86%85%E5%AD%98%E5%88%86%E9%85%8D/版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。