当前位置: 首页 > news >正文

当内存紧张时出现mm_vmscan_direct_reclaim 直接回收,需要对其分析

1. 核心概念说明

mm_vmscan_direct_reclaim 是 Linux 内核内存管理的关键路径,负责同步直接页面回收

  • 触发条件:当进程申请内存时,若空闲页低于 low watermark 且快速分配失败,会触发直接回收(而非唤醒 kswapd)。

  • 关键动作

    • 调用 __perform_reclaimtry_to_free_pages() 同步释放页面。

    • 阻塞申请进程直至回收完成,可能导致性能抖动

2. 性能影响与监控指标

问题现象
  • 系统响应延迟或卡顿(尤其内存密集型任务)。

  • /proc/vmstat 中以下指标显著上升:

pgscan_direct  # 直接回收扫描的页数
pgsteal_direct # 直接回收实际释放的页数
allocstall     # 内存分配停滞次数
 

监控命令

watch -n 1 "grep -e pgscan_direct -e pgsteal_direct -e allocstall /proc/vmstat"

3. 调优建议

调整水位线参数

修改 /proc/sys/vm/lowmem_reserve_ratio 可优化各内存域的保留页比例,减轻直接回收压力

# 示例:降低 Normal 区域的保留比例
echo 256 > /proc/sys/vm/lowmem_reserve_ratio
 

try_to_free_pages 函数功能

unsigned long try_to_free_pages(struct zonelist *zonelist, int order,
7217  				gfp_t gfp_mask, nodemask_t *nodemask)
7218  {
7219  	unsigned long nr_reclaimed;
7220  	struct scan_control sc = {
7221  		.nr_to_reclaim = SWAP_CLUSTER_MAX,
7222  		.gfp_mask = current_gfp_context(gfp_mask),
7223  		.reclaim_idx = gfp_order_zone(gfp_mask, order),
7224  		.order = order,
7225  		.nodemask = nodemask,
7226  		.priority = DEF_PRIORITY,
7227  		.may_writepage = !laptop_mode,
7228  		.may_unmap = 1,
7229  		.may_swap = 1,
7230  	};
7231  	bool skip_swap = false;
7232  
7233  	/*
7234  	 * scan_control uses s8 fields for order, priority, and reclaim_idx.
7235  	 * Confirm they are large enough for max values.
7236  	 */
7237  	BUILD_BUG_ON(MAX_ORDER >= S8_MAX);
7238  	BUILD_BUG_ON(DEF_PRIORITY > S8_MAX);
7239  	BUILD_BUG_ON(MAX_NR_ZONES > S8_MAX);
7240  
7241  	/*
7242  	 * Do not enter reclaim if fatal signal was delivered while throttled.
7243  	 * 1 is returned so that the page allocator does not OOM kill at this
7244  	 * point.
7245  	 */
7246  	if (throttle_direct_reclaim(sc.gfp_mask, zonelist, nodemask))
7247  		return 1;
7248  
7249  	trace_android_vh_tune_scan_control(&skip_swap);
7250  	if (skip_swap)
7251  		sc.may_swap = 0;
7252  	set_task_reclaim_state(current, &sc.reclaim_state);
7253  	trace_mm_vmscan_direct_reclaim_begin(order, sc.gfp_mask);
7254  
7255  	nr_reclaimed = do_try_to_free_pages(zonelist, &sc);
7256  
7257  	trace_mm_vmscan_direct_reclaim_end(nr_reclaimed);
7258  	set_task_reclaim_state(current, NULL);
7259  
7260  	return nr_reclaimed;
7261  }
7262  

这是一个非常经典的内存直接回收函数。当进程申请内存时,如果系统无法快速分配(快速路径失败),且空闲内存低于最低水位线,就会调用这个函数同步地尝试回收内存。下面我们来详细解析它的逻辑。

🔍 函数作用与核心流程

try_to_free_pages 是Linux内核直接内存回收的核心入口。它的目标是立即释放一些内存页,以满足当前进程的分配请求。这个过程是同步的,意味着它会阻塞调用它的进程,直到回收完成或失败。

其最简化的核心流程如下:

  1. 初始化控制参数(第7220-7230行)。
  2. 检查是否应该限制本次回收(第7246行)。
  3. 执行真正的回收操作(第7255行)。
  4. 清理并返回回收的页数(第7257-7260行)。

⚙️ 关键结构体:scan_control

函数的核心是初始化一个 struct scan_control sc。这个结构体就像是给回收器的一份“任务说明书”,决定了回收行为的方方面面:

字段作用解析代码中的值
.nr_to_reclaim本次回收期望释放的页数SWAP_CLUSTER_MAX(通常为32页)
.gfp_mask本次内存分配的标志,回收器会根据此标志决定哪些操作是允许的(如是否可进行I/O操作)。current_gfp_context(gfp_mask)
.order原始内存申请的阶数,表示需要  个连续页框。回收器会考虑这个值。order(参数传入)
.priority回收的优先级,数值越小优先级越高,回收越激进。初始值设为默认优先级。DEF_PRIORITY(通常为12)
.may_writepage是否允许回写脏页到磁盘。回写脏页会引发I/O,可能阻塞回收过程。!laptop_mode(笔记本模式下可能禁止)
.may_unmap是否允许取消页表映射。这是回收页面的前提步骤之一。1(允许)
.may_swap是否允许交换匿名页到Swap分区。Swap操作也会产生I/O。1(允许,除非第7250行被覆盖)

⚠️ 关键检查:throttle_direct_reclaim

有一个非常重要的检查:throttle_direct_reclaim。这个函数的作用是判断当前进程是否应该被“节流”(throttle),即暂停直接回收,并加入一个等待队列。

  • 为什么需要节流? 当系统中多个进程同时触发直接回收时,会造成大量的I/O竞争和锁竞争,导致每个回收效率都极低,系统长时间卡顿。这时,内核会选择让部分进程稍等片刻,由后台回收线程 kswapd 先工作,释放一些内存后再唤醒它们。
  • 如何判断? 它会检查内存节点(node)的平衡状态。如果节点内存严重不足,当前进程就会被加入 pgdat->pfmemalloc_wait 等待队列,函数返回 1,从而让 try_to_free_pages 也直接返回 1。这相当于告诉内存分配器:“先别急,等一会儿再试”。
  • 如果被节流,函数返回 1,这通常会让内存分配器稍后重试分配。

🚀 执行回收与返回结果

如果通过了节流检查,函数就进入核心步骤:

  1. 调用 do_try_to_free_pages:这个函数是回收逻辑的真正执行者。它会根据 sc 中的参数,按照优先级从低到高(priorityDEF_PRIORITY 开始递减)多次尝试扫描和回收内存。其内部会调用 shrink_zones, shrink_node 等函数,遍历LRU链表,尝试回收文件页和匿名页。

  2. 返回回收页数nr_reclaimed 记录了本次实际回收的页面数量。这个返回值至关重要:

    • 如果 nr_reclaimed > 0,说明回收取得了一定成效,内存分配器可能会用这些新释放的内存再次尝试分配。
    • 如果 nr_reclaimed == 0,说明直接回收失败了。这通常是内存极度紧张的标志,可能会触发更激进的机制,例如OOM Killer,开始选择并杀死进程来释放内存。

💎 总结与问题定位

简单来说,try_to_free_pages 是系统在内存分配路径上设置的一道“紧急关卡”。当它被频繁调用时,说明系统内存压力已经很大。

如何判断回收是否成功? 主要就是看它的返回值 nr_reclaimed。如果返回值经常为0,就需要警惕OOM的风险。

如何观察这个函数的行为? 你可以通过内核跟踪点(tracepoint)来监控,例如 trace_mm_vmscan_direct_reclaim_begin 和 trace_mm_vmscan_direct_reclaim_end(,它们会记录每次直接回收的开始、结束以及回收的页数,是分析内存压力的利器。

三.allow_direct_reclaim 函数

static bool allow_direct_reclaim(pg_data_t *pgdat)
7088  {
7089  	struct zone *zone;
7090  	unsigned long pfmemalloc_reserve = 0;
7091  	unsigned long free_pages = 0;
7092  	int i;
7093  	bool wmark_ok;
7094  
7095  	if (pgdat->kswapd_failures >= MAX_RECLAIM_RETRIES)
7096  		return true;
7097  
7098  	for (i = 0; i <= ZONE_NORMAL; i++) {
7099  		zone = &pgdat->node_zones[i];
7100  		if (!managed_zone(zone))
7101  			continue;
7102  
7103  		if (!zone_reclaimable_pages(zone))
7104  			continue;
7105  
7106  		pfmemalloc_reserve += min_wmark_pages(zone);
7107  		free_pages += zone_page_state_snapshot(zone, NR_FREE_PAGES);
7108  	}
7109  
7110  	/* If there are no reserves (unexpected config) then do not throttle */
7111  	if (!pfmemalloc_reserve)
7112  		return true;
7113  
7114  	wmark_ok = free_pages > pfmemalloc_reserve / 2;
7115  
7116  	/* kswapd must be awake if processes are being throttled */
7117  	if (!wmark_ok && waitqueue_active(&pgdat->kswapd_wait)) {
7118  		if (READ_ONCE(pgdat->kswapd_highest_zoneidx) > ZONE_NORMAL)
7119  			WRITE_ONCE(pgdat->kswapd_highest_zoneidx, ZONE_NORMAL);
7120  
7121  		wake_up_interruptible(&pgdat->kswapd_wait);
7122  	}
7123  
7124  	return wmark_ok;
7125  }
allow_direct_reclaim 这个函数是 Linux 内核内存管理中的一个关键决策点,它的核心作用是判断当前内存节点的状况是否允许进程直接进行内存回收,还是应该将回收工作交给专门的 kswapd 内核线程来处理,以避免多个进程同时回收导致系统性能恶化。

这个函数通过几个关键检查来决定是否“放行”直接回收。

⚙️ 关键判断逻辑详解

  1. 检查 kswapd 健康状况 if (pgdat->kswapd_failures >= MAX_RECLAIM_RETRIES):这行代码首先检查 kswapd 线程是否已经多次尝试回收内存但均告失败

    。如果失败次数过多,意味着后台异步回收机制可能已经失效,系统内存压力巨大。此时函数会返回 true,作为一种“兜底”策略,允许进程自己动手进行直接回收,奋力一搏。
  2. 计算关键内存指标 接着,函数遍历该内存节点(pg_data_t)中 ZONE_NORMAL 及更低区域(通常是 ZONE_DMAZONE_DMA32)的内存区域(zone

    。它计算两个核心数值:
    • pfmemalloc_reserve:这是为应对紧急内存分配(如网络处理)而预留的页面总数,简单加总了各个区域的最低水位线(min_wmark_pages)。
    • free_pages:这是这些区域中当前空闲页面的总数。
  3. 评估内存水位线状态 wmark_ok = free_pages > pfmemalloc_reserve / 2:这是最关键的判断。它检查当前的空闲内存是否大于预留内存的一半。如果为 true,说明内存状况尚可,允许直接回收;如果为 false,则意味着为系统关键操作预留的内存可能不足,情况比较危险

⚠️ 水位线不足时的应对措施

wmark_okfalse 时,函数并不会简单地返回结果,它还会采取一个重要行动:

  • 唤醒 kswapd:函数会检查 kswapd 的等待队列是否活跃,如果是,则确保 kswapd 的工作范围集中在 ZONE_NORMAL 及以下的关键区域,并立即唤醒它。这样做的目的是让专业的内核回收线程来统一、高效地处理内存危机,避免多个进程各自为战进行直接回收,从而减少 I/O 竞争和系统锁争用,防止系统性能雪崩。

💎 核心作用总结

简单来说,allow_direct_reclaim 就像是内存回收的“交通警察”。它的决策逻辑是:

  • 返回 true(允许直接回收):要么是 kswapd 已经无能为力,需要进程自救;要么是当前内存水位线还算安全,进程可以自行回收。
  • 返回 false(应节流直接回收):当系统关键内存储备不足时,它会拦下想要自己回收内存的进程,同时唤醒 kswapd 这个“专业救援队” 来统一处理问题,以此保障系统的整体稳定性。

这个机制是 Linux 内核在面对内存压力时,在“让进程自己快速解决问题”和“保持系统整体流畅稳定”之间做出的重要权衡。

http://www.dtcms.com/a/491232.html

相关文章:

  • Windows环境变量
  • MySQL分区表(PARTITION):水平分表示例 (基于用户ID哈希分表)不依赖第三方中间件
  • 数值计算-例题
  • 合肥市建设通网站十堰网站建设哪家好
  • 拼车平台网站开发提高网站打开速度
  • C++ 继承(1)
  • uniapp——配置鸿蒙环境,进行真机调试
  • 鸿蒙仓颉:如何自定义带Tag的日志打印信息
  • 带你了解STM32:RTC实时时钟(第一部分)
  • 前端 JavaScript 面试题大全(含答案及解析)
  • 网站页面设计报告wordpress 门户插件
  • 国外营销型网站建设容城网站建设
  • 将.idea取消git托管
  • YOLOv1和YOLOv2目标检测算法总结
  • 系统移植篇之uboot-4:UART
  • net和cn哪个做网站好wordpress企业主题 视频
  • 石家庄网站排名优化360网站建设服务
  • 苍穹外卖-购物车 前端修改(小程序主页与购物车模块显示不一致)
  • 聊聊 Unity(小白专享、C# 小程序 之 小算盘、计算器)
  • 江苏金安建设公司网站在线设计平台官网
  • 专业网站建设公司哪里济南兴田德润什么活动电子商务网站的整个建设流程
  • 【2025年湖南省赛misc复盘】
  • 北京建设教育协会官方网站做产品推广得网站
  • 牙刮匙与牙周治疗疗效的相关性分析
  • 三星XR头显发布会正式敲定,苹果/Meta/微美全息竞逐AR底层核心卡位“军备竞赛”
  • 【C++进阶系列】:类型转换和IO流
  • 卡板技术支持 东莞网站建设动态设计是什么意思
  • 宠物服务平台(程序+文档)
  • Maven再次认识
  • 【STM32项目开源】基于STM32的智能厨房环境监控系统