drop_caches_sysctl_handler 函数
通过 echo 到文件/proc/sys/vm/drop_cache的处理函数为drop_caches_sysctl_handler,其中echo 1 > /proc/sys/vm/drop_cache为释放page 页cache,echo 2 > /proc/sys/vm/drop_cache为释放slab cache, echo 3 > /proc/sys/vm/drop_cache为释放page 页cache和slab cache。
释放slab
echo 2 > /proc/sys/vm/drop_cache为释放slab cache其主要处理函数为drop_slab->drop_slab_node
首先通过mem_cgroup_iter用于遍历内存控制组(memory cgroup,简称memcg)其中第一个参数root:层次结构的根节点,即遍历的起始点。第二个参数prev:上一次调用返回的内存控制组,如果是第一次调用则为NULL。第三个参数reclaim:用于共享回收遍历的cookie,如果是完整的遍历则为NULL。再通过内层do-while循环遍历所有的cgroup,其中freed为每次释放的slab对象数量,当释放的数量小于10时表示当前已没有更多的可释放的slab cache,即退出最外层while循环。
以struct super_block为例,其中包括目录项dentry cache和 inode cache 和其他cache
其中在sget_userns函数中通过register_shrinker_prepared注册了&s->s_shrink。
do_shrink_slab释放slab
do_shrink_slab函数中会调用ret = shrinker->scan_objects(shrinker, shrinkctl);进行slab cache的释放,其中ret返回值即代表释放的slab数量。
以struct super_block中的目录项dentry释放为例
其中sb->s_dentry_lru保存所有可释放slab的目录项dentry cache。通过list_lru_shrink_walk函数释放mem_cgroup对应的目录项cache。并将所有需要释放的目录项加入到dispose链表中,并调用shrink_dentry_list函数进行进行释放。
其函数原理为:
(1)使用一个 while 循环来遍历传入的 list 列表,直到列表为空。
从列表的尾部(list->prev)获取一个目录项(dentry)。这是因为 LRU(Least Recently Used)缓存策略通常从尾部开始删除最不常用的项。
(2)使用 spin_lock 对 dentry 进行加锁,以确保线程安全地访问 dentry。
(3)使用 rcu_read_lock 读取 RCU(Read-Copy Update)锁,RCU 是一种用于实现无锁读操作的机制。
(4)调用 shrink_lock_dentry 尝试锁定 dentry。如果无法锁定,说明有其他线程正在使用它,那么跳过当前循环,处理下一个目录项。
(5)如果 shrink_lock_dentry 返回失败,则从 LRU 列表中删除这个 dentry,并检查其引用计数 d_lockref.count 是否小于 0。
(6)如果引用计数小于 0,并且 d_flags 中有 DCACHE_MAY_FREE 标志,那么可以安全地释放这个 dentry,调用 dentry_free 来完成释放。
(7)如果成功锁定了 dentry,首先从 LRU 列表中删除它,然后调用 __dentry_kill 来标记这个 dentry 为已删除状态。
(8)获取当前 dentry 的父目录项 parent。如果父目录项和当前目录项相同(例如,对于根目录),则跳过后续处理。
(9)如果父目录项不是自己且父目录项的引用计数小于等于1(即表示没有其他更多的地方用到该目录项)。
这个函数的目的是在不影响正在使用的目录项的情况下,清理那些不再需要的目录项,以释放内存。它使用了一种递归的方式,不仅删除当前的目录项,还删除它的所有祖先目录项,这样可以减少由于层次结构而产生的碎片。通过这种方式,它有助于优化 dentry 缓存的性能和内存使用。
需要注意的是在__dentry_kill函数中会通过dentry_unlist函数调整当前目录d_child和父目录d_subdirs的链表关系,并调用dentry_free最终释放cache内存。
drop_pagecache_sb释放page cache
drop_pagecache_sb函数调用invalidate_mapping_pages函数对该超级块中所有inode节点i_mapping地址空间的所有未锁定的page 页调用invalidate_inode_page函数。
其中invalidate_inode_page函数检查脏页和正在回写磁盘页和映射页,都不满足时调用invalidate_complete_page函数进行实际的page cache释放。
其中page_mapped函数为判断page->_mapcount映射引用计数。
在alloc_set_pte函数中对虚拟地址映射的page 页设置page->_mapcount映射引用计数。
__alloc_pages_slowpath函数
在__alloc_pages_slowpath慢速申请page页函数中,会先后调用__alloc_pages_direct_reclaim和__alloc_pages_direct_compact函数进行内存页分配。
__alloc_pages_direct_reclaim函数中首先通过__perform_reclaim函数尝试回收一些内存,然后再调用get_page_from_freelist再次申请内存页。
__alloc_pages_direct_compact函数通过try_to_compact_pages函数尝试进行内存规整将内存页碎片整理为完成的内存片,其中可通过echo 1 > /proc/sys/vm/compact_memory主动触发内存规整。再通过get_page_from_freelist函数重新申请内存页。
总结
1.drop slab cache会遍历所有所有注册到shrinker_list的链表并调用其回调函数scan_objects释放slab cache。
2.drop page cache 会释放所有struct super_block下的所有inode节点地址空间inode->i_mapping的所有非锁定非脏页非回写页非映射页进行page cache释放。
3.通过/proc/sys/vm/drop_caches 写入特定的值时释放cache缓存。写入值1表示释放page页cache。写入值2表示释放目录项和 inode 缓存等其他slab cache。写入值3表示上述两种cache都进行释放。