如何从内核空间中缩小 Linux 页面缓存?

Posted

技术标签:

【中文标题】如何从内核空间中缩小 Linux 页面缓存?【英文标题】:How can I shrink the Linux page cache from within kernel space? 【发布时间】:2015-11-02 18:08:11 【问题描述】:

我正在开发一个涉及一些自定义硬件和我为硬件编写的自定义 Linux 设备驱动程序的系统。系统有时需要非常快速地移动大量数据,因此我的驱动程序会动态(即在需要时)分配大的 (1 GB) DMA 缓冲区,这些缓冲区被使用,然后在不再需要时释放。为了分配这么大的缓冲区,我实际上使用dma_alloc_coherent 分配了一堆较小的缓冲区(256 X 4MB),然后使用remap_pfn_range 将它们连续映射到用户空间。这在大多数情况下都非常有效。

在测试过程中,系统长时间运行测试用例后,我有时会看到 DMA 分配失败,我的驱动程序中的 dma_alloc_coherent 调用之一失败,导致我的应用层软件崩溃。我终于能够追查到这个问题,我发现当我看到 DMA 分配失败时,Linux 内核页面缓存非常满。

例如,在我上次捕获页面缓存失败时,系统上的 32 GB RAM 中的 27 GB 被我捕获。我怀疑页面缓存“充满”导致dma_alloc_coherent 调用失败。为了测试这个理论,我手动清空了页面缓存:

# echo 1 > /proc/sys/vm/drop_caches

这将缓存大小从 27 GB 降低到 94 MB,并且我能够毫无问题地分配 20+ 1 GB DMA 缓冲区。

显然,页面缓存是一件有益的事情,所以我不希望每次在分配 DMA 缓冲区时空间不足时都必须完全清空它。我的问题是:如何动态缩小内核空间中的页面缓存,以便如果对 dma_alloc_coherent 的调用失败,我可以恢复足够的空间,以便我可以重试调用并使其成功?

我的系统基于 x86_64,运行 3.16.x Linux 内核。

我发现了一些模糊的参考,暗示我正在尝试的可能是可能的,例如“这些对象是自动 当系统上的其他地方需要内存时由内核回收。”(来自:https://www.kernel.org/doc/Documentation/sysctl/vm.txt)。但我还没有找到任何表明内存是如何回收的细节。

对此的任何帮助将不胜感激!

【问题讨论】:

【参考方案1】:

TL;DR:扫描活动的超级块并删除对非脏块的引用,直到您回收了所需的系统内存。 (或者你最终用完了对活动超级块的引用。)


如何编写内核代码来动态收缩fs page-cache, 恢复足够的空间以便后续调用dma_alloc_coherent() 成功?

要回答这个问题,让我们看看“drop_caches 操作”是如何将系统上的 fs 页面缓存从 27GB 减少到 94MB 的。

    echo 1 > /proc/sys/vm/drop_caches 调用drop_caches_sysctl_handler()

    依次调用iterate_supers()和 将指针传递给函数drop_pagecache_sb()

接下来发生的事情是iterate_supers() 扫描活动超级块,每次找到一个时,它都会调用drop_pagecache_sb(),并将对活动超级块的引用传递给它。

这个迭代过程一直持续到对所有活动超级块的引用从 fs 页面缓存中释放。这是一种非破坏性操作,只会释放完全未使用的块。脏对象将继续使用,直到写入磁盘并且不可释放。如果您首先运行sync 将它们刷新到磁盘,“drop_caches 操作”往往会释放更多内存。

由于您有兴趣运行此过程以回收有限/已知数量的内存,即即将使用dma_alloc_coherent() 请求的内存,您只需在每个结束时进行额外检查即可实现上述功能一旦空闲系统内存量超过所需级别,立即迭代并中止超级块扫描。


要进一步优化此过程,请记住几点:

是否对某些块设备有偏好? 您可能希望首先迭代您不关心的块设备的活动超级块。如果没有回收足够的内存,则扫描您希望保留在 fs 页面缓存中的块设备,除非绝对有必要回收所需的内存。 get_active_super() 在这里可能会有所帮助。

iterate_supers_type() 似乎很有趣 它允许迭代特定file_system_type的超级块

请注意,这是一个推测性的解决方案,纯粹基于对 Linux 内核中现有代码的分析,您观察到这些代码已经解决了您的问题。一旦实现了上述方法,它将只允许您控制相同的内容,即尝试仅在您的即时需求所需的范围内回收 fs 页面缓存内存。

【讨论】:

感谢您的回复,您所描述的实际上正是我所做的。然而,它确实感觉像是一个巨大的黑客,但它工作正常。我仍然不明白为什么dma_alloc_coherent 不会自动从页面缓存中释放内存以确保分配成功。我很想更好地了解 Linux VM 系统的本质。 为确保已知数量的可用内存,内存可调参数会有所帮助。然而,从内核驱动程序中动态配置它们可能是一个非常特殊的场景,并没有受到太多关注。也许你可以将你的实现提交给上游内核来试水...... :-)【参考方案2】:

从技术上讲,当某些分配失败时,内核将尝试释放内存。取决于内存故障(软故障/硬故障)。硬故障导致内核进入直接回收路径。直接回收是一种代价高昂的操作,可能需要不确定的时间才能完成,即使在分配失败之后也是如此。

这里有两种选择:

1) 使用诸如dirty_ratio、dirty_background_ratio 等VM 设置来保持空闲内存。见:https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/6/html/Performance_Tuning_Guide/s-memory-tunables.html

2) 编写一个内核守护进程,它调用处理 drop_cache 的内核函数(因为 drop_cache 可能会休眠)。

【讨论】:

我在dma_alloc_coherent 代码路径中找不到触发内存回收的任何位置。正如我之前所说,我宁愿尽可能多地使用页面缓存,如果我不是绝对必须的话,不要清空它(调用 drop_cache)。

以上是关于如何从内核空间中缩小 Linux 页面缓存?的主要内容,如果未能解决你的问题,请参考以下文章

如何从 Linux 内核访问用户空间内存?

如何在 Linux 中刷新地址空间区域的 CPU 缓存?

如何清理 Linux 内核使用的缓存

进程通过内核缓存区请求设备I/O的一些事情

Linux系统清除缓存

linux学习之缓存机制