经过大量调查,我原来的理解有几个问题。
首先,页面缓存是驻留在内存中的非连续页面的集合。很容易陷入将“页面缓存”视为单个连续的内存块/页面(类似于硬件缓存)的陷阱,但页面缓存只是内存中可用的一些页面的集合供以后访问。
“页面缓存”中的这些页面甚至没有虚拟存储在一起。如,内核doesn't 将页面缓存中的所有页面保存在一个全局结构或列表中。相反,更好的思考方式是页面缓存实际上是内核中所有 LRU 列表的并集。
这一点以及 mm/vmscan.c 有许多对 mem_cgroups 的引用并有一个名为 shrink_node_memcgs 的函数的事实,让我觉得每个 cgroup 都有自己的页面缓存。
如前所述,每个 cgroup 都有自己的 LRU 列表。然而,每个 cgroup 没有自己的页面缓存。但是,如果你从所有 cgroup 中整理所有 LRU,你将持有页面缓存的所有页面。
我知道一个page cache是用struct address_space来表示的
这是不正确的。 struct address_space 确实代表了页面缓存中的一些页面,但它本身并不代表整个页面缓存。 struct address_space 实际上表示来自单个 inode(文件)或块设备的缓存页面(参见 address_space 结构中的 host member)。事实上,原帖中的链接one 有这样的引用:“一个更好的名字[struct address_space] 可能是 page_cache_entity
或 physical_pages_of_a_file。”(pg.327,第 16 章)
如何找出与给定结构地址空间相关联的 cgroup?
由于address_space 对应于单个inode(文件)而不是单个cgroup,因此该结构中的所有页面并非都计入同一个cgroup,因此驻留在同一个cgroup LRU 列表。例如,假设 cgroup 1 中的一个进程读取一个大文件的开头,但 cgroup 2 中的另一个进程读取该文件的结尾。每个进程访问的页面来自相同的inode 和struct address_space,但其中一些页面将在 cgroup 1 的 LRU 列表中,而其他页面将在 cgroup 2 中。
因此无法从struct address_space 中找到 cgroup。相反,理论上,您可以遍历 struct address_space 页面,然后找到对应于每个单独页面 (page->mem_cgroup->css.cgroup) 的 cgroup。
请记住,向一个 cgroup 收费的页面仍有可能被不同 cgroup 中的另一个进程共享/访问。请参阅此处针对 v1 (Section 2.3) 和此处针对 v2 ("Memory Ownership") 为共享内存收取 cgroup 费用的规则。
附录:
在研究过程中,我遇到了this article,这让我感到困惑,并让我认为address_space 与单个cgroup 相关联。图 4.2 使 address_space 看起来像埋在 mm_struct 中;并且由于mm_struct 特定于一个进程,那么这个address_space 也应该对应于一个进程,并且通过扩展,该进程的cgroup。实际上,这个mm_struct对应的进程持有一个文件的文件描述符(用struct file表示),这个文件描述符指向文件inode及其对应的address_space。需要这个address_space 在“页面缓存”中专门从此文件中查找页面。