【问题标题】:Repeated Minor Pagefaults at Same Address After Calling mlockall()调用 mlockall() 后在同一地址重复出现次要页面错误
【发布时间】:2014-06-02 19:12:12
【问题描述】:

问题

在尝试减少/消除应用程序中出现轻微页面错误的过程中,我发现了一个令人困惑的现象;也就是说,即使我认为我已经采取了足够的措施来防止页面错误,我也会反复触发对同一地址的写入的次要页面错误。

背景

根据here 的建议,我致电mlockall 将所有当前和未来的页面锁定到内存中。

在我最初的用例(涉及一个相当大的数组)中,我还按照here 的建议通过写入每个元素(或至少写入每个页面)来预先故障数据;尽管我意识到那里的建议是针对运行带有 RT 补丁的内核的用户,但强制写入以阻止 COW/需求分页的一般想法应该仍然适用。

我曾认为mlockall 可以用来防止轻微的页面错误。虽然手册页似乎只保证不会出现重大错误,但各种其他资源(例如上面)表明它也可以用来防止轻微的页面错误。

内核文档似乎也表明了这一点。例如,unevictable-lru.txtpagemap.txt 声明mlock() 的页面是不可回收的,因此不适合回收。

尽管如此,我还是继续触发了几个小页面错误。

示例

我创建了一个非常精简的示例来说明问题:

#include <sys/mman.h> // mlockall
#include <stdlib.h> // abort

int main(int , char **) {
  int x;

  if (mlockall(MCL_CURRENT | MCL_FUTURE)) abort();

  while (true) {
    asm volatile("" ::: "memory"); // So GCC won't optimize out the write
    x = 0x42;
  }
  return 0;
}

在这里,我反复写信到同一个地址。很容易看到(例如,通过cat /proc/[pid]/status | awk '{print $10}')在初始化完成后很长时间我仍然有轻微的页面错误。

运行 systemtap-doc 中包含的 pfaults.stp 脚本的修改版本*,我记录了每个页面错误的时间、触发错误的地址、触发错误的指令地址、是否是主要/次要、和读/写。在启动和mlockall 的初始故障之后,所有故障都相同:尝试写入x 触发了次要写入故障。

连续页面错误之间的间隔显示出惊人的模式。对于一次特定的运行,时间间隔以秒为单位: 2, 4, 4, 4.8, 8.16, 13.87, 23.588, 40.104, 60, 60, 60, 60, 60, 60, 60, 60, 60, ... 这似乎是(大约)指数回退,绝对上限为 1 分钟。

在一个独立的 CPU 上运行它没有影响;以更高的优先级运行也不行。但是,以实时优先级运行可以消除页面错误。

问题

  1. 这种行为是预期的吗?
    1a。什么解释了时间安排?
  2. 是否可以防止这种情况发生?

版本

我正在运行 Ubuntu 14.04,内核为3.13.0-24-generic,Systemtap 版本为2.3/0.156, Debian version 2.3-1ubuntu1 (trusty)。使用gcc-4.8 编译的代码没有额外的标志,尽管优化级别似乎无关紧要(前提是asm volatile 指令保留在原位;否则写入将完全优化)

如果证明相关,我很乐意提供更多详细信息(例如确切的 stap 脚本、原始输出等)。


*实际上,vm.pagefault 探针因我的内核和 systemtap 组合而损坏,因为它引用了一个不再存在于内核的 handle_mm_fault 函数中的变量,但修复很简单)

【问题讨论】:

    标签: linux virtual-memory systemtap


    【解决方案1】:

    @fche 提到 Transparent Huge Pages 让我走上了正轨。

    对我在问题中链接到的内核文档进行的不那么粗心的阅读表明,mlock 确实不会阻止内核将页面迁移到新的页面框架;事实上,有一整节专门讨论migrating mlocked pages。因此,仅调用 mlock() 并不能保证您不会遇到任何轻微的页面错误

    有点晚了,我看到this answer 引用了相同的段落并部分回答了我的问题。

    内核可能移动页面的原因之一是memory compaction,内核释放了一个大的连续页面块,因此可以分配一个“巨大的页面”。透明大页面可以轻松禁用;参见例如this answer.

    我的特定测试用例是 3.13 kernel 中引入的一些 NUMA 平衡更改的结果。

    引用LWN article linked therein

    调度器会定期扫描每个进程的地址 空间,撤销对当前页面的所有访问权限 驻留在 RAM 中。 下次受影响的进程尝试访问时 该内存,将导致页面错误。调度程序将捕获该内存 故障并恢复对相关页面的访问...

    可以通过将进程的 NUMA 策略设置为显式使用某个节点来禁用调度程序的这种行为。这可以在命令行使用numactl(例如numactl --membind=0)或调用libnuma库来完成。

    编辑:sysctl documentation 明确声明关于 NUMA 平衡:

    如果目标工作负载已绑定到 NUMA 节点,则应禁用此功能。

    这可以通过sysctl -w kernel.numa_balancing=0来完成

    页面迁移可能还有其他原因,但这对我的目的来说已经足够了。

    【讨论】:

      【解决方案2】:

      这里只是推测,但也许您看到的是一些正常的内核页面利用率跟踪(甚至可能是 KSM 或 THP 或 cgroup),其中它试图确定有多少页面处于活动状态。探测 mark_page_accessed 函数,例如

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-03-13
        • 1970-01-01
        • 1970-01-01
        • 2013-07-07
        • 1970-01-01
        • 2022-12-14
        • 2012-01-22
        相关资源
        最近更新 更多