Linux 内存回收机制

【基本结构】
5条lru链表:
anonymous active
anonymous inactive
file active
file inactive
unevictable
根据page的属性(是否mlock)判断是否放到unevictable list,根据pg_active和pg_active标志在active和inactive之间流动。

【在active和inactive之间流动的核心为:】

– 如果页面被认为是活跃的,则将该页的 PG_active 置位;否则,不置位。
– 当页面被访问时,检查该页的 PG_referenced 位,若未被置位,则置位之;若发现该页的 PG_referenced 已经被置位了,则意味着该页经常被访问,这时,若该页在 inactive 链表上,则置位其 PG_active 位,将其移动到 active 链表上去,并清除其 PG_referenced 位的设置;如果页面的 PG_referenced 位被置位了一段时间后,该页面没有被再次访问,那么 Linux 操作系统会清除该页面的 PG_referenced 位,因为这意味着这个页面最近这段时间都没有被访问。
– PG_referenced 位同样也可以用于页面从 active 链表移动到 inactive 链表。对于某个在 active 链表上的页面来说,其 PG_active 位被置位,如果 PG_referenced 位未被置位,给定一段时间之后,该页面如果还是没有被访问,那么该页面会被清除其 PG_active 位,挪到 inactive 链表上去。

【思考】

个人觉得,second chance回收算法在某种意义上也可以完成上面的意图,即发现某个page被pg_referenced置位了,那么把他移到链表的尾部(最新被访问),这样就不需要多个list了。但是这样就不能统计nr_active和nr_inactive了,而Linux的回收算法还会把这两个链表的长度考虑进去,做到一定程度的平衡,或者说主动的进行双向的流动,而上面说的second chance一条链的算法做不到主动去平衡。有了主动平衡,在回收的时候可以只遍历inactive链表,减少了一次性扫描太多page的可能性,一定程度上可以缓解系统性能抖动。
分了anonymous和file两种链的目的,我目前只考虑到可以在回写时区别调用不同的函数,一个写到swap,一个调用相应文件系统的回写函数
freelist保存了free的物理页,kswapd和alloc_slowpath会给他添砖加瓦,而程序的释放等是其收入的主要来源。kswapd周期性的唤醒或者被alloc_slowpath唤醒(如果分配内存的函数没能从freelist上找到所需的order的内存,那么就要悲剧的进入slowpath了,现象就是程序卡顿了,qps下降了。。。),判断当前内存(某个zone,如果有numa)是否balance,这里的balance可以简单理解为(2.6.32内核)是否低于low water mark(通过vm.min_free_kbytes的设定值计算出来的一个下限水位值),如果低于这个值,那么kswapd会shrink上面那些list,然后释放出来的page,挂到freelist里面,直到内存达到high water mark

【shrink list的顺序?】

enum lru_list {
    LRU_INACTIVE_ANON = LRU_BASE,
    LRU_ACTIVE_ANON = LRU_BASE + LRU_ACTIVE,
    LRU_INACTIVE_FILE = LRU_BASE + LRU_FILE,
    LRU_ACTIVE_FILE = LRU_BASE + LRU_FILE + LRU_ACTIVE,
    LRU_UNEVICTABLE,
    NR_LRU_LISTS
};
#define for_each_lru(lru) for (lru = 0; lru < NR_LRU_LISTS; lru++)
#define for_each_evictable_lru(lru) for (lru = 0; lru <= LRU_ACTIVE_FILE; lru++)
但是真正回收的时候,还是要给active几分面子的:
static unsigned long shrink_list(enum lru_list lru, unsigned long nr_to_scan,
                 struct lruvec *lruvec, struct scan_control *sc)
{
    if (is_active_lru(lru)) {
        if (inactive_list_is_low(lruvec, lru))
            shrink_active_list(nr_to_scan, lruvec, sc, lru);
        return 0;
    }
    return shrink_inactive_list(nr_to_scan, lruvec, sc, lru);
}

【什么样的page可以被回收 】

要看具体的配置,具体干活的函数在这里:__isolate_lru_page
基本上都可以除了unevictable不可以
如果是脏页:
Only pages without mappings or that have a
             * ->migratepage callback are possible to migrate
             * without blocking
migratepage是什么鬼:在move_to_new_page函数中找到下面一段注释
        /*
         * Most pages have a mapping and most filesystems provide a
         * migratepage callback. Anonymous pages are part of swap
         * space which also has its own migratepage callback. This
         * is the most common path for page migration.
         */

【参考文献】

Leave a Reply

Your email address will not be published. Required fields are marked *