folio

[!NOTE] NOTE
There are only two hard things in Computer Science: cache invalidation and naming things.

计算机科学中只有两件难事:缓存失效和命名.

-- Phil Karlton

取名,要用名字恰到好处地描述其想描述的事物,要体现代码注释的最高原则: 源码即注释,这其实一点都不轻松.取名一般都是从生僻的变为大众的,这样才能朗朗上口,为人民群众所喜闻乐见,比如陈港生更名为成龙,杨旎奥改名为杨紫,刘福荣改名为刘德华.而内核从page到folio的一次改变,似乎是反其道而行之了.感觉有相当数量的同学可能都不见得认识folio这个单词.

folio 的英文解释是 由一张或多张整张纸组成的书籍或小册子, 和单页(page)对应. 它的目标在于解决内核面临的一个纠结状况.至于这个名字叫folio、pageset、superpage还是head_page,其实都没有那么重要了,背后真正重要的是,它要解决什么问题

乱局

众所周知,在Linux内核中,我们通常使用page来描述一页,这一页通常是4KB.这个世界如果所有人都是4KB的单页,那就简单归一了.但是,在晴朗的天空中,却漂浮中一朵乌云,这朵乌云就是compound page以及由compound page衍生出的hugepage,它们并非总是单页的.

在Linux中,我们并不总是以单一的4KB basepage为单位来获取、映射和释放内存.我们有时候,会把多个4KB复合在一起,进行申请、映射和释放:

在 Linux 内核中,struct foliostruct page 都是用于描述物理内存页面的结构体,但它们的设计目标和使用场景有所不同.


1. struct page


2. struct folio

引入的原因:


3. foliopage 的关系


4. folio 的优势


5. 示例代码

以下是如何从 foliopage 互相转换的简单代码:

#include <linux/mm.h>
#include <linux/folio.h>

// 获取 folio 的信息
void print_folio_info(struct page *page)
{
    struct folio *folio = page_folio(page);

    printk(KERN_INFO "Folio address: %p\n", folio);
    printk(KERN_INFO "Folio order (size): %d\n", folio_order(folio));
    printk(KERN_INFO "Folio refcount: %d\n", folio_ref_count(folio));

    // 获取 folio 内的第一个 page
    struct page *folio_start_page = folio_page(folio, 0);
    printk(KERN_INFO "Folio's first page address: %p\n", folio_start_page);
}

// 检查 folio 是否匿名
bool is_folio_anon(struct page *page)
{
    struct folio *folio = page_folio(page);
    return folio_test_anon(folio);
}
int get_page_type(pte_t *pte) {
    struct folio *folio = pfn_folio(pte_pfn(*pte));
    enum page_type page_type;
    if (unlikely(folio_test_ksm(folio))) {
        page_type = UNKNOWN_PAGE;
    } else if (folio_test_anon(folio)) {
        page_type = ANON_PAGE;
    } else {
        page_type = FILE_PAGE;
    }
    return page_type;
}

6. 总结

在新版本的 Linux 内核中,folio 的使用逐渐替代了大部分涉及大页的 page 操作.

folio 相关函数

代码中我们可能经常会看到 folio_test_lru, folio_set_lru 等函数, 但是好像又没有直接看到它的定义. 通过 clangd 我们可以追溯到该宏定义 PAGEFLAG, 其定义了相关的函数展开

#define PAGEFLAG(uname, lname, policy)                  \
    TESTPAGEFLAG(uname, lname, policy)              \
    SETPAGEFLAG(uname, lname, policy)               \
    CLEARPAGEFLAG(uname, lname, policy)

PAGEFLAG(Referenced, referenced, PF_HEAD)
    TESTCLEARFLAG(Referenced, referenced, PF_HEAD)
    __SETPAGEFLAG(Referenced, referenced, PF_HEAD)
PAGEFLAG(Dirty, dirty, PF_HEAD) TESTSCFLAG(Dirty, dirty, PF_HEAD)
    __CLEARPAGEFLAG(Dirty, dirty, PF_HEAD)
PAGEFLAG(LRU, lru, PF_HEAD) __CLEARPAGEFLAG(LRU, lru, PF_HEAD)
    TESTCLEARFLAG(LRU, lru, PF_HEAD)
PAGEFLAG(Active, active, PF_HEAD) __CLEARPAGEFLAG(Active, active, PF_HEAD)
    TESTCLEARFLAG(Active, active, PF_HEAD)

以 TESTPAGEFLAG 为例, 其会被展开为 folio_test_lru 和 Pagelru 函数. 函数实现其实就是对于该 bit 的判断

#define TESTPAGEFLAG(uname, lname, policy)              \
static __always_inline bool folio_test_##lname(struct folio *folio) \
{ return test_bit(PG_##lname, folio_flags(folio, FOLIO_##policy)); }    \
static __always_inline int Page##uname(struct page *page)       \
{ return test_bit(PG_##lname, &policy(page, 0)->flags); }

简而言之, PAGEFLAG(Referenced, referenced, PF_HEAD) 实际上就相当于定义了一大串的

static __always_inline bool folio_test_referenced(struct folio *folio)
{
    return test_bit(PG_referenced, folio_flags(folio, FOLIO_PF_HEAD));
}
static __always_inline int PageReferenced(struct page *page)
{
    return test_bit(PG_referenced, &PF_HEAD(page, 0)->flags);
}
static __always_inline void folio_set_referenced(struct folio *folio)
{
    set_bit(PG_referenced, folio_flags(folio, FOLIO_PF_HEAD));
}
static __always_inline void SetPageReferenced(struct page *page)
{
    set_bit(PG_referenced, &PF_HEAD(page, 1)->flags);
}
static __always_inline void folio_clear_referenced(struct folio *folio)
{
    clear_bit(PG_referenced, folio_flags(folio, FOLIO_PF_HEAD));
}
static __always_inline void ClearPageReferenced(struct page *page)
{
    clear_bit(PG_referenced, &PF_HEAD(page, 1)->flags);
}

[!TIP] TIP
看不清的话可以把 include/linux/page-flags.h 这部分宏定义 copy 下来然后用预处理器展开一下

gcc -E a.c -i a.i

参考

zood