上面章节将kswapd内核线程的整理逻辑体现出来了,本节将介绍其中的很多细节函数。
1、balance_pgdat函数
balance_pgdat函数是回收页面的主函数。这个函数比较长,查看时可以先看整体框架。
/*负责在指定NUMA节点上回收内存,直至满足分配请求或达到回收上限*/
static unsigned long balance_pgdat(pg_data_t *pgdat, int order/*期望分配的连续物理页阶数*/,
int *classzone_idx/*输入为本次分配请求涉及的最高内存域,输出为本次回收实际触及的最高内存域*/)
{
int i;
/*本次回收需要扫描的最高zone索引*/
int end_zone = 0; /* Inclusive. 0 = ZONE_DMA */
unsigned long nr_soft_reclaimed;
unsigned long nr_soft_scanned;
/*linux内核内存回收子系统中的核心控制结构,它封装了一次内存回收操作的所有控制参数和结果统计*/
struct scan_control sc = {
.gfp_mask = GFP_KERNEL,
.order = order, /*目标分配阶*/
.priority = DEF_PRIORITY, /*扫描优先级,初始值12,越低越激进*/
.may_writepage = !laptop_mode, /*是否允许回写脏页*/
.may_unmap = 1, /*是否允许解除页面映射,为1允许解除映射回收存在映射的页面*/
.may_swap = 1, /*是否允许交换页面,为1允许使用swap分区*/
};
count_vm_event(PAGEOUTRUN);
do {
/*统计在当前优先级下,尝试回收页面的总次数,用于判断是否需要进行内存规整*/
unsigned long nr_attempted = 0;
/*标志位,指示是否需要提高回收优先级(即变得更激进)*/
bool raise_priority = true;
/*标志位,指示本次回收是否需要考虑内存规整,order>0就需要内存规则*/
bool pgdat_needs_compaction = (order > 0);
/*回收页面数置零*/
sc.nr_reclaimed = 0;
/*
* Scan in the highmem->dma direction for the highest
* zone which needs scanning
*/
/*
第一轮扫描,确定回收范围end_zone
从高端zone向低端zone反向扫描,这是为了与内核内存分配器从高到低查找zone的顺序匹配,避免重复扫描
*/
for (i = pgdat->nr_zones - 1; i >= 0; i--) {
struct zone *zone = pgdat->node_zones + i;
if (!populated_zone(zone)) /*zone是否为空*/
continue;
/*
当回收压力增大(priority降低)时,跳过那些被标记为不可回收的zone(如ZONE_MOVABLE)
*/
if (sc.priority != DEF_PRIORITY &&
!zone_reclaimable(zone)/*zone无需再进行回收*/)
continue;
/*
* Do some background aging of the anon list, to give
* pages a chance to be referenced before reclaiming.
*/
/*对匿名页的活跃LRU链表进行"老化"处理,非活跃匿名页面较少时,将部分(最多32个)活跃页面移至非活跃链表,为回收做准备*/
age_active_anon(zone, &sc);
/*
* If the number of buffer_heads in the machine
* exceeds the maximum allowed level and this node
* has a highmem zone, force kswapd to reclaim from
* it to relieve lowmem pressure.
*/
/*
这是一个特殊优化。
buffer_head是内核中用于管理块设备缓冲区的核心数据结构(定义在<linux/buffer_head.h>),主要关联文件系统的元数据(如inode、目录项)和磁盘块的缓存,
每个buffer_head对应一个磁盘块(通常512B~4KB),用于跟踪缓冲区的状态(如脏页、锁定、I/O进度等)
当系统buffer_heads过多且有高端内存时,强制从高端内存回收,以缓解低端内存压力。
如果触发,则设置end_zone并跳出循环
*/
if (buffer_heads_over_limit && is_highmem_idx(i)) {
end_zone = i;
break;
}
/*
调用zone_balanced()检查当前zone的水位是否满足order阶分配的需求后仍为高水位值
如果不满足(!zone_balanced),说明此zone及其之下的所有zone都需要回收,记录end_zone = i并跳出循环
如果满足(zone_balanced),说明此zone内存充足,清除其ZONE_CONGESTED(拥塞)和ZONE_DIRTY(脏页)标志,表示无需特殊处理
*/
if (!zone_balanced(zone, order, 0/*balance_gap为0*/, 0)) {
end_zone = i;
break;
} else {
/*
* If balanced, clear the dirty and congested
* flags
*/
clear_bit(ZONE_CONGESTED, &zone->flags);
clear_bit(ZONE_DIRTY, &zone->flags);
}
}
if (i < 0) /*如果上一轮扫描未找到任何不平衡的zone(i=-1),说明整个节点内存充足,直接跳转到out退出*/
goto out;
/*
第二轮扫描,判断是否需要内存规整
*/
/*从低端zone向高端zone正向扫描,遍历所有需要回收的zone*/
for (i = 0; i <= end_zone; i++) {
struct zone *zone = pgdat->node_zones + i;
if (!populated_zone(zone))
continue;
/*
* If any zone is currently balanced then kswapd will
* not call compaction as it is expected that the
* necessary pages are already available.
*/
/*
如果仍需规整,但发现某个zone的内存水位已达到低水位(low_wmark_pages),则认为该zone有足够的空闲页面,无需规整。
将pgdat_needs_compaction置为false
*/
if (pgdat_needs_compaction &&
zone_watermark_ok(zone, order,
low_wmark_pages(zone),
*classzone_idx, 0))
pgdat_needs_compaction = false;
}
/*
* If we're getting trouble reclaiming, start doing writepage
* even in laptop mode.
*/
/*
当回收优先级变得很低(即回收压力大)时,强制开启sc.may_writepage,即使在省电的laptop_mode下也允许回写脏页,以释放更多内存
*/
if (sc.priority < DEF_PRIORITY - 2)
sc.may_writepage = 1; /*回收压力大,允许回写操作,回收脏页*/
/*
* Now scan the zone in the dma->highmem direction, stopping
* at the last zone which needs scanning.
*
* We do this because the page allocator works in the opposite
* direction. This prevents the page allocator from allocating
* pages behind kswapd's direction of progress, which would
* cause too much scanning of the lower zones.
*/
/*
第三轮扫描,执行实际回收
*/
/*再次从低端向高端扫描,对end_zone及以下的每个zone执行回收*/
for (i = 0; i <= end_zone; i++) {
struct zone *zone = pgdat->node_zones + i;
if (!populated_zone(zone))
continue;
if (sc.priority != DEF_PRIORITY &&
!zone_reclaimable(zone))
continue;
/*重置本轮对该zone的扫描页面计数*/
sc.nr_scanned = 0;
nr_soft_scanned = 0;
/*
* Call soft limit reclaim before calling shrink_zone.
*/
/*优先对超出cgroup软限制的页面进行回收,并更新回收统计*/
nr_soft_reclaimed = mem_cgroup_soft_limit_reclaim(zone,
order, sc.gfp_mask,
&nr_soft_scanned);
sc.nr_reclaimed += nr_soft_reclaimed;
/*
* There should be no need to raise the scanning
* priority if enough pages are already being scanned
* that that high watermark would be met at 100%
* efficiency.
*/
/*
调用kswapd_shrink_zone()对当前zone执行核心回收逻辑。
如果回收效果显著,则将raise_priority置为false,表示无需再提高优先级
*/
if (kswapd_shrink_zone(zone, end_zone,
&sc, &nr_attempted))
raise_priority = false;
}
/*
* If the low watermark is met there is no need for processes
* to be throttled on pfmemalloc_wait as they should not be
* able to safely make forward progress. Wake them
*/
/*
检查是否有进程因内存紧张而在pfmemalloc_wait队列上等待,并且系统整体内存水位是否已恢复到安全水平
*/
if (waitqueue_active(&pgdat->pfmemalloc_wait) &&
pfmemalloc_watermark_ok(pgdat))
wake_up_all(&pgdat->pfmemalloc_wait); /*如果条件满足,则唤醒所有等待的进程,让它们可以继续执行*/
/*
* Fragmentation may mean that the system cannot be rebalanced
* for high-order allocations in all zones. If twice the
* allocation size has been reclaimed and the zones are still
* not balanced then recheck the watermarks at order-0 to
* prevent kswapd reclaiming excessively. Assume that a
* process requested a high-order can direct reclaim/compact.
*/
/*
如果请求的是高阶内存(order > 0),但回收的页面数已远超需求(2^order的两倍),说明内存碎片严重,难以分配连续内存
回收量达标但合并失败,已回收页面数达到2^order+1页(目标阶数的2倍),却仍无法形成order阶连续块,说明碎片过多无法合并
*/
if (order && sc.nr_reclaimed >= 2UL << order)
order = sc.order = 0; /*将目标阶数降级为0,转为优先保证基础内存(单页)的充足,避免过度回收*/
/*检查退出条件*/
/* Check if kswapd should be suspending */
if (try_to_freeze() || kthread_should_stop())
break;
/*
* Compact if necessary and kswapd is reclaiming at least the
* high watermark number of pages as requsted
*/
/*触发内存规整*/
/*
如果仍需规整,并且回收的页面数超过了尝试回收的次数(说明回收有效),则调用compact_pgdat()对该节点执行内存规整,以整理内存碎片
*/
if (pgdat_needs_compaction && sc.nr_reclaimed > nr_attempted)
compact_pgdat(pgdat, order);
/*
* Raise priority if scanning rate is too low or there was no
* progress in reclaiming pages
*/
/*
如果本轮回收效果不佳(未回收足够页面),则降低sc.priority的值(提高扫描激进程度)
*/
if (raise_priority || !sc.nr_reclaimed)
sc.priority--;
} while (sc.priority >= 1 && !pgdat_balanced(pgdat, order, *classzone_idx));
/*当回收优先级降至1以下,或整个节点的内存已满足order阶分配需求时,循环结束*/
out:
/*
* Return the order we were reclaiming at so prepare_kswapd_sleep()
* makes a decision on the order we were last reclaiming at. However,
* if another caller entered the allocator slow path while kswapd
* was awake, order will remain at the higher level
*/
/*将本次回收实际触及的最高zone索引写回,供kswapd下次唤醒时使用*/
*classzone_idx = end_zone;
return order; /*返回本次回收后,kswapd认为可以满足的最高分配阶*/
}
整体循环的退出条件,当回收优先级降至1以下,或整个节点的内存已满足order阶分配需求时,循环结束。
/*判断一个内存节点(Node)是否"平衡",即其管理的所有相关内存区域(Zone)的空闲内存是否充足*/
static bool pgdat_balanced(pg_data_t *pgdat, int order, int classzone_idx)
{
/*累计节点内所有受伙伴系统管理的页面总数*/
unsigned long managed_pages = 0;
/*累计节点内已处于"平衡"状态的页面数量*/
unsigned long balanced_pages = 0;
int i;
/* Check the watermark levels */
for (i = 0; i <= classzone_idx; i++) {
struct zone *zone = pgdat->node_zones + i;
if (!populated_zone(zone)) /*跳过空 Zone*/
continue;
/*统计节点内所有zone 伙伴系统管理的页面数量 managed_pages的数量*/
managed_pages += zone->managed_pages;
/*
* A special case here:
*
* balance_pgdat() skips over all_unreclaimable after
* DEF_PRIORITY. Effectively, it considers them balanced so
* they must be considered balanced here as well!
*/
/*
特殊情况,处理不可回收Zone,对于标记为all_unreclaimable的Zone(如ZONE_MOVABLE在内存紧张时),balance_pgdat()回收逻辑会直接跳过它们,
因此,这里也将其视为"平衡"状态,并将其所有页面计入balanced_pages,以避免kswapd在此类Zone上做无用功
*/
if (!zone_reclaimable(zone)) { /*不可回收zone*/
balanced_pages += zone->managed_pages;
continue;
}
/*判断当前Zone在分配2^order个页面下是否满足水位线要求且能提供order阶的连续内存*/
if (zone_balanced(zone, order, 0, i))
balanced_pages += zone->managed_pages; /*若平衡,将该Zone的所有页面计入balanced_pages*/
else if (!order) /*若不平衡且order为0,对于单页分配,要求所有Zone都必须平衡,一旦发现不平衡的Zone,立即返回false,表示节点不平衡*/
return false;
}
/*
根据order的值返回最终的平衡判断
高阶分配(order > 0),只要"平衡"的页面总数balanced_pages不少于总管理页面数managed_pages的25%(managed_pages >> 2),
就认为节点整体是平衡的。这是一个权衡,避免因个别Zone不平衡而过度回收。
单页分配(order == 0),由于前面已确保所有Zone都平衡,此处直接返回true
*/
if (order)
return balanced_pages >= (managed_pages >> 2);
else
return true;
}
pgdat_balanced()需要注意参数classzone_idx,它表示在页面分配路径上计算出来第一个最合适内存分配的zone的编号,通过wake_all_kswapds()传递下来。
判断zone是否能够进行回收的标准就是扫描次数是否>= 可回收页面的总数*6(匿名页+文件页),如果zone中页面之前被扫描的次数足够多再次扫描意义不大
/*
true(可回收),表示已扫描的页面数相对较少,回收工作尚不充分,zone仍被认为是可回收的
false(不可回收),表示已扫描的页面数过多,但回收效果不佳,说明大部分页面可能已"僵死"或难以回收,zone被视为"基本不可回收"
*/
bool zone_reclaimable(struct zone *zone)
{
/*
该机制旨在避免在可回收页面还很多时,因少量扫描失败就过早放弃回收,
乘以6意味着,内核允许扫描的页面数是可回收页面总数的6倍,这被认为是一个合理的上限。
6这个阈值是在特定内核版本和负载下,通过大量实验确定的"甜点值",
后续版本的内核虽然对此逻辑有过重构,但*6这个经验阈值的核心思想被保留了下来
*/
return zone_page_state(zone, NR_PAGES_SCANNED)/*内存回收过程中,累计扫描过的页面总数*/ <
zone_reclaimable_pages(zone) * 6; /*统计一个内存区域(zone)中当前"真正可回收"的页面总数 *6*/
}
/*
统计一个内存区域(zone)中当前"真正可回收"的页面总数,包括文件页和(在有Swap时)匿名页
*/
static unsigned long zone_reclaimable_pages(struct zone *zone)
{
int nr;
/*
活跃文件页数量+非活跃文件页数量
*/
nr = zone_page_state(zone, NR_ACTIVE_FILE) +
zone_page_state(zone, NR_INACTIVE_FILE);
/*
文件页与磁盘文件关联,回收时可直接丢弃(对于干净页)或写回文件(脏页),因此不依赖swap即可回收
匿名页没有对应的磁盘文件(如进程堆、栈),回收时必须将其内容写入Swap空间才能释放物理内存
如果系统中没有可用的swap(get_nr_swap_pages() == 0),匿名页就无法被回收,将它们计入"可回收页面"总数会严重误导内存管理决策
*/
if (get_nr_swap_pages() > 0) /*可用swap页面总数*/
nr += zone_page_state(zone, NR_ACTIVE_ANON) +
zone_page_state(zone, NR_INACTIVE_ANON); /*活跃匿名页数量+非活跃匿名页数量*/
return nr;
}
注意,zone中可回收页面的统计中对于匿名页面的统计比较特殊,如果设备中无swap分区/文件,那么匿名页面的回收就会存在问题,匿名页面是不纳入可回收页面统计范围内。
/*
内核中kswapd后台线程进行内存回收时,专门处理匿名页老化的函数
其逻辑是,当系统中有swap空间,且某个内存区域(zone)的非活跃匿名页(Inactive Anon)数量过少时,
从活跃匿名页(Active Anon)链表尾部迁移一批页面(32)到非活跃链表,为后续的回收做准备
*/
static void age_active_anon(struct zone *zone, struct scan_control *sc)
{
struct mem_cgroup *memcg;
/*total_swap_pages记录系统中Swap空间的物理总大小(所有可用页面数)*/
if (!total_swap_pages) /*无swap分区则直接返回,无处理活跃匿名页*/
return;
memcg = mem_cgroup_iter(NULL, NULL, NULL);
do {
struct lruvec *lruvec = mem_cgroup_zone_lruvec(zone, memcg);
if (inactive_anon_is_low(lruvec)) /*非活跃匿名页面数量<活跃匿名页面数量*/
/*将活跃匿名链表尾部"可能不再活跃"的页面挑出来,根据访问情况决定,仍然活跃的放回active,不再活跃的移到inactive*/
shrink_active_list(SWAP_CLUSTER_MAX/*32,本次要从active LRU中扫描多少页**/, lruvec,
sc, LRU_ACTIVE_ANON/*活跃匿名页面*/);
memcg = mem_cgroup_iter(NULL, memcg, NULL);
} while (memcg);
}
判断zone中非活跃页面的数量和活跃页面数量之间的关系,非活跃页面数量<活跃页面数量认为inactive is low,
这时可以将活跃链表中近期未被访问的页面放到非活跃链表上,为后续的回收做准备。
/**
* inactive_anon_is_low - check if anonymous pages need to be deactivated
* @lruvec: LRU vector to check
*
* Returns true if the zone does not have enough inactive anon pages,
* meaning some active anon pages need to be deactivated.
*/
static int inactive_anon_is_low(struct lruvec *lruvec)
{
/*
* If we don't have swap space, anonymous page deactivation
* is pointless.
*/
if (!total_swap_pages) /*无swapu分区就无法进行匿名页的回收*/
return 0;
if (!mem_cgroup_disabled())
return mem_cgroup_inactive_anon_is_low(lruvec);
/*判断非活跃匿名页面和活跃匿名页面之间的关系*/
return inactive_anon_is_low_global(lruvec_zone(lruvec));
}
static int inactive_anon_is_low_global(struct zone *zone)
{
unsigned long active, inactive;
active = zone_page_state(zone, NR_ACTIVE_ANON);
inactive = zone_page_state(zone, NR_INACTIVE_ANON);
/*
非活跃匿名页数量*比例系数 < 活跃匿名页数量
当zone内部managed_pages容量小于G时,inactive_ratio的值为1,大于则为根号下(10×gb)的取值
*/
if (inactive * zone->inactive_ratio /*1*/< active)
return 1;
return 0;
}
shrink_active_list函数在后面部分进行详细讲解。
如何评判一个zone是否到达平衡即其内部页面数量充足,能够进行页面的分配?即判断在分配2^order个页面后是否还满足高水位值+balance_gap。
static bool zone_balanced(struct zone *zone, int order,unsigned long balance_gap, int classzone_idx)
{
/*在分配2^order个页面下,是否满足水位值*/
if (!zone_watermark_ok_safe(zone, order, high_wmark_pages(zone) +balance_gap/*高水位+balance_gap值*/, classzone_idx, 0))
return false;
/*
判断指定内存区域(zone)是否适合执行内存压缩(compaction)操作
*/
if (IS_ENABLED(CONFIG_COMPACTION)/*开启内存规则*/ && order && compaction_suitable(zone,
order, 0, classzone_idx) == COMPACT_SKIPPED)
return false;
return true;
}
include/linux/vmstat.h
bool zone_watermark_ok_safe(struct zone *z, unsigned int order,
unsigned long mark, int classzone_idx, int alloc_flags)
{
/*当前内存域zone中完全空闲的页面总数*/
long free_pages = zone_page_state(z, NR_FREE_PAGES);
if (z->percpu_drift_mark && free_pages < z->percpu_drift_mark)/*开启精确计算*/
free_pages = zone_page_state_snapshot(z, NR_FREE_PAGES); /*将zone中各个CPU的空闲内存数量进行汇总计算*/
/*判断水位值是否ok*/
return __zone_watermark_ok(z, order, mark, classzone_idx, alloc_flags,
free_pages);
}
static inline unsigned long zone_page_state_snapshot(struct zone *zone,enum zone_stat_item item)
{
/*
vm_stat[item]是全局基准值,记录所有CPU已同步的统计总和
vm_stat_diff[item]是每个CPU本地未同步的增量
所以该计算更精确,将单个CPU未同步的数据进行统计
*/
/*获取zone->vm_stat[]统计数据*/
long x = atomic_long_read(&zone->vm_stat[item]);
#ifdef CONFIG_SMP
int cpu;
/*强制进行同步操作,将zone中各个CPU的内存统计数据进行汇总*/
for_each_online_cpu(cpu)
x += per_cpu_ptr(zone->pageset, cpu)->vm_stat_diff[item];
if (x < 0)
x = 0;
#endif
return x;
}
判断zone内部的水位值情况,去除需要分配的2^order个页面后,剩余可用页面和调整后水位值之间的关系。
mm/page_alloc.c
bool zone_watermark_ok(struct zone *z, unsigned int order/*分配阶数*/, unsigned long mark,
int classzone_idx, int alloc_flags)
{
return __zone_watermark_ok(z, order, mark, classzone_idx, alloc_flags,zone_page_state(z, NR_FREE_PAGES));
}
static bool __zone_watermark_ok(struct zone *z, unsigned int order,
unsigned long mark, int classzone_idx, int alloc_flags,
long free_pages)
{
/* free_pages may go negative - that's OK */
/*mark值为水位值,分配物理页面后剩余的物理页面和水位值之间的关系*/
long min = mark;
int o;
long free_cma = 0;
/*去除需要分配的物理页面*/
free_pages -= (1 << order) - 1;
/*当分配请求携带ALLOC_HIGH或ALLOC_HARDER标志时,内核会放宽水位检查(如 min减半),允许分配后剩余页略低于lowmem_reserve[]*/
if (alloc_flags & ALLOC_HIGH) /*高优先级分配:水位要求减半,这样对水位的要就就降低,允许在不满足条件下也能优先分配*/
min -= min / 2;
if (alloc_flags & ALLOC_HARDER)
min -= min / 4;
#ifdef CONFIG_CMA
/* If allocation can't use CMA areas don't use free CMA pages */
if (!(alloc_flags & ALLOC_CMA))
free_cma = zone_page_state(z, NR_FREE_CMA_PAGES);
#endif
/*free_pages < min + z->lowmem_reserve[]的空间时拒绝*/
if (free_pages - free_cma <= min + z->lowmem_reserve[classzone_idx])
return false;
for (o = 0; o < order; o++) {
/* At the next order, this order's pages become unavailable */
/*去除 zone free_area中比oder小的各个order中空闲物理页面,因为对于当前order而言,小于order的free_area是没法进行分配的,视为无效*/
free_pages -= z->free_area[o].nr_free << o;
/* Require fewer higher order pages to be free */
min >>= 1; /*min的值同步减半,min /= 2*/
if (free_pages <= min) /*去除需要分配的order个页面后剩余可用页面和min进行比较*/
return false;
}
return true;
}
/*
负责对单个内存区域(zone)执行回收操作,它根据水位线、内存碎片等因素,决定回收多少页面以及何时停止
*/
static bool kswapd_shrink_zone(struct zone *zone,
int classzone_idx,
struct scan_control *sc,
unsigned long *nr_attempted/*输出参数,用于累计本次回收尝试的页数*/)
{
int testorder = sc->order;
/*判断zone是否"足够平衡"的阈值*/
unsigned long balance_gap;
/*标识当前是否为低内存压力场景*/
bool lowmem_pressure;
/* Reclaim above the high watermark. */
/*
SWAP_CLUSTER_MAX在内核中默认定义为32,这是一个经过权衡的经验值,用于平衡I/O效率、CPU开销和系统响应性.
设置回收目标,将本次回收的目标页数sc->nr_to_reclaim设置为"高水位线"和SWAP_CLUSTER_MAX(一次回收的页数簇)中的较大值。
这确保了回收工作至少会进行一轮,以将空闲内存推高到高水位线以上.
*/
sc->nr_to_reclaim = max(SWAP_CLUSTER_MAX/*32*/, high_wmark_pages(zone)/*高水位值*/);
printk("-----%s,sc->nr_to_reclaim:%x%x \n",__func__,sc->nr_to_reclaim);
/*
* Kswapd reclaims only single pages with compaction enabled. Trying
* too hard to reclaim until contiguous free pages have become
* available can hurt performance by evicting too much useful data
* from memory. Do not reclaim more than needed for compaction.
*/
/*
为内存规整优化,如果启用了内存规整(compaction)且当前是分配高阶连续内存(sc->order > 0),则检查该zone是否适合进行规整
如果适合,testorder会被设为0,这意味着,在判断是否"平衡"时,将只要求zone有足够的单页空闲,而不是要求有order阶的连续空闲块,
从而避免因过度回收而影响性能
*/
if (IS_ENABLED(CONFIG_COMPACTION) && sc->order &&
compaction_suitable(zone, sc->order, 0, classzone_idx)
!= COMPACT_SKIPPED)
testorder = 0;
/*
* We put equal pressure on every zone, unless one zone has way too
* many pages free already. The "too many pages" is defined as the
* high wmark plus a "gap" where the gap is either the low
* watermark or 1% of the zone, whichever is smaller.
*/
/*
计算平衡阈值,balance_gap定义了一个"宽松"的平衡区间。
一个zone的空闲页数只要高于high_wmark + balance_gap,就可以被认为是"足够平衡"的。
这避免了在所有zone水位线都略低于high时,kswapd对每个zone都进行过度回收
*/
/*取低水位值和zone->managed_pages/100之间的较小者*/
balance_gap = min(low_wmark_pages(zone)/*低水位值*/, DIV_ROUND_UP(
zone->managed_pages, KSWAPD_ZONE_BALANCE_GAP_RATIO/*100*/));
printk("-----%s,balance_gap:0x%x \n",__func__,balance_gap);
/*
* If there is no low memory pressure or the zone is balanced then no
* reclaim is necessary
*/
/*
lowmem_pressure是一个特定于高端内存的特殊压力标志
*/
lowmem_pressure = (buffer_heads_over_limit && is_highmem(zone));
/*
判断是否平衡,如果当前没有特殊压力,并且zone_balanced()判断该zone在testorder阶和balance_gap阈值下是平衡的,
则无需进一步回收,直接返回true
*/
if (!lowmem_pressure && zone_balanced(zone, testorder,balance_gap, classzone_idx))
return true;
/*
执行实际回收,调用shrink_zone()函数,扫描并回收zone中的页面。
第三个参数指示本次回收是否针对classzone_idx指定的最高优先级zone,这会影响kswapd后续的扫描策略
*/
shrink_zone(zone, sc, zone_idx(zone) == classzone_idx);
/* Account for the number of pages attempted to reclaim */
/*
累计回收尝试量,将本次shrink_zone尝试回收的页数(sc->nr_to_reclaim)累加到输出参数*nr_attempted中。
这个统计值用于上层balance_pgdat()判断是否需要提高回收优先级
*/
*nr_attempted += sc->nr_to_reclaim;
/*
回收工作结束后,清除ZONE_WRITEBACK标志。
这表明最近一次扫描中遇到的"大量正在回写"的情况已得到缓解
*/
clear_bit(ZONE_WRITEBACK, &zone->flags);
/*
* If a zone reaches its high watermark, consider it to be no longer
* congested. It's possible there are dirty pages backed by congested
* BDIs but as pressure is relieved, speculatively avoid congestion
* waits.
*/
/*
如果zone已经恢复到平衡状态(zone_balanced判断通过),则清除ZONE_CONGESTED和ZONE_DIRTY标志
这向系统表明,由I/O拥塞或大量脏页引起的回收压力已经解除,可以恢复正常的内存分配流程
*/
if (zone_reclaimable(zone) &&
zone_balanced(zone, testorder, 0, classzone_idx)) {
clear_bit(ZONE_CONGESTED, &zone->flags);
clear_bit(ZONE_DIRTY, &zone->flags);
}
/*
函数的最终返回值表示本次回收工作的"努力程度"。
如果实际扫描的页数sc->nr_scanned达到了目标sc->nr_to_reclaim,则返回true
*/
return sc->nr_scanned >= sc->nr_to_reclaim;
}