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

[Linux]学习笔记系列 -- mm/shrinker.c 内核缓存收缩器(Kernel Cache Shrinker) 响应内存压力的回调机制

文章目录

  • mm/shrinker.c 内核缓存收缩器(Kernel Cache Shrinker) 响应内存压力的回调机制
      • 历史与背景
        • 这项技术是为了解决什么特定问题而诞生的?
        • 它的发展经历了哪些重要的里程碑或版本迭代?
        • 目前该技术的社区活跃度和主流应用情况如何?
      • 核心原理与设计
        • 它的核心工作原理是什么?
        • 它的主要优势体现在哪些方面?
        • 它存在哪些已知的劣势、局限性或在特定场景下的不适用性?
      • 使用场景
        • 在哪些具体的业务或技术场景下,它是首选解决方案?请举例说明。
        • 是否有不推荐使用该技术的场景?为什么?
      • 对比分析
        • 请将其 与 其他相似技术 进行详细对比。
  • mm/shrinker.c
    • shrinker_alloc
    • shrinker_register 将 shrinker 结构体注册到内核的 shrinker 子系统中

在这里插入图片描述

https://github.com/wdfk-prog/linux-study

mm/shrinker.c 内核缓存收缩器(Kernel Cache Shrinker) 响应内存压力的回调机制

历史与背景

这项技术是为了解决什么特定问题而诞生的?

这项技术以及其实现的“收缩器”(Shrinker)框架,是为了解决Linux内核中一个根本性的资源管理问题:如何在一个统一的框架下,回收由各种不同内核子系统所占用的内存缓存

  • 内核缓存的多样性:Linux内核不仅仅有用于缓存文件数据的页面缓存(Page Cache)。还有许多其他重要的缓存,例如用于加速路径查找的dcache、用于缓存文件元数据的inode缓存,以及Slab/Slub分配器自身为内核对象维护的缓存等。
  • 缺乏统一回收接口:当系统物理内存(RAM)不足时,内存管理(MM)子系统需要释放一些内存。对于页面缓存,它有自己复杂的LRU(最近最少使用)算法来回收。但对于dcache、inode cache等“非页面缓存”的内存,MM子系统本身并不知道它们的内部结构,也不知道哪些对象是“可回收的”(例如,一个未被使用的dentry)。
  • 解耦的需求:在没有Shrinker框架的情况下,内存管理代码将不得不硬编码对dcache、inode cache等特定子系统的了解,以便调用它们的清理函数。这会造成紧密的耦合,使得代码难以维护,并且无法支持新的内核子系统添加自己的缓存回收逻辑。

Shrinker框架的诞生就是为了解决这个问题。它提供了一个通用的、基于回调的“插件”机制。任何内核子系统只要实现了自己的缓存,就可以向MM子系统注册一个shrinker,从而参与到全局的内存回收过程中。

它的发展经历了哪些重要的里程碑或版本迭代?

Shrinker机制是随着Linux内存管理系统的成熟而逐步完善的。

  • 早期概念:内核很早就有了回收dentry和inode缓存的机制,这可以看作是Shrinker思想的雏形。
  • 标准化接口mm/shrinker.c的出现,将这个过程标准化为一个正式的框架。register_shrinker()unregister_shrinker() API的确立,使得任何子系统都可以动态地加入或退出内存回收体系。
  • 引入两阶段回调 (count/scan):早期的实现可能只有一个简单的“shrink”回调。现代的Shrinker框架演进为一个更智能的两阶段过程:
    1. count_objects:一个快速的、通常无锁的回调,用于估算可回收对象的数量。
    2. scan_objects:一个较重的回调,负责实际扫描和释放对象。
      这种分离使得MM子系统可以先“探查”所有缓存的“胖瘦”,然后根据内存压力和每个缓存的可回收对象数量,按比例地、公平地分配回收任务。
  • NUMA和Cgroup感知:为了适应现代多节点(NUMA)服务器和容器化(cgroups)环境,Shrinker框架被扩展为NUMA和内存cgroup感知的。这意味着内存回收可以更具针对性,只在内存压力高的NUMA节点或cgroup内部触发其关联的shrinker,提高了回收的效率和隔离性。
目前该技术的社区活跃度和主流应用情况如何?

Shrinker是Linux内存管理中一个基础、稳定且至关重要的组成部分。

  • 社区活跃度:其核心框架非常稳定。相关的社区活动主要集中在:1) 新的内核子系统(如新的网络协议栈或驱动)注册自己的shrinker;2) 对现有shrinker(特别是VFS的)进行性能调优,使其在scan_objects阶段更高效。
  • 主流应用:它是所有主要内核缓存进行自我清理的标准方式。
    • VFS:最重要的使用者。fs/dcache.cfs/inode.c都注册了shrinker,用于在内存压力下修剪未使用的dentry和inode对象。
    • Slab/Slub分配器:Slab分配器自身也注册了shrinker,用于将完全空闲的slab页面释放回页分配器。
    • XArray/Radix Tree:这些内核中广泛使用的数据结构,也提供了shrinker来回收其内部节点所占用的内存。

核心原理与设计

它的核心工作原理是什么?

mm/shrinker.c的核心是一个“发布-订阅”模型,MM子系统是发布者(发布内存压力事件),各种内核缓存是订阅者(订阅并响应事件)。

  1. 注册(Subscription):一个希望参与内存回收的内核子系统(如VFS)会创建一个struct shrinker对象。这个结构体中最关键的是两个函数指针:count_objectsscan_objects。然后,该子系统调用register_shrinker()将这个对象注册到一个全局的shrinker链表中。
  2. 触发(Publication):当系统内存不足时,无论是后台的kswapd内核线程被唤醒,还是某个进程因分配内存失败而触发“直接回收”(direct reclaim),它们最终都会调用到shrink_slab()函数。
  3. 协同回收过程shrink_slab()会遍历所有已注册的shrinker对象,并对每个对象执行以下两步操作:
    • 第一步:计数(Count):调用该shrinker的count_objects回调函数。这个函数需要快速地(通常是原子地读取一个计数器)返回该缓存中当前可回收对象的估算数量。例如,dcache的count函数会返回LRU链表中未使用dentry的数量。MM子系统会汇总所有shrinker的计数值,从而了解当前整个内核缓存的可回收潜力。
    • 第二步:扫描(Scan):根据当前的内存压力和第一步中得到的计数值,MM子系统会计算出一个需要该shrinker释放的对象数量(nr_to_scan)。然后,它调用该shrinker的scan_objects回调函数,并传入这个数量。scan_objects函数则负责执行实际的清理工作,例如,遍历LRU链表,释放最多nr_to_scan个未使用的对象。最后,它返回实际释放的对象数量。
它的主要优势体现在哪些方面?
  • 模块化与解耦:MM子系统无需知道任何关于dcache或inode cache的内部实现。它只通过标准的回调接口与它们通信,这使得代码清晰、易于维护。
  • 可扩展性:任何新的内核代码只要想实现一个可回收的缓存,都可以简单地通过实现两个回调函数并注册一个shrinker来集成到全局内存管理中。
  • 公平与高效:通过count/scan两阶段机制,MM子系统可以根据每个缓存的“可回收性”来按比例施加压力,避免了对某个缓存进行过度的、低效的扫描,从而更公平、更高效地回收内存。
它存在哪些已知的劣势、局限性或在特定场景下的不适用性?
  • 实现依赖:Shrinker框架的效率高度依赖于每个shrinker实现者的代码质量。一个 poorly-written 的scan_objects函数(例如,持有锁的时间过长,或者扫描效率低下)会拖慢整个系统的内存回收过程。
  • 估算的局限性count_objects返回的是一个估算值,可能与实际可回收的数量有偏差。这可能导致MM子系统的回收决策不是最优的。
  • 不适用于页面缓存:这个框架是为基于对象的内核缓存设计的。它不适用于页面缓存(Page Cache)的回收。页面缓存有其自己的一套更复杂的、基于LRU和页面状态的回收机制。

使用场景

在哪些具体的业务或技术场景下,它是首选解决方案?请举例说明。

在内核中,任何子系统创建了一个动态的、非必需的、基于对象的内存缓存时,提供一个shrinker是标准且首选的做法。

  • VFS dentry缓存回收:这是最经典的例子。当系统运行时,会创建大量的dentry对象来加速路径查找。但其中很多dentry在一段时间后就不再被使用。当内存紧张时,MM子系统会调用VFS注册的dentry shrinker。其scan_objects函数会扫描dentry的LRU链表,将那些引用计数为0的dentry从哈希表和父子关系中脱离,并释放其占用的slab对象。
  • Slab空闲页面回收:内核通过kmem_cache_create创建了很多slab缓存。当某个缓存(例如dentry_cache)中的大量对象被释放后,可能会出现整个slab页面都变为空闲的情况。Slab分配器注册的shrinker被调用时,就会寻找这些完全空闲的slab页面,并将它们返还给伙伴系统(Buddy System),供其他用途使用。
是否有不推荐使用该技术的场景?为什么?
  • 页面缓存(Page Cache):如前所述,页面缓存的回收逻辑(Active/Inactive List等)比shrinker模型复杂得多,有其专门的处理路径。
  • 必需的核心数据:Shrinker用于回收“缓存”——即那些为了性能而保留,但丢弃后系统仍能正常工作(尽管可能变慢)的数据。它绝不能用于释放那些正在被使用的、不可或缺的核心数据结构。

对比分析

请将其 与 其他相似技术 进行详细对比。

对比一:Shrinker机制 vs. 页面缓存回收(Page Reclaim)

特性Shrinker机制页面缓存回收
回收对象通用内核对象,通常由Slab/Slub分配器管理(如dentries, inodes)。内存页(struct page,主要是文件数据页(File-backed pages)和匿名页(Anonymous pages)。
核心机制基于回调的“请求-响应”模型。MM请求,子系统响应并释放。基于LRU(最近最-少使用)链表的管理。MM直接操作Active/Inactive链表来决定回收哪些页面。
决策者子系统本身scan_objects函数内部决定具体释放哪个对象。MM子系统kswapd直接决定哪个page将被回收、换出或丢弃。
工作范围补充页面回收,清理内核数据结构占用的非页面缓存内存。内存回收的主力,负责清理系统中最大头的内存消耗者——页面缓存。
关系协同工作。一次完整的内存回收过程,通常会同时进行页面回收和调用shrinkers。

对比二:Shrinker机制 vs. drop_caches

特性Shrinker机制echo N > /proc/sys/vm/drop_caches
触发方式自动、按需。由内核在检测到内存压力时自动、渐进地触发。手动、强制。由管理员或脚本显式触发,是一次性的、全局性的、暴力的操作。
回收方式智能、渐进。根据压力大小,回收适量的、最不常用的对象(LRU)。暴力、全部。尽可能多地丢弃所有可丢弃的缓存(N=1丢弃页面缓存,N=2丢棄dentry/inode,N=3丢弃全部)。
对性能的影响旨在平滑系统性能,避免因内存不足而突然停顿。会导致剧烈的性能下降。因为所有热缓存都被清空,后续的文件访问会触发大量的磁盘I/O风暴。
使用场景内核正常的、持续的后台内存管理。仅限于调试和测试。用于在可控环境下测试应用的冷启动性能,或临时释放内存以进行某些特殊操作。绝不应在生产环境中作为常规操作使用

mm/shrinker.c

shrinker_alloc

struct shrinker *shrinker_alloc(unsigned int flags, const char *fmt, ...)
{struct shrinker *shrinker;unsigned int size;va_list ap;int err;shrinker = kzalloc(sizeof(struct shrinker), GFP_KERNEL);if (!shrinker)return NULL;va_start(ap, fmt);/* return 0; */err = shrinker_debugfs_name_alloc(shrinker, fmt, ap);va_end(ap);if (err)goto err_name;/* 已分配(SHRINKER_ALLOCATED) */shrinker->flags = flags | SHRINKER_ALLOCATED;/* (DEFAULT_SEEKS),表示默认的搜索次数 */shrinker->seeks = DEFAULT_SEEKS;/* 尝试分配与内存控制组相关的资源 */if (flags & SHRINKER_MEMCG_AWARE) {/* return -ENOSYS; */err = shrinker_memcg_alloc(shrinker);if (err == -ENOSYS) {/*Memcg不受支持,回退到非Memcg感知的收缩器. */shrinker->flags &= ~SHRINKER_MEMCG_AWARE;goto non_memcg;}if (err)goto err_flags;return shrinker;}
/* 非 memcg 模式 */
non_memcg:/** nr_deferred 在每个 memcg 层级上可用于支持 memcg 的 shrinkers,因此仅在以下情况下分配 nr_deferred:*  - 非 memcg 支持的 shrinkers*  - !CONFIG_MEMCG*  - 通过内核命令行禁用 memcg*//* 分配延迟计数器(nr_deferred) */size = sizeof(*shrinker->nr_deferred);if (flags & SHRINKER_NUMA_AWARE)size *= nr_node_ids;shrinker->nr_deferred = kzalloc(size, GFP_KERNEL);if (!shrinker->nr_deferred)goto err_flags;return shrinker;err_flags:shrinker_debugfs_name_free(shrinker);
err_name:kfree(shrinker);return NULL;
}
EXPORT_SYMBOL_GPL(shrinker_alloc);

shrinker_register 将 shrinker 结构体注册到内核的 shrinker 子系统中

void shrinker_register(struct shrinker *shrinker)
{/* 检查 shrinker 是否通过 shrinker_alloc 动态分配 */if (unlikely(!(shrinker->flags & SHRINKER_ALLOCATED))) {pr_warn("Must use shrinker_alloc() to dynamically allocate the shrinker");return;}mutex_lock(&shrinker_mutex);list_add_tail_rcu(&shrinker->list, &shrinker_list);/* 表示 shrinker 已成功注册 */shrinker->flags |= SHRINKER_REGISTERED;shrinker_debugfs_add(shrinker);mutex_unlock(&shrinker_mutex);init_completion(&shrinker->done);/** 现在收缩器已完全设置好,首次引用它以表明现在可以通过* shrinker_try_get() 使用它进行查找操作。*/refcount_set(&shrinker->refcount, 1);
}
EXPORT_SYMBOL_GPL(shrinker_register);
http://www.xdnf.cn/news/19169.html

相关文章:

  • 深入解析PCIe 6.0拓扑架构:从根复合体到端点的完整连接体系
  • 宜春城区光纤铺设及接口实地调研
  • C5仅支持20MHZ带宽,如果路由器5Gwifi处于40MHZ带宽信道时,会出现配网失败
  • Pytest 插件方法:pytest_runtest_makereport
  • Stream API 讲解
  • Day17_【机器学习—在线数据集 鸢尾花案例】
  • 宜春城区SDH网图分析
  • 漫谈《数字图像处理》之浅析图割分割
  • 从9.4%到13.5%:ICDM2025录取率触底反弹,竞争压力稍缓
  • 新工具-mybatis-flex学习及应用
  • 大模型应用开发笔记(了解篇)
  • 使用 Bright Data Web Scraper API + Python 高效抓取 Glassdoor 数据:从配置到结构化输出全流程实战
  • Vue 项目首屏加载速度优化
  • 阿里云百炼智能体连接云数据库实践(DMS MCP)
  • AI-调查研究-64-机器人 从零构建机械臂:电机、减速器、传感器与控制系统全剖析
  • 深入解析Qt节点编辑器框架:交互逻辑与样式系统(二)
  • 如何使用 Vector 连接 Easysearch
  • cloudflare-ddns
  • nacos登录认证
  • 2026届大数据毕业设计选题推荐-基于Python的出行路线规划与推荐系统 爬虫数据可视化分析
  • 使用TensorFlow Lite Mirco 跑mirco_speech语音识别yes/no
  • Blender中旋转与翻转纹理的实用方法教学
  • Speculation Rules API
  • 华为HCIP数通学习与认证解析!
  • 从零开始的云计算生活——第五十四天,悬梁刺股,kubernetes模块之组件与网络
  • rapid_table v3.0.0发布了
  • MySQL数据库精研之旅第十四期:索引的 “潜规则”(上)
  • 新手向:Python实现数据可视化图表生成
  • 《R for Data Science (2e)》免费中文翻译 (第6章) --- scripts and projects
  • MySQL-内置函数