你是什么内存: PageAnon 与 PageSwapBacked

一、前言

    最近在翻阅内存回收相关代码,发现有 PageAnon(page) 和 PageSwapBacked(page) 两个函数/宏很费解。从一些资料上来看二者都有表示匿名页或者非文件页的含义,且内核中有二者的各种组合使用,这进一步加剧了我的迷惑:
    一个page究竟会在哪些场景下会出现如下情况:

PageAnon(page) && PageSwapBacked(page)
!PageAnon(page) && PageSwapBacked(page)
PageAnon(page) && !PageSwapBacked(page)

    要了解他们的含义,先看看他们的实现细节, 二者在代码层面的实现如下:

  • PageAnon(page)
#define PAGE_MAPPING_ANON       0x1
static __always_inline int PageAnon(struct page *page)
{
        page = compound_head(page);
        return ((unsigned long)page->mapping & PAGE_MAPPING_ANON) != 0;
}

    即该函数判断page->mapping的bit0是否置1,如果是则PageAnon()返回true。

  • PageSwapBacked(page)

    而PageSwapBacked(page)则是判断page->flags是否有PG_swapbacked设置标志。

二、不同的组合场景

2.1 PageAnon(page) && PageSwapBacked(page)场景

    如果一个页PageAnon(page) && PageSwapBacked(page),则说明这个page是一个标准的匿名页,其page->mapping指向对应的vma->anon_vma。

    从下面的这个流程图可以看出端倪:

图1 匿名页缺页异常分配page的流程

    上面的流程是缺页异常分配一个匿名页的流程,在这个流程中为新分配的page调用__SetPageSwapBacked()设置PG_swapbacked标志;
    然后调用WRITE_ONCE(page->mapping, (struct address_space *) anon_vma)设置page->mapping,这样设置后(page->mapping & PAGE_MAPPING_ANON) != 0,即PageAnon()为true。

2.2 !PageAnon(page) && PageSwapBacked(page)

    这个场景是page为shmem的情况,这里为了与匿名页的情况进行对比,同样以缺页异常分配page流程来观察:

 图2 shmem缺页异常分配page流程

    这是shmem缺页异常的流程,这个流程先分配一个page,然后调用__SetPageSwapBacked()设置PG_swapbacked标志;
    但是其page->mapping指向的是struct adress_space*类型的inode->i_mapping,这是一个指针满足4字节(32位)或者8字节(64位)对齐,因而(page->mapping & PAGE_MAPPING_ANON)==0,即PageAnon()为false.

2.3 PageAnon(page) && !PageSwapBacked(page)   

    这是一种比较特殊的情况,称为lazyfree pages,这种pages是通过madvise()对匿名页设置了MADV_FREE形成的。
    关于MADV_FREE相关的情况具体参考:https://man7.org/linux/man-pages/man2/madvise.2.html
    不过要形成PageAnon(page) && !PageSwapBacked(page)这种状态并非一蹴而就,而是分两个阶段:
      1) 将page加入到lazyfree缓存链表this_cpu_ptr(&lru_pvecs.lru_lazyfree)中,如下流程图所示:

图3 madvise设置 MADV_FREE 页流程

      2) 清除PG_swapbacked标志

       清除lazyfree pages PG_swapbacked标志的点有两个:
       (1)lazyfree lru缓存链表满或者PageCompound(page)的情况
       (2)内存紧张进行回收时的情况
    这两种情况都会调用lru_lazyfree_fn()函数来将lazyfree lru缓存链表上的page转移到lru链表上。

 图4 将lazefree page转移到lru链表

三、总结   

    除了上面的几个分析,实际上还可以参考include/linux/page-flags.h文件中对于PG_swapbacked标志的注释:

 * PG_swapbacked is set when a page uses swap as a backing storage.  This are
 * usually PageAnon or shmem pages but please note that even anonymous pages
 * might lose their PG_swapbacked flag when they simply can be dropped (e.g. as
 * a result of MADV_FREE).

    用个人粗糙的英文翻译一下大概就是:当一个page使用swap作为后备存储时需要为该page设置PG_swapbacked标志位。这种情况通常是匿名页或者shmem页,但是需要注意的是即使是匿名页也有可能不设置PG_swapbacked(例如MADV_FREE)。

原文地址:https://www.cnblogs.com/liuhailong0112/p/14426096.html