关于linux:Linux-内存管理新特性-Memory-folios-解读

1次阅读

共计 9401 个字符,预计需要花费 24 分钟才能阅读完成。

一、folio [ˈfoʊlioʊ] 是什么

1.1 folio 的定义

Add memory folios, a new type to represent either order-0 pages or the head page of a compound page.

folio 能够看成是 page 的一层包装,没有开销的那种。folio 能够是单个页,也能够是复合页。

$$
(图片援用围绕 HugeTLB 的极致优化)
$$

上图是 page 构造体的示意图,64 字节治理 flags, lru, mapping, index, private, {ref_, map_}count, memcg_data 等信息。当 page 是复合页的时候,上述 flags 等信息在 head page 中,tail page 则复用治理 compound_{head, mapcount, order, nr, dtor} 等信息。

struct folio {
        /* private: don't document the anon union */
        union {
                struct {
        /* public: */
                        unsigned long flags;
                        struct list_head lru;
                        struct address_space *mapping;
                        pgoff_t index;
                        void *private;
                        atomic_t _mapcount;
                        atomic_t _refcount;
#ifdef CONFIG_MEMCG
                        unsigned long memcg_data;
#endif
        /* private: the union with struct page is transitional */
                };
                struct page page;
        };
};

folio 的构造定义中,flags, lru 等信息和 page 完全一致,因而能够和 page 进行 union。这样能够间接应用 folio->flags 而不必 folio->page->flags。

#define page_folio(p)           (_Generic((p),                          \
        const struct page *:    (const struct folio *)_compound_head(p), \
        struct page *:          (struct folio *)_compound_head(p)))
#define nth_page(page,n) ((page) + (n))
#define folio_page(folio, n)    nth_page(&(folio)->page, n)

第一眼看 page_folio 可能有点懵,其实等效于:

switch (typeof(p)) {
  case const struct page *:
    return (const struct folio *)_compound_head(p);
  case struct page *:
    return (struct folio *)_compound_head(p)));
}

就这么简略。

_Generic 是 C11 STANDARD – 6.5.1.1 Generic selection(https://www.open-std.org/JTC1/sc22/wg14/www/docs/n1570.pdf)个性,语法如下:

Generic selection
Syntax
 generic-selection:
  _Generic (assignment-expression , generic-assoc-list)
 generic-assoc-list:
  generic-association
  generic-assoc-list , generic-association
 generic-association:
  type-name : assignment-expression
  default : assignment-expression

page 和 folio 的互相转换也很间接。不论 head,tail page,转化为 folio 时,意义等同于获取 head page 对应的 folio;folio 转化为 page 时,folio->page 用于获取 head page,folio_page(folio, n) 能够用于获取 tail page。

问题是,原本 page 就能代表 base page,或者 compound page,为什么还须要引入 folio?

1.2 folio 能做什么?

The folio type allows a function to declare that it’s expecting only a head page. Almost incidentally, this allows us to remove various calls to VM_BUG_ON(PageTail(page)) and compound_head().

起因是,page 的含意太多了,能够是 base page,能够是 compound head page,还能够是 compound tail page。

如上述所说,page 元信息都寄存在 head page(base page 能够看成是 head page)上,例如 page->mapping, page->index 等。但在 mm 门路上,传递进来的 page 参数总是须要判断是 head page 还是 tail page。因为没有上下文缓存,mm 门路上可能会存在太多反复的 compound_head 调用。

这里以 mem_cgroup_move_account 函数调用举例,一次 mem_cgroup_move_account 调用,最多能执行 7 次 compound_head。

static inline struct page *compound_head(struct page *page)
{unsigned long head = READ_ONCE(page->compound_head);
        if (unlikely(head & 1))
                return (struct page *) (head - 1);
        return page;
}

再以 page_mapping(page) 为例具体分析,进入函数外部,首先执行 compound_head(page) 获取 page mapping 等信息。另外还有一个分支 PageSwapCache(page),当执行这个分支函数的时候,传递的是 page,函数外部还需执行一次 compound_head(page) 来获取 page flag 信息。

struct address_space *page_mapping(struct page *page)
{
        struct address_space *mapping;
        page = compound_head(page);
        /* This happens if someone calls flush_dcache_page on slab page */
        if (unlikely(PageSlab(page)))
                return NULL;
        if (unlikely(PageSwapCache(page))) {
                swp_entry_t entry;
                entry.val = page_private(page);
                return swap_address_space(entry);
        }
        mapping = page->mapping;
        if ((unsigned long)mapping & PAGE_MAPPING_ANON)
                return NULL;
        return (void *)((unsigned long)mapping & ~PAGE_MAPPING_FLAGS);
}
EXPORT_SYMBOL(page_mapping);

当切换到 folio 之后,page_mapping(page) 对应 folio_mapping(folio),而 folio 隐含着 folio 自身就是 head page,因而两个 compound_head(page) 的调用就省略了。mem_cgroup_move_account 仅仅是冰山一角,mm 门路上到处是 compound_head 的调用。千里之行; 始于足下,不仅执行开销缩小了,开发者也能失去提醒,以后 folio 肯定是 head page,缩小判断分支。

1.3 folio 的间接价值

1)缩小太多冗余 compound_head 的调用。

2)给开发者提醒,看到 folio,就能认定这是 head page。

3)修复潜在的 tail page 导致的 bug。

Here's an example where our current confusion between"any page"and"head page" at least produces confusing behaviour, if not an
outright bug, isolate_migratepages_block():
        page = pfn_to_page(low_pfn);
        if (PageCompound(page) && !cc->alloc_contig) {const unsigned int order = compound_order(page);
                if (likely(order < MAX_ORDER))
                        low_pfn += (1UL << order) - 1;
                goto isolate_fail;
        }
compound_order() does not expect a tail page; it returns 0 unless it's
a head page.  I think what we actually want to do here is:
        if (!cc->alloc_contig) {struct page *head = compound_head(page);
            if (PageHead(head)) {const unsigned int order = compound_order(head);
                low_pfn |= (1UL << order) - 1;
                goto isolate_fail;
            }
        }
Not earth-shattering; not even necessarily a bug.  But it's an example
of the way the code reads is different from how the code is executed,
and that's potentially dangerous.  Having a different type for tail
and not-tail pages prevents the muddy thinking that can lead to
tail pages being passed to compound_order().

1.4 folio-5.16 曾经合入

This converts just parts of the core MM and the page cache.

willy/pagecache.git 共有 209 commit。这次 5.16 的合并窗口中,作者 Matthew Wilcox (Oracle) mailto:willy@infradead.org 先合入 folio 根底局部,即 Merge tag folio-5.16,其中蕴含 90 commits,74 changed files with 2914 additions and 1703 deletions。除了 folio 定义等基础设施之外,这次改变次要集中在 memcg, filemap, writeback 局部。folio-5.16 用 folio 逐渐取代 page 的过程,仿佛值得一提。mm 门路太多了,如果强迫症一次性替换完,就得 top-down 的形式,从 page 调配的中央改成 folio,而后一路改上来。这不事实,简直要批改整个 mm 文件夹了。folio-5.16 采纳的是 bottom-up 的形式,在 mm 门路的某个函数开始,将 page 替换成 folio,其外部所有实现都用 folio,造成一个“闭包”。而后批改其 caller function,用 folio 作为参数调用该函数。直到所有 caller function 都改完了,那么这个“闭包”又扩大了一层。有些函数的调用者很多,一时改不完,folio-5.16 就提供了一层 wrapper。这里以 page_mapping/folio_mapping 为例。

首先闭包里是 folio_test_slab(folio),folio_test_swapcache(folio) 等基础设施,而后向上扩大到 folio_mapping。page_mapping 的调用者很多,mem_cgroup_move_account 能顺利地调用 folio_mapping,而 page_evictable 却还是保留应用 page_mapping。那么闭包在这里进行扩大。

struct address_space *folio_mapping(struct folio *folio)
{
        struct address_space *mapping;
        /* This happens if someone calls flush_dcache_page on slab page */
        if (unlikely(folio_test_slab(folio)))
                return NULL;
        if (unlikely(folio_test_swapcache(folio)))
                return swap_address_space(folio_swap_entry(folio));
        mapping = folio->mapping;
        if ((unsigned long)mapping & PAGE_MAPPING_ANON)
                return NULL;
        return (void *)((unsigned long)mapping & ~PAGE_MAPPING_FLAGS);
}
struct address_space *page_mapping(struct page *page)
{return folio_mapping(page_folio(page));
}
mem_cgroup_move_account(page, ...) {folio = page_folio(page);
  mapping = folio_mapping(folio);
}
page_evictable(page, ...) {ret = !mapping_unevictable(page_mapping(page)) && !PageMlocked(page);
}

二、folio 就这些吗?

很多小伙伴看到这里是不是和我有一样的感触:就这些吗?仅仅是 compound_head 的问题吗?我不得不去学习 LWN: A discussion on folios(https://lwn.net/Articles/869942/),LPC 2021 – File Systems MC(https://www.youtube.com/watch?v=U6HYrd85hQ8&t=1475s)大佬对于 folio 的探讨。而后发现 Matthew Wilcox 的主题不是《The folio》,而是《Efficient buffered I/O》。事件并不简略。

这次 folio-5.16 合入的都是 fs 相干的代码,组里大佬提到“Linux-mm 社区大佬不批准全副把 page 替换成 folio,对于匿名页和 slab,短期内还是不能替换”。于是我持续翻阅 Linux-mm 邮件列表。

2.1 folio 的社区探讨

2.1.1 命名

首先是 Linus,Linus 示意他不厌恶这组 patch,因为这组 patch 的确解决了 compound_head 的问题;然而他也不喜爱这组 patch,起因是 folio 听起来不直观。通过若干对于取名的探讨,当然命名最初还是 folio。

2.1.2 FS 开发者的意见

目前 page cache 中都是 4K page,page cache 中的大页也是只读的,例如代码大页(https://openanolis.cn/sig/Cloud-Kernel/doc/475049355931222178)个性。为什么 Transparent huge pages in the page cache 始终没有实现,能够参考这篇 LWN(https://lwn.net/Articles/686690/)。其中一个起因是,要实现 读写 file THP,基于 buffer_head 的 fs 对 page cache 的解决过于简单。

  • buffer_head

1.buffer_head 代表的是物理内存映射的块设施偏移地位,个别一个 buffer_head 也是 4K 大小,这样一个 buffer_head 正好对应一个 page。某些文件系统可能采纳更小的 block size,例如 1K,或者 512 字节。这样一个 page 最多能够有 4 或者 8 个 buffer_head 构造体来形容其内存对应的物理磁盘地位。
2. 这样,在解决 multi-page 读写的时候,每个 page 都须要通过 get_block 获取 page 和 磁盘偏移的关系,低效且简单。

  • iomap

1.iomap 最后是从 XFS 外部拿进去的,基于 extent,人造反对 multi-page。即在解决 multi-page 读写的时候,仅需一次翻译就能获取所有 page 和 磁盘偏移的关系。
2. 通过 iomap,文件系统与 page cache 隔离开来了,例如,它们在示意大小的时候都应用字节,而不是有多少 page。因而,Matthew Wilcox 倡议任何间接应用 page cache 的文件系统都应该思考要换到 iomap 或 netfs_lib 了。
3. 隔离 fs 与 page cache 的形式或者不止 folio,然而例如 scatter gather 是不被承受的,形象太简单。

这也是为什么 folio 先在 XFS/AFS 中落地了,因为这两个文件系统就是基于 iomap 的。这也是为什么 FS 开发者都强烈心愿 folio 被合入,他们能够不便地在 page cache 中应用更大的 page,这个做法能够使文件系统的 I/O 更有效率。buffer_head 有一些性能是以后 iomap 依然不足的。而 folio 的合入,能让 iomap 失去推动,从而使 block-based 文件系统可能改成应用 iomap。

2.1.3 MM 开发者的意见

最大的异议来自 Johannes Weiner,他抵赖 compound_head 的问题,但感觉修复该问题而引入这么大的改变不值得;同时认为 folio 对 fs 的所做的优化,anonymous page 不须要。

Unlike the filesystem side, this seems like a lot of churn for very little tangible value. And leaves us with an end result that nobody appears to be terribly excited about.But the folio abstraction is too low-level to use JUST for file cache and NOT for anon. It’s too close to the page layer itself and would duplicate too much of it to be maintainable side by side.

最初在 Kirill A. Shutemov、Michal Hocko 等大佬的力挺 folio 态度下,Johannes Weiner 也斗争了。

2.1.4 达成统一

社区探讨到最初,针对 folio 的拥护意见在 folio-5.15 的代码中都曾经不存在了,但错过了 5.15 的合并窗口,因而这次 folio-5.16 一成不变被合入了。

2.2 folio 的深层价值

I think the problem with folio is that everybody wants to read in her/his hopes and dreams into it and gets disappointed when see their somewhat related problem doesn’t get magically fixed with folio.
Folio started as a way to relief pain from dealing with compound pages. It provides an unified view on base pages and compound pages. That’s it.
It is required ground work for wider adoption of compound pages in page cache. But it also will be useful for anon THP and hugetlb.
Based on adoption rate and resulting code, the new abstraction has nice downstream effects. It may be suitable for more than it was intended for initially. That’s great.
But if it doesn’t solve your problem… well, sorry…
The patchset makes a nice step forward and cuts back on mess I created on the way to huge-tmpfs.
I would be glad to see the patchset upstream.
–Kirill A. Shutemov

大家都晓得“struct page 相干的凌乱”,但没有人去解决,大家都在默默忍耐这长期以来的困扰,在代码中充斥着如下代码。

if (compound_head(page)) // do A;
else                     // do B;

folio 并不完满,或者因为大家冀望太高,导致多数人对 folio 的最终实现示意悲观。但少数人认为 folio 是在正确方向上的重要一步。毕竟后续还有更多工作要实现。

三、folio 后续工作及其他

3.1 folio 开发计划

For 5.17, we intend to convert various filesystems (XFS and AFS are ready; other filesystems may make it) and also convert more of the MM and page cache to folios. For 5.18, multi-page folios should be ready.

3.2 folio 还能晋升性能

The 80% win is real, but appears to be an artificial benchmark (postgres startup, which isn’t a serious workload). Real workloads (eg building the kernel, running postgres in a steady state, etc) seem to benefit between 0-10%.

folio-5.16 缩小大量 compound_head 调用,在 sys 高的 micro benchmark 中该当有性能晋升。未实测。folio-5.18 multi-page folios 反对之后,实践上 I/O 效率能晋升,刮目相待。

3.3 我应该怎么用 folio?

FS 开发者最应该做的就是把那些依然应用 buffer head 的文件系统转换为应用 iomap 进行 I/O,至多对于那些 block-based 文件系统都应该这么做。其余开发者欣然接受 folio 即可,基于 5.16+ 开发的新个性能用 folio 就用 folio,相熟一下 API 即可,内存调配回收等 API 实质没有扭转。

点击立刻收费试用云产品 开启云上实际之旅!

原文链接

本文为阿里云原创内容,未经容许不得转载。

正文完
 0