伙伴系统分配器 - __alloc_pages
来源:互联网 发布:会计理解 知乎 编辑:程序博客网 时间:2024/05/20 04:11
kernel可以通过几个分配函数从伙伴系统分配页面:
alloc_pages
get_zeroed_page
get_dma_pages
这几个函数都是通过alloc_pages来实现页面分配的,而alloc_pages的核心实现就是__alloc_pages。
alloc_pages
在gfp.h中定义
#define alloc_pages(gfp_mask, order) \ alloc_pages_node(numa_node_id(), gfp_mask, order)
两个参数:@gfp_mask 申请标志位;@order 申请页面的阶数
MAX_ORDER是一个宏,定义了buddy系统最大的阶数,大于这个阶数的申请是注定失败的。系统缺省定义是11,也就是说2^11 = 2048个pages。这个值可以通过架构特定的配置FORCE_MAX_ZONEORDER来修改缺省值,一般来说我们不需要更改这个值。要注意的是GPU VPU等申请的连续内存区比较大,并且这些驱动不会频繁分配释放,系统不会通过buddy系统分配内存,而是使用预留内存的方式。
@gfp_mask可参看 伙伴系统分配器 分配掩码
numa_node_id获得当前执行CPU对应的node id
alloc_page_node
static inline struct page *alloc_pages_node(int nid, gfp_t gfp_mask, unsigned int order){ if (unlikely(order >= MAX_ORDER)) return NULL; /* Unknown node is current node */ if (nid < 0) nid = numa_node_id(); return __alloc_pages(gfp_mask, order, NODE_DATA(nid)->node_zonelists + gfp_zone(gfp_mask));}
在alloc_pages_node中做了一件很重要的事,计算__alloc_pages的第三个参数NODE_DATA(nid)->node_zonelists + gfp_zone(gfp_mask)
第三个参数是用来通知__alloc_pages首先从该node的哪一个zone进行分配,node_zonelists是节点的zone链表,一般顺序是DMA, Normal, Highmem;
gfp_zone(gfp_mask)计算给定的分配域(包含在gfp_mask)中,所对应的zone偏移。这个偏移加上node_zonelists就是选择的内存域。
__alloc_pages
文件mm/page_alloc.c中实现,是zone buddy的核心分配函数,分析该函数前,我们先了解一些可能的标记和重要的辅助函数
辅助标记
kenel定义了一些函数需要使用的标记,用来控制内存区空闲页面数达到不同watermark时的分配行为
#define ALLOC_NO_WATERMARKS 0x01 /* don't check watermarks at all */#define ALLOC_WMARK_MIN 0x02 /* use pages_min watermark */#define ALLOC_WMARK_LOW 0x04 /* use pages_low watermark */#define ALLOC_WMARK_HIGH 0x08 /* use pages_high watermark */#define ALLOC_HARDER 0x10 /* try to alloc harder */#define ALLOC_HIGH 0x20 /* __GFP_HIGH set */#define ALLOC_CPUSET 0x40 /* check for correct cpuset */
这些标志用来表示也分配过程中,需要考虑当前内存区的哪些分配水印。内存区的三个水印:zone->pages_min, zone->page_low, zone->page_high
默认情况下,仅有内存域包含的页数大于page_high时,才会进行分配。
ALLOC_NO_WATERMARKS 完全不检查水印,也就是略过分配页的选择过程,直接调用buffered_rmqueue进行分配。
ALLOC_WMARK_MIN 在当前分配区使用zone->pages_min进行检查。
ALLOC_WMARK_LOW 在当前分配区使用zone->pages_low进行检查。
ALLOC_WMAEK_HIGH 在当前分配区使用zone->pages_high进行检查
ALLOC_HARDER 通知伙伴系统放宽检查限制,其实就是对水印给定的值乘以一个系数 3/4
ALLOC_HIGH 比HARDER更紧急的分配请求,进一步放宽限制
ALLOC_CPUSET 只能在当前节点相关连的内存节点进行分配。
zone_watermark_ok
上诉标志会在函数zone_watermark_ok中检查
1215 /*1216 * Return 1 if free pages are above 'mark'. This takes into account the order1217 * of the allocation.1218 */1219 int zone_watermark_ok(struct zone *z, int order, unsigned long mark,1220 int classzone_idx, int alloc_flags)1221 {1222 /* free_pages my go negative - that's OK */1223 long min = mark;1224 long free_pages = zone_page_state(z, NR_FREE_PAGES) - (1 << order) + 1;1225 int o;1226 1227 if (alloc_flags & ALLOC_HIGH)1228 min -= min / 2;1229 if (alloc_flags & ALLOC_HARDER)1230 min -= min / 4;1231 1232 if (free_pages <= min + z->lowmem_reserve[classzone_idx])1233 return 0;1234 for (o = 0; o < order; o++) {1235 /* At the next order, this order's pages become unavailable */1236 free_pages -= z->free_area[o].nr_free << o;1237 1238 /* Require fewer higher order pages to be free */1239 min >>= 1;1240 1241 if (free_pages <= min)1242 return 0;1243 }1244 return 1;1245 }
返回1 表示满足给定的水印,0表示不满足。
zone_page_state返回给定zone的free pages数目。
1232 首先要判断空闲页面数目,分配给定的free_pages是否还能满足min 和z->lowmem_reserver[],从这个可以看出lowmem_reserve是不包含min的。lowmem_reserve的作用有点小复杂,可以参考另外一篇文章lowmem_reserve的理解
1234 ~ 1243做循环,对于小于给定参数@order的buddy链表,要把他们的容量从free_pages中减去,因为这些页面对当前分配请求来说和非空闲页面没有区别,
1239 每一次循环,所需空闲页的最小值折半,这也是个经验算法。
1241 如果发现空闲页面小于mark,那么说明已经剩余的页面无法满足分配了,直接失败退出。
1244 表明当前内存zone满足分配的请求。
get_page_from_freelist
/* * get_page_from_freelist goes through the zonelist trying to allocate * a page. */static struct page *get_page_from_freelist(gfp_t gfp_mask, unsigned int order,struct zonelist *zonelist, int alloc_flags){struct zone **z;struct page *page = NULL;int classzone_idx = zone_idx(zonelist->zones[0]);struct zone *zone;nodemask_t *allowednodes = NULL;/* zonelist_cache approximation */int zlc_active = 0;/* set if using zonelist_cache */int did_zlc_setup = 0;/* just call zlc_setup() one time */enum zone_type highest_zoneidx = -1; /* Gets set for policy zonelists */zonelist_scan:/* * Scan zonelist, looking for a zone with enough free. * See also cpuset_zone_allowed() comment in kernel/cpuset.c. */z = zonelist->zones;do {/* * In NUMA, this could be a policy zonelist which contains * zones that may not be allowed by the current gfp_mask. * Check the zone is allowed by the current flags */if (unlikely(alloc_should_filter_zonelist(zonelist))) {if (highest_zoneidx == -1)highest_zoneidx = gfp_zone(gfp_mask);if (zone_idx(*z) > highest_zoneidx)continue;}if (NUMA_BUILD && zlc_active &&!zlc_zone_worth_trying(zonelist, z, allowednodes))continue;zone = *z;if ((alloc_flags & ALLOC_CPUSET) &&!cpuset_zone_allowed_softwall(zone, gfp_mask))goto try_next_zone;if (!(alloc_flags & ALLOC_NO_WATERMARKS)) {unsigned long mark;if (alloc_flags & ALLOC_WMARK_MIN)mark = zone->pages_min;else if (alloc_flags & ALLOC_WMARK_LOW)mark = zone->pages_low;elsemark = zone->pages_high;if (!zone_watermark_ok(zone, order, mark, classzone_idx, alloc_flags)) {if (!zone_reclaim_mode || !zone_reclaim(zone, gfp_mask, order))goto this_zone_full;}}page = buffered_rmqueue(zonelist, zone, order, gfp_mask);if (page)break;this_zone_full:if (NUMA_BUILD)zlc_mark_zone_full(zonelist, z);try_next_zone:if (NUMA_BUILD && !did_zlc_setup) {/* we do zlc_setup after the first zone is tried */allowednodes = zlc_setup(zonelist, alloc_flags);zlc_active = 1;did_zlc_setup = 1;}} while (*(++z) != NULL);if (unlikely(NUMA_BUILD && page == NULL && zlc_active)) {/* Disable zlc cache for second zonelist scan */zlc_active = 0;goto zonelist_scan;}return page;}这个函数分为两个步骤:
1. 选择要分配的页
2. 移除步骤1中选择的页,主要由buffered_rmqueue实现
参数@zonelist指向备用内存区链表的指针。在预期内存区中(zonelist->zone[0])没有足够空闲空间的情况下,该列表确定了扫描系统其他内存域的顺序。
do循环是遍历zonelist,找到一个满足分配条件的内存区,如果成功,则调用buffer_rmqueue试图分配需要的页面。
__alloc_pages
在了解可用标记和辅助函数后,我们可以开始分析__alloc_pages函数了。该函数实现比较复杂,尤其是在可用内存不充足的情况下;如果可用内存重组,该函数的流程还是非常简单的。
/* * This is the 'heart' of the zoned buddy allocator. */struct page * fastcall__alloc_pages(gfp_t gfp_mask, unsigned int order, struct zonelist *zonelist){ const gfp_t wait = gfp_mask & __GFP_WAIT; struct zone **z; struct page *page; struct reclaim_state reclaim_state; struct task_struct *p = current; int do_retry; int alloc_flags; int did_some_progress; might_sleep_if(wait); if (should_fail_alloc_page(gfp_mask, order)) return NULL;restart: z = zonelist->zones; /* the list of zones suitable for gfp_mask */ if (unlikely(*z == NULL)) { /* * Happens if we have an empty zonelist as a result of * GFP_THISNODE being used on a memoryless node */ return NULL; } page = get_page_from_freelist(gfp_mask|__GFP_HARDWALL, order, zonelist, ALLOC_WMARK_LOW|ALLOC_CPUSET); if (page) goto got_pg;
在最简单的情况下,只调用一次get_page_from_freelist就成功的获得了分配的页面,直接通过跳转指令到got_pg处。
for (z = zonelist->zones; *z; z++) wakeup_kswapd(*z, order); /* * OK, we're below the kswapd watermark and have kicked background * reclaim. Now things get more complex, so set up alloc_flags according * to how we want to proceed. * * The caller may dip into page reserves a bit more if the caller * cannot run direct reclaim, or if the caller has realtime scheduling * policy or is asking for __GFP_HIGH memory. GFP_ATOMIC requests will * set both ALLOC_HARDER (!wait) and ALLOC_HIGH (__GFP_HIGH). */ alloc_flags = ALLOC_WMARK_MIN; if ((unlikely(rt_task(p)) && !in_interrupt()) || !wait) alloc_flags |= ALLOC_HARDER; if (gfp_mask & __GFP_HIGH) alloc_flags |= ALLOC_HIGH; if (wait) alloc_flags |= ALLOC_CPUSET; /* * Go through the zonelist again. Let __GFP_HIGH and allocations * coming from realtime tasks go deeper into reserves. * * This is the last chance, in general, before the goto nopage. * Ignore cpuset if GFP_ATOMIC (!wait) rather than fail alloc. * See also cpuset_zone_allowed() comment in kernel/cpuset.c. */ page = get_page_from_freelist(gfp_mask, order, zonelist, alloc_flags); if (page) goto got_pg;
内核再一次遍历所有的内存zone,对每个zone都调用wakeup_kswapd,该函数唤醒负责换出页的内核守护进程。交换守护进程通过缩减内核缓存和页面回收来获得更多的空闲内存,缩减内核缓存和页面回涉及到页面写回或者换出很少使用的页面。这两种措施都是由守护进程发起的。
唤醒守护进程后,内核开始重新尝试从内存zones中查找合适的内存块。这一次搜索更为积极,对分配标志做了调整,修改为在一些特定情况下需要的分配标记,因此也减小了水印。
如果再次失败,内核会使用更积极的分配措施:
rebalance: if (((p->flags & PF_MEMALLOC) || unlikely(test_thread_flag(TIF_MEMDIE))) && !in_interrupt()) { if (!(gfp_mask & __GFP_NOMEMALLOC)) {nofail_alloc: /* go through the zonelist yet again, ignoring mins */ page = get_page_from_freelist(gfp_mask, order, zonelist, ALLOC_NO_WATERMARKS); if (page) goto got_pg; if (gfp_mask & __GFP_NOFAIL) { congestion_wait(WRITE, HZ/50); goto nofail_alloc; } } goto nopage; }
TIF_MEMDIE表示该进程已经被oom killer选择中,而PF_MEMALLOC比较复杂,我们单独讨论,PF_MEMALLOC表示当前进程是内存管理程序,需要一点点额外的内存,以便能继续执行下去。
__GFP_NOMMEALLOC表示禁止使用紧急分配链表,因此无法再尝试禁止水印的情况下调用get_page_from_freelist,此时只能失败。
如果允许使用紧急分配链表,则使用标志ALLOC_NO_WATERMAERKS尝试分配,如果失败,还要看分配标志是否有__GFP_NOFAIL,该标志表示不允许失败,首先调用conestion_wait等待,然后再尝试分配,直到成功。
1560 p->flags |= PF_MEMALLOC;1561 reclaim_state.reclaimed_slab = 0;1562 p->reclaim_state = &reclaim_state;1563 1564 did_some_progress = try_to_free_pages(zonelist->zones, order, gfp_mask);1565 1566 p->reclaim_state = NULL;1567 p->flags &= ~PF_MEMALLOC;
在调用try_to_free_pages之前,先设置PF_MEMALLOC保证try_to_free_pages可以分配预留内存,try_to_free_pages不仅会把不常用页面交换到交换空间,还会shrink各种cache
1574 if (likely(did_some_progress)) {1575 page = get_page_from_freelist(gfp_mask, order,1576 zonelist, alloc_flags);1577 if (page)1578 goto got_pg;1579 } else if ((gfp_mask & __GFP_FS) && !(gfp_mask & __GFP_NORETRY)) {1580 if (!try_set_zone_oom(zonelist)) {1581 schedule_timeout_uninterruptible(1);1582 goto restart;1583 }1584 1585 /*1586 * Go through the zonelist yet one more time, keep1587 * very high watermark here, this is only to catch1588 * a parallel oom killing, we must fail if we're still1589 * under heavy pressure.1590 */1591 page = get_page_from_freelist(gfp_mask|__GFP_HARDWALL, order,1592 zonelist, ALLOC_WMARK_HIGH|ALLOC_CPUSET);1593 if (page) {1594 clear_zonelist_oom(zonelist);1595 goto got_pg;1596 }1597 1598 /* The OOM killer will not help higher order allocs so fail */1599 if (order > PAGE_ALLOC_COSTLY_ORDER) {1600 clear_zonelist_oom(zonelist);1601 goto nopage;1602 }1603 1604 out_of_memory(zonelist, gfp_mask, order);1605 clear_zonelist_oom(zonelist);1606 goto restart;1607 }
did_some_progress表示的确释放了一些页面,那么再尝试进行分配;否则内核正在做VFS层的操作,同时又没有设置GFP_NORETRY,那么调用OOM killer
1599行表示如果分配的阶数很大,那么即便调用OOM很大的几率仍然无法满足2^order分配,所以不执行oom killer。这个解释听起来不那么合理的,因为有时候一个进程可能占据几十M的内存空间,杀掉它,必然会释放很大的内存空间,获得连续空间的几率应该也很大的。
1609 /*1610 * Don't let big-order allocations loop unless the caller explicitly1611 * requests that. Wait for some write requests to complete then retry.1612 *1613 * In this implementation, __GFP_REPEAT means __GFP_NOFAIL for order1614 * <= 3, but that may not be true in other implementations.1615 */1616 do_retry = 0;1617 if (!(gfp_mask & __GFP_NORETRY)) {1618 if ((order <= PAGE_ALLOC_COSTLY_ORDER) ||1619 (gfp_mask & __GFP_REPEAT))1620 do_retry = 1;1621 if (gfp_mask & __GFP_NOFAIL)1622 do_retry = 1;1623 }1624 if (do_retry) {1625 congestion_wait(WRITE, HZ/50);1626 goto rebalance;1627 }
看注释,意思是说在分配标志没有NORETRY的情况下,要考虑几种情况,没什么可分析的。
- 伙伴系统分配器 - __alloc_pages
- Linux伙伴系统分配器
- 伙伴系统分配器 - buffered_rmqueue
- 伙伴系统分配器 - __free_pages
- 伙伴系统分配器 分配掩码
- 伙伴系统分配器 - PF_MEMALLOC 标志位
- linux内存管理--伙伴系统和内存分配器
- [linux内存]伙伴系统学习笔记(三)--分配器API
- Linux内存管理--伙伴系统和内存分配器
- 伙伴地址,伙伴系统
- 伙伴分配器的一个极简实现
- 伙伴分配器的一个极简实现
- 伙伴分配器的一个极简实现
- 伙伴分配器的一个极简实现
- 伙伴系统
- 伙伴系统
- 伙伴系统
- 伙伴系统
- 浅谈MVC模式在游戏开发的应用
- 关于Flurry的数据统计
- 如何用Instruments来分析应用程序的性能瓶颈
- ODBC相关(转)
- APP STORE 付费验证(IAP)服务端验证全过程
- 伙伴系统分配器 - __alloc_pages
- iOS Safari检测并打开App的思路分析
- iOS人脸识别技术
- C++反射机制的实现
- 求指定区间的质数-python版 【Sphere OJ】
- ASIHTTPRequest
- 利用TEA算法进行数据加密
- android Theme使用总结
- HEVC的码率控制的相关提案