Linux メモリ管理の新機能 - メモリ フォリオの解釈

1.フォリオ [ˈfoʊlioʊ] とは

1.1 フォリオの定義

オーダー 0 ページまたは複合ページの先頭ページを表す新しいタイプのメモリー フォリオを追加します。

Folio は、オーバーヘッドのない、ページのパッケージング層とみなすことができます。フォリオは、単一ページまたは複合ページにすることができます。

(画像は HugeTLB 周りの究極の最適化を引用しています)

上図はページ構造の模式図で、64バイトでフラグ、lru、マッピング、インデックス、プライベート、{ref_、map_}count、memcg_dataなどの情報が管理されます。複合ページの場合、上記のフラグ等の情報は先頭ページにあり、末尾ページでは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 の構造定義では、フラグ、lru などの情報はページと完全に一致しているため、ページと結合できます。これにより、Folio->page->flags の代わりに Folio->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 選択 ( 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

ページとノンブルの間の変換も非常に簡単です。先頭ページ、末尾ページに関係なく、ノンブル化した場合は先頭ページに対応するノンブルを取得するのと同じ意味となり、ノンブルをページ化した場合は、folio->pageで先頭ページを取得し、folio_page(folio, n) を使用して末尾ページを取得できます。

問題は、本来、page はベース ページまたは複合ページを表すことができるのに、なぜ Folio を導入する必要があるのか​​ということです。

1.2 Folio で何ができるの?

Folio タイプを使用すると、関数は先頭ページのみを期待していることを宣言できます。ほとんど偶然ですが、これにより、VM_BUG_ON(PageTail(page)) および Compound_head() へのさまざまな呼び出しを削除できるようになります。

その理由は、ページの意味が多すぎるためです。ベース ページ、複合ヘッド ページ、または複合テール ページになる可能性があります。

前述したように、ページ -> マッピング、ページ -> インデックスなどのページ メタ情報は、先頭ページ (ベース ページを先頭ページとみなすことができます) に格納されます。ただし、mm パスでは、渡されたページパラメータが先頭ページであるか末尾ページであるかを常に判断する必要があります。コンテキスト キャッシュがないため、mm パス上に重複した complex_head 呼び出しが多すぎる可能性があります。

ここでは mem_cgroup_move_account 関数呼び出しを例に挙げますが、1 回の mem_cgroup_move_account 呼び出しで、compound_head を最大 7 回実行できます。

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) を実行して、ページ マッピングとその他の情報を取得します。また、PageSwapCache(page)というブランチもあり、このブランチ関数を実行する際にはページが渡され、ページフラグ情報を取得するために関数内でcompound_head(page)を一度実行する必要があります。

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 自体が先頭ページであることを意味するため、2 つの complex_head(page) 呼び出しは省略されます。mem_cgroup_move_account は氷山の一角にすぎず、mm パスは complex_head 呼び出しでいっぱいです。少ないものを積み重ねると多くが得られ、実行のオーバーヘッドが削減されるだけでなく、開発者は現在の Folio が先頭ページである必要があることを通知して、判断の分岐を減らすこともできます。

1.3 フォリオの直接的な価値

1) 過剰な冗長な complex_head 呼び出しを減らします。

2) 開発者が Folio を見たときに、これが先頭ページであることを認識するように促します。

3) テールページによって引き起こされる潜在的なバグを修正します。

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 がマージされました

これにより、コア MM とページ キャッシュの一部のみが変換されます。

willy/pagecache.git には合計 209の コミットがあります。この 5.16 マージ ウィンドウでは、作成者 Matthew Wilcox (Oracle) <[email protected]> が最初に Folio の基本部分、つまりマージ タグ folio-5.16 をマージしました。これには、90 件のコミット、2914 件の追加と 1703 件の削除を含む 74 件の変更 ファイル が 含ま れ  います 。Folio 定義やその他のインフラストラクチャに加えて、この変更は主に memcg、ファイルマップ、およびライトバックの部分に焦点を当てています。folio-5.16 ページを段階的に Folio に置き換えるプロセスは、言及する価値があると思われます。mmパスが多すぎるので、強迫性障害で一気に差し替える場合は、トップダウン方式でページ割り当てからノンブルに変更し、最後まで変更する必要があります。これは非現実的であり、ほとんど mm フォルダー全体を変更することになります。Folio-5.16 はボトムアップ方式を採用しており、mm パス内の特定の関数の先頭でページが Folio に置き換えられ、内部実装はすべて Folio を使用し、「クロージャ」を形成します。次に、その呼び出し元関数を変更して、folio をパラメータとして関数を呼び出すようにします。すべての呼び出し側関数が変更されるまで、この「クロージャ」は別の層によって拡張されます。一部の関数には多数の呼び出し元があり、一度に変更することはできません。Folio-5.16 はラッパーのレイヤーを提供します。ここでは例として、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);
}

2.フォリオはそれだけですか?

これを見て、多くの友人が私と同じように感じているでしょうか。「それだけですか?」それは単にcompound_headの問題でしょうか?LWN: フォリオに関するディスカッション( 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 コミュニティのボスは、すべてのページを Folio に置き換えることに同意していません。匿名ページとスラブについては、依然として置き換えることができません」と述べました。短期的には。」そこで私は Linux-mm メーリング リストを閲覧し続けました。

2.1 Folio に関するコミュニティのディスカッション

2.1.1 命名

1 人目は Linus です。Linus は、このパッチ セットは、compound_head の問題を解決するため、嫌いではないと言いましたが、Folio が直観的ではないので、このパッチ セットも好きではないと言いました。ネーミングについて何度か議論した結果、もちろんネーミングはフォリオになりました。

2.1.2 FS開発者の意見

現在、ページ キャッシュには 4K ページがあり、コード ラージ ページ( https://openanolis.cn/sig/Cloud-Kernel/doc/475049355931222178 )など、ページ キャッシュ内のラージ ページも読み取り専用です。特徴。ページ キャッシュ内の透明な巨大ページが実装されていない理由については、この LWN ( https://lwn.net/Articles/686690/ )を参照してください。その理由の 1 つは、ファイル THP の読み書きを実現するには、buffer_head ベースの fs によるページ キャッシュの処理が複雑すぎるためです。

  • buffer_headbuffer_head
    は物理メモリマップのブロックデバイスオフセット位置を表し、通常、buffer_headのサイズも4Kであるため、buffer_headは正確にページに対応する。一部のファイル システムでは、1K や 512 バイトなど、より小さいブロック サイズが使用される場合があります。このようなページには、そのメモリに対応する物理ディスクの位置を記述するために、最大 4 つまたは 8 つのbuffer_head 構造を持つことができます。
    このように、複数ページの読み書きを扱う場合、各ページはget_blockによりページとディスクオフセットの関係を取得する必要があり、非効率的かつ煩雑である。
  • iomap
    iomap は当初、エクステントに基づいて XFS 内から取り出され、当然マルチページをサポートします。つまり、複数ページの読み取りと書き込みを処理する場合、すべてのページとディスク オフセットの間の関係を取得するために必要な変換は 1 回だけです。
    iomap を介して、ファイル システムはページ キャッシュから分離されます。たとえば、ファイル システムはどちらも、ページ数ではなくサイズを表すときにバイトを使用します。したがって、Matthew Wilcox 氏は、ページ キャッシュを直接使用するファイル システムは、iomap または netfs_lib への切り替えを検討する必要があると提案しました。
    fs とページ キャッシュを分離する方法は Folio よりも優れている可能性がありますが、たとえば、Scatter Gather は受け入れられず、抽象化が複雑すぎます。

これら 2 つのファイル システムが iomap に基づいているため、Folio が最初に XFS/AFS に実装されたのはこのためです。これが、FS 開発者が Folio の導入を強く望んでいる理由であり、ページ キャッシュ内の大きなページを簡単に使用できるため、ファイル システムの I/O をより効率的に行うことができます。buffer_head には、現在の iomap にまだ欠けている機能がいくつかあります。Folio の統合により iomap が進歩し、iomap を使用するようにブロックベースのファイル システムを変更できるようになります。

2.1.3 MM開発者の意見

最大の反対意見は Johannes Weiner 氏で、彼は、compound_head の問題を認めたが、問題を解決するためにそのような大きな変更を導入する価値はないと感じ、また、fs 上の Folio の最適化は匿名ページには必要ないと考えていました。

ファイルシステム側とは異なり、これは、目に見える価値が非常に少ないため、大量のチャールズが行われているように見えます。そして、誰もそれほど興奮しているようには見えない最終結果が残ります。しかし、フォリオの抽象化は低レベルすぎて、ファイルキャッシュにのみ使用でき、アノンには使用できません。これはページ レイヤー自体に近すぎるため、複製する部分が多すぎて並べて維持できません。

結局、ヨハネス・ヴァイナーもキリル・A・シュテモフ、ミハル・ホッコ、その他の大物らによるフォリオの支援を受けて妥協した。

2.1.4 合意に達する

コミュニティのディスカッションの終了時点で、folio に対する反対意見は folio-5.15 のコードには存在しませんでしたが、5.15 のマージ ウィンドウが失われていたため、今回は folio-5.16 がそのままマージされました。

2.2 Folioの深い価値

Folio の問題は、誰もが自分の希望や夢を読み込んで読みたいと思っているのに、自分と多少関連する問題が Folio で魔法のように解決されないのを見て失望することだと思います。
Folio は、複合ページの処理による苦痛を軽減する方法として始まりました。基本ページと複合ページの統合ビューを提供します。それでおしまい。
これは、ページ キャッシュで複合ページを広く採用するために必要な基礎作業です。しかし、これは、anon THP と hugetlb にも役立ちます。
採用率と結果のコードに基づいて、新しい抽象化は下流に優れた効果をもたらします。当初の目的以上に適している可能性があります。それは素晴らしいことです。
ただし、問題が解決しない場合は、申し訳ありませんが…
このパッチセットは素晴らしい前進をもたらし、huge-tmpfs に移行する途中で生じた混乱を軽減します。
アップストリームのパッチセットを見ていただければ幸いです。
--キリル・A・シュテモフ

「構造体ページ関連の混乱」は誰もが知っていますが、誰も解決できず、誰もがこの長期にわたるトラブルに黙って耐えてきたのですが、コードには次のようなコードが埋め込まれています。

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

Folio は完璧ではありません。おそらく、みんなの期待が大きすぎたため、Folio の最終的な実装に失望を表明した人が数人いました。しかし、ほとんどの人は Folio が正しい方向への重要な一歩であると考えています。結局のところ、将来的にはやるべきことがまだたくさんあります。

3.フォリオフォロー業務等

3.1 フォリオ開発計画

5.17 では、さまざまなファイルシステムを変換し (XFS と AFS は準備が整っています。他のファイルシステムでも対応できる可能性があります)、さらに多くの MM とページ キャッシュを Folio に変換する予定です。5.18 では、複数ページの Folio が用意されているはずです。

3.2 folio もパフォーマンスを向上させることができます

80% の勝利は本物ですが、人工的なベンチマークであるようです (postgres の起動、深刻なワークロードではありません)。実際のワークロード (カーネルの構築、定常状態での postgres の実行など) では、0 ~ 10% の利益が得られるようです。

folio-5.16 では、大量の complex_head 呼び出しが削減され、sys が高いマイクロ ベンチマークのパフォーマンスが向上します。未検証。folio-5.18 で複数ページ Folio がサポートされると、理論的には I/O 効率が向上する可能性があるため、しばらく様子見します。

3.3 Folio はどのように使用すればよいですか?

FS 開発者が行うべき最も重要なことは、少なくともブロックベースのファイル システムでは、まだバッファ ヘッドを使用しているファイル システムを I/O に iomap を使用するように変換することです。他の開発者は Folio を容易に受け入れることができます。5.16 以降をベースにして開発された新機能が Folio を使用できる場合は、Folio を使用してください。API に慣れるだけです。メモリの割り当てやリサイクルなどの API の本質は変わりません。

クリックして今すぐクラウド製品を無料で試し、クラウドでの実践的な取り組みを始めましょう!

元のリンク

この記事は Alibaba Cloud のオリジナル コンテンツであり、許可なく複製することはできません。

中学生がコンパイルした Windows 12 Deepin-IDE の Web 版が正式公開 「真の独自開発」として知られる QQ が「3 端末同時アップデート」を実現、基盤となる NT アーキテクチャは Electron QQをベースにLinux、3.2.0を正式リリース 「紅蒙の父」王成陸 : 紅蒙PC版システムは来年立ち上げ ChatGPTに挑戦 国産AI大型モデル8製品 GitUI v0.24.0をリリース GitのUbuntu 23.10のデフォルト壁紙Rustで書かれた端末が 明らかに 迷路の中の「Tauren」 JetBrainsが 中国で WebStorm 2023.3ロードマップを発表Human Java Ecosystem、Solon v2.5.3リリース
{{名前}}
{{名前}}

おすすめ

転載: my.oschina.net/yunqi/blog/10108651