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。
标签:opt,memcg,cpuset,echo,内存,cgroup,计费,ChatGPT,备忘 From: https://www.cnblogs.com/pengdonglin137/p/17891908.html