内存资源控制器实现备忘 【ChatGPT】

发布时间 2023-12-09 22:16:32作者: 摩斯电码

Memory Resource Controller(Memcg) Implementation Memo

最后更新时间:2010/2

基础内核版本:基于2.6.33-rc7-mm(34版本的候选版本)。

由于虚拟内存变得复杂(其中一个原因是memcg...),memcg的行为也变得复杂。这是一份关于memcg内部行为的文档。请注意,实现细节可能会发生变化。

  • API主题应在内存资源控制器中

0. 如何记录使用情况?

使用了2个对象。

  • page_cgroup ....每页一个对象。
    在启动时分配或内存热插拔。在内存热移除时释放。

  • swap_cgroup ...每个swp_entry一个条目。
    在swapon()时分配。在swapoff()时释放。

    page_cgroup有USED位,并且不会发生对page_cgroup的双重计数。swap_cgroup仅在对已计费页进行交换时使用。

1. 计费

在mem_cgroup_try_charge()中,可以对一页/swp_entry进行计费(usage += PAGE_SIZE)。

2. 取消计费

通过下面的接口对一页/swp_entry进行取消计费(usage -= PAGE_SIZE)

  • mem_cgroup_uncharge()
    当页的引用计数下降到0时调用。

  • mem_cgroup_uncharge_swap()
    当swp_entry的引用计数下降到0时调用。对交换的计费消失。

3. 计费-提交-取消

Memcg页面分两步计费:

  • mem_cgroup_try_charge()

  • mem_cgroup_commit_charge() 或 mem_cgroup_cancel_charge()

在try_charge()时,没有标志表示“此页已计费”。此时,usage += PAGE_SIZE。

在commit()时,页面与memcg相关联。

在cancel()时,简单地usage -= PAGE_SIZE。

在下面的解释中,我们假设CONFIG_SWAP=y。

4. 匿名页

匿名页是在下面的位置新分配的:

  • 通过缺页映射给在MAP_ANONYMOUS映射
  • 写时复制

4.1 交换入。在交换入时,页面从交换缓存中取出。有两种情况。

a. 如果SwapCache是新分配并读取,则没有计费。

b. 如果SwapCache已被进程映射,则已经计费。

4.2 交换出。在交换出时,典型的状态转换如下。

a. 添加到交换缓存(标记为SwapCache)swp_entry的引用计数 += 1。

b. 完全取消映射。swp_entry的引用计数 += ptes的数量。

c. 写回到交换。

d. 从交换缓存中删除(从SwapCache中移除)swp_entry的引用计数 -= 1。

最后,在任务退出时,(e)zap_pte()被调用,swp_entry的引用计数 -=1 -> 0。

5. 页面缓存

页面缓存在filemap_add_folio()中计费。

逻辑非常清晰。(有关迁移,请参见下文)

注意:
__remove_from_page_cache()由remove_from_page_cache()和__remove_mapping()调用。

6. Shmem(tmpfs)页面缓存

理解shmem的页面状态转换最好的方法是阅读mm/shmem.c。

但是,简要解释围绕shmem的memcg行为将有助于理解逻辑。

Shmem的页面(仅为叶页,而不是直接/间接块)可以位于

  • shmem的索引树。

  • SwapCache。

  • 既在索引树上又在SwapCache上。这发生在交换入和交换出时,

当...

  • 向shmem的索引树添加新页面时。

  • 读取swp页面。(从swap_cgroup移动计费到page_cgroup)

7. 页面迁移

mem_cgroup_migrate()

8. LRU

每个memcg都有其自己的LRU向量(非活动匿名、活动匿名、非活动文件、活动文件、不可驱逐)来自每个节点的页面,每个LRU在该memcg和节点的单个lru_lock下处理。

9. 典型测试。

对竞争情况的测试。

9.1 对memcg设置小限制。

当您进行竞争情况的测试时,将memcg的限制设置得非常小而不是以GB为好。在xKB或xxMB限制下进行的测试中发现了许多竞争情况。

(GB下的内存行为和MB下的内存行为显示出非常不同的情况。)

9.2 Shmem

从历史上看,memcg对shmem的处理很差,我们在这里看到了一些麻烦。这是因为shmem是页面缓存,但也可以是SwapCache。始终对shmem/tmpfs进行测试是一个好主意。

9.3 迁移

对于NUMA,迁移是另一个特殊情况。要进行简单测试,cpuset是有用的。以下是一个执行迁移的示例脚本:

    mount -t cgroup -o cpuset none /opt/cpuset

    mkdir /opt/cpuset/01
    echo 1 > /opt/cpuset/01/cpuset.cpus
    echo 0 > /opt/cpuset/01/cpuset.mems
    echo 1 > /opt/cpuset/01/cpuset.memory_migrate
    mkdir /opt/cpuset/02
    echo 1 > /opt/cpuset/02/cpuset.cpus
    echo 1 > /opt/cpuset/02/cpuset.mems
    echo 1 > /opt/cpuset/02/cpuset.memory_migrate

在上述设置中,当您将一个任务从01移动到02时,将会发生从节点0到节点1的页面迁移。以下是一个迁移所有任务到cpuset下的脚本:

    --
    move_task()
    {
    for pid in $1
    do
            /bin/echo $pid >$2/tasks 2>/dev/null
            echo -n $pid
            echo -n " "
    done
    echo END
    }

    G1_TASK=`cat ${G1}/tasks`
    G2_TASK=`cat ${G2}/tasks`
    move_task "${G1_TASK}" ${G2} &
    --

9.4 内存热插拔

内存热插拔测试是一个很好的测试。

要离线内存,请执行以下操作:

    # echo offline > /sys/devices/system/memory/memoryXXX/state
    (XXX是内存的位置)

这也是测试页面迁移的简单方法。

9.5 嵌套cgroups

使用类似以下的测试来测试嵌套cgroups:

    mkdir /opt/cgroup/01/child_a
    mkdir /opt/cgroup/01/child_b

    将限制添加到01。
    在01/child_b中添加限制
    在child_a和child_b下运行作业

在作业运行时随机创建/删除以下组:

    /opt/cgroup/01/child_a/child_aa
    /opt/cgroup/01/child_b/child_bb
    /opt/cgroup/01/child_c

在新组中运行新作业也是一个好主意。

9.6 与其他子系统一起挂载

与其他子系统一起挂载是一个很好的测试,因为与其他cgroup子系统存在竞争和锁依赖关系。

例如:

    # mount -t cgroup none /cgroup -o cpuset,memory,cpu,devices

然后在此下进行任务移动、mkdir、rmdir等操作。

9.7 swapoff

除了memcg的管理是memcg的一个复杂部分之外,swapoff的调用路径与通常的swap-in路径不同。值得明确测试。

例如,像下面这样的测试是不错的:

(Shell-A):

    # mount -t cgroup none /cgroup -o memory
    # mkdir /cgroup/test
    # echo 40M > /cgroup/test/memory.limit_in_bytes
    # echo 0 > /cgroup/test/tasks

在此下运行malloc(100M)程序。您将看到60M的交换。

(Shell-B):

    # 将/cgroup/test中的所有任务移动到/cgroup
    # /sbin/swapoff -a
    # rmdir /cgroup/test
    # kill malloc任务。

当然,也应该测试tmpfs与swapoff的情况。

9.8 OOM-Killer

由于memcg的限制导致的内存不足将杀死memcg下的任务。当使用层次结构时,内核将杀死层次结构下的任务。

在这种情况下,不应调用panic_on_oom,并且不应杀死其他组中的任务。

引起OOM在memcg下并不困难。

情况A)当您可以swapoff时:

    #swapoff -a
    #echo 50M > /memory.limit_in_bytes

运行51M的malloc

情况B)当您使用mem+swap限制时:

    #echo 50M > memory.limit_in_bytes
    #echo 50M > memory.memsw.limit_in_bytes

运行51M的malloc

9.9 在任务迁移时移动计费

与任务关联的计费可以随着任务迁移而移动。

(Shell-A):

    #mkdir /cgroup/A
    #echo $$ >/cgroup/A/tasks
在/cgroup/A中运行一些使用一定量内存的程序。

(Shell-B):

    #mkdir /cgroup/B
    #echo 1 >/cgroup/B/memory.move_charge_at_immigrate
    #echo "运行在A组中的程序的pid" >/cgroup/B/tasks

通过读取A和B的*.usage_in_bytes或memory.stat,您可以看到计费已经被移动。

请参阅Memory Resource Controller的8.2,了解应写入move_charge_at_immigrate的值。

9.10 内存阈值

内存控制器使用cgroups通知API实现内存阈值。您可以使用tools/cgroup/cgroup_event_listener.c来测试它。

(Shell-A)创建cgroup并运行事件监听器:

    # mkdir /cgroup/A
    # ./cgroup_event_listener /cgroup/A/memory.usage_in_bytes 5M

(Shell-B)将任务添加到cgroup并尝试分配和释放内存:

    # echo $$ >/cgroup/A/tasks
    # a="$(dd if=/dev/zero bs=1M count=10)"
    # a=

每次您越过阈值时,您将从cgroup_event_listener看到消息。

使用/cgroup/A/memory.memsw.usage_in_bytes来测试memsw阈值。

也可以测试根cgroup。