memory cgroupとpageのLRUめも

カーネルは4.1系です。

include/linux/mm_inline.hにLRUへの登録・削除処理の実装があります。

static __always_inline void add_page_to_lru_list(struct page *page,
                struct lruvec *lruvec, enum lru_list lru)
{
    int nr_pages = hpage_nr_pages(page);
    mem_cgroup_update_lru_size(lruvec, lru, nr_pages);
    list_add(&page->lru, &lruvec->lists[lru]);
    __mod_zone_page_state(lruvec_zone(lruvec), NR_LRU_BASE + lru, nr_pages);
}

static __always_inline void del_page_from_lru_list(struct page *page,
                struct lruvec *lruvec, enum lru_list lru)
{
    int nr_pages = hpage_nr_pages(page);
    mem_cgroup_update_lru_size(lruvec, lru, -nr_pages);
    list_del(&page->lru);
    __mod_zone_page_state(lruvec_zone(lruvec), NR_LRU_BASE + lru, -nr_pages);
}

LRUに関連する構造体はこれらです。

これらの構造体の関連はこのようになります。

f:id:masami256:20170817002559p:plain

LRUはadd_page_to_lru_list()見ての通りlruvec構造体のlists配列のどこかにつながる感じです。

page構造体からlruvec構造体はmem_cgroup_page_lruvec()を使うことで取得できます。

lruvec = mem_cgroup_page_lruvec(page, page_zone(page));

mem_cgroup_page_lruvec()の処理のうち、最初にmemcgを使わない場合の処理がありますが、ここではmemcgは有効の場合を見てるので飛ばして、本筋的なところはこの辺です。

 memcg = page->mem_cgroup;
    /*
    * Swapcache readahead pages are added to the LRU - and
    * possibly migrated - before they are charged.
    */
    if (!memcg)
        memcg = root_mem_cgroup;

    mz = mem_cgroup_page_zoneinfo(memcg, page);
    lruvec = &mz->lruvec;

page構造体の構造体のメンバ変数mem_cgroupがpageが所属するmemcgのmem_cgroup構造体にアクセスできます。そしてmem_cgroup_page_zoneinfo()を使ってmem_cgroup_per_zone構造体を取得します。mem_cgroup_page_zoneinfo()はこんな関数です。

static struct mem_cgroup_per_zone *
mem_cgroup_page_zoneinfo(struct mem_cgroup *memcg, struct page *page)
{
    int nid = page_to_nid(page);
    int zid = page_zonenum(page);

    return &memcg->nodeinfo[nid]->zoneinfo[zid];
}

nidがNUMAのノード番号でzidがZONE_NORMALとかのインデックスですね(きっと)。page構造体からそのmemcg使っているNUMAノードとZONEを取得してるはずです。これでlruvec構造体は取得できました。

では、実際にadd_page_to_lru_list()を呼んでるところを見てみると、例えばこのunlock_page_lru()があります。

static void unlock_page_lru(struct page *page, int isolated)
{
    struct zone *zone = page_zone(page);

    if (isolated) {
        struct lruvec *lruvec;

        lruvec = mem_cgroup_page_lruvec(page, zone);
        VM_BUG_ON_PAGE(PageLRU(page), page);
        SetPageLRU(page);
        add_page_to_lru_list(page, lruvec, page_lru(page));
    }
    spin_unlock_irq(&zone->lru_lock);
}

この関数だと対象のzoneはpage構造体から取得しています。そして、mem_cgroup_page_lruvec()でlruvec構造体を取得してからのadd_page_to_lru_list()でlruvecのlistsのリストにpageを繋いでいます。listsのインデックスはpage_lru()で決めていますね。

static inline enum lru_list page_lru_base_type(struct page *page)
{
    if (page_is_file_cache(page))
        return LRU_INACTIVE_FILE;
    return LRU_INACTIVE_ANON;
}

ファイルキャッシュとして使うかどうかでindexが変わるようです。

というわけで、LRUはmemcg単位に存在していて、NUMAノードやZONEによって違うlruvecが使われます。そして、pageの用途によっても違うリストに繋がるというのがわかりました。 ( ´ー`)フゥー...