2022年の不揮発メモリとLinux対応動向について 

この記事は、[Fujitsu Advent Calendar 2022](https://qiita.com/advent-calendar/2022/fujitsu)の25日目の記事です。

「え、まだこのネタで書くの？」と思う方もいらっしゃるかもしれませんが、ええ、今年もこのネタで書きます。


# 1. NVDIMMの終焉とCXLの台頭

## 1.1 Intel Optane Persistent Memory

これを読む皆さんはおそらくご承知のことでしょう。NVDIMMの最有力製品であった[Intel Optane Persisntent Memory](https://www.intel.co.jp/content/www/jp/ja/products/docs/memory-storage/optane-persistent-memory/overview.html)のビジネスは、残念ながら[終焉に向かう](https://www.intc.com/news-events/press-releases/detail/1563/intel-reports-second-quarter-2022-financial-results)ことが以下のように発表されました。

> In Q2 2022, we initiated the winding down of our Intel Optane memory business.

実は発表と同時にすぐに製品出荷を終了してしまったわけではありません。以下の記事から少し引用します。

https://www.school.ctc-g.co.jp/vmware/columns/noda/noda79.html

> 既存のOptane永続化メモリやOptane SSD製品群については、Optaneのユーザーを耐用年数の終了までサポートすることが約束されました。Intel製品の保証条件に変更はなく、通常の5年保証によって、この保証期間中はテクニカルサポートを受けられます。

> また、第4世代インテル®Xeonスケーラブルプロセッサー（コードネーム「Sapphire Rapids」）搭載のインテル®Optaneパーシステントメモリ（PMem）300シリーズ（コードネーム「Crow Pass」）についても開発を継続するとのことです。

Sapphire Rapidsの出荷は[2023年が予定](https://gazlog.com/entry/intel-sapphire-rapids-yieldrate/)されていますから、それに合わせて製品としてはもう少しサポートが続くといってよいでしょう。

とはいえ、IntelがOptane Persistent memoryをギブアップするのは早すぎたし、3DXpointもこのまま消えていくのは正直勿体ないと感じています。性能測定もしてみましたが、3D Xpointの性能自体は確かに他の不揮発デバイスよりも優秀でした。NVDIMMとしてはダメだったかもしれませんが、3D XpointがCXLの不揮発メモリデバイスとして復活を遂げてくれると個人的には嬉しいのですが、ちょっと厳しいだろうなぁ…。

それに、新たな技術が出たとしても、それに対するよいユースケースが見つかったり、使いこなされて普及されるようになるまでには非常に時間がかかるものです。Dockerが流行る前にもコンテナー技術はSolarisコンテナーなどずいぶん前から存在していたわけで、最も良いユースケースが見つかるまでは何年もかかっているはずです。

せめて、IntelはCXLベースの不揮発メモリの製品がリリースされた後に撤退の発表をしてくれたら、今少し悪い印象を防げたのではないかと思うのですが…。世の中、中々難しいものです。

## 1.2 進捗が進んできたCXL

さて一方で、今年は[Compute Express Link(CXL)](https://www.computeexpresslink.org/)は元気いっぱいでした。昨年の記事ではCXLにも不揮発メモリが搭載されることについて言及していましたが、そのCXLは2022/8/1には最新仕様として[3.0が公開](https://www.computeexpresslink.org/download-the-specification)されたうえ、同日にライバル仕様であった[Open CAPIを吸収した](https://www.globenewswire.com/news-release/2022/08/01/2489825/0/en/CXL-Consortium-and-OpenCAPI-Consortium-Sign-Letter-of-Intent-to-Transfer-OpenCAPI-Specifications-to-CXL.html)ことも発表されました。

そうすると残りのライバルは[CCIX](https://www.ccixconsortium.com/)なのですが、CCIXのプレスリリースは[2021年9月](https://www.ccixconsortium.com/ccix-base-specification-revision-1-1-version-1-0/)を最後に更新されていません。また、CCIXの主要メンバー企業はCXLにも二股をかけている状態なので、どっちが勝っても問題ない形になっています。こうなると、事実上CXLが勝ち残った[^intel]と考えても良いでしょう。

[^intel]: CPU以外のデバイスの販売に関してIntelはどうもイマイチぱっとしませんが、この手の仕様の標準化に関しては手堅いなと思うことがあります。 これまでPCIやPCI expressなどの仕様もIntelの影響が大きいものでした。また、何よりも優秀なLinux Kernelエンジニアを抱えていて、[Linuxに取り込まれている数](https://satoru-takeuchi.hatenablog.com/entry/2020/03/10/215417)も相当なものがあります。この多数の優秀なエンジニアによって、Linuxにおいてデファクトスタンダードを作り上げてしまう力が、私は見逃してはならないIntelの強みだと考えています。

昨年から今年にかけて、CXLの仕様について私も本格的に勉強を始めたのですが、その結果CXLについていくつか講演依頼をいただいたり、海外出張してCXL仕様について議論する機会を得たりもしました。そういう意味では私にとって2022年は充実した一年ともなりました。

CXLについて私が講演した資料は以下の通り公開していますので、詳しい説明はそちらに譲ります。ご覧になっていただけると幸いです。

- CXL2.0のまで仕様と、その仕様を理解するための基礎知識としてPCIやACPIについての簡単な解説
https://www.slideshare.net/ygotokernel/cxlpdf

- CXL3.0も含めた解説と、Linuxコミュニティの動向
https://www.slideshare.net/ygotokernel/nediasniacxlpdf

上記の資料のとおり、CXL3.0において共有メモリやFabricの定義が入ったことは個人的に意義が大きいと考えています。今後の分散システムの鍵を握るインフラとなることは間違いないでしょう。

また、調査した今年8月の時点ではまだコミュニティで実装中であったため、上記の資料ではシステム起動時のドライバが必要としていましたが、最新の[CXL開発サブツリー](https://git.kernel.org/pub/scm/linux/kernel/git/cxl/cxl.git)を参照すると、以下のようにACPIテーブル（ここではCFMWS)の読み取り(parse)の実装など、システム起動時に必要な処理がマージされてきていました。昨年よりも着実に開発が進捗していることがうかがえます。コードの解説はここではしませんが、興味がある人は調べてみると面白いかもしれません。

```c
drivers/cxl/acpi.cより抜粋
  :
  :
static int cxl_parse_cfmws(union acpi_subtable_headers *header, void *arg,
                           const unsigned long end)
{
        int target_map[CXL_DECODER_MAX_INTERLEAVE];
        struct cxl_cfmws_context *ctx = arg;
        struct cxl_port *root_port = ctx->root_port;
        struct resource *cxl_res = ctx->cxl_res;
        struct cxl_cxims_context cxims_ctx;
        struct cxl_root_decoder *cxlrd;
        struct device *dev = ctx->dev;
        struct acpi_cedt_cfmws *cfmws;
        cxl_calc_hb_fn cxl_calc_hb;
        struct cxl_decoder *cxld;
        unsigned int ways, i, ig;
        struct resource *res;
        int rc;

        cfmws = (struct acpi_cedt_cfmws *) header;

        rc = cxl_acpi_cfmws_verify(dev, cfmws);
        if (rc) {
                dev_err(dev, "CFMWS range %#llx-%#llx not registered\n",
                        cfmws->base_hpa,
                        cfmws->base_hpa + cfmws->window_size - 1);
                return 0;
        }

        rc = eiw_to_ways(cfmws->interleave_ways, &ways);
        if (rc)
                return rc;
        rc = eig_to_granularity(cfmws->granularity, &ig);
        if (rc)
                return rc;
        for (i = 0; i < ways; i++)
                target_map[i] = cfmws->interleave_targets[i];

        res = kzalloc(sizeof(*res), GFP_KERNEL);
        if (!res)
                return -ENOMEM;

        res->name = kasprintf(GFP_KERNEL, "CXL Window %d", ctx->id++);
        if (!res->name)
                goto err_name;

        res->start = cfmws->base_hpa;
        res->end = cfmws->base_hpa + cfmws->window_size - 1;
        res->flags = IORESOURCE_MEM;
  :
  :
```

# 2. 従来から取り組んできた開発の進捗状況

さて、ハードや仕様の話はこれくらいにして、私のチームでずっと続けてきたLinuxの不揮発メモリ対応の話をしましょう。なお、これらの機能はNVDIMMだけでなく、今後出てくると思われるCXLの不揮発メモリでも有効です。

## 2.1 Filesystem DAXの状況

まずはFilesystem DAXの状況について述べないといけません。

[2021年のLinux Plumbers Conference](https://lpc.events/event/11/page/103-lpc-2021-overview)において、[私やチームメンバーのRuanさんが報告した内容](https://lpc.events/event/11/contributions/894/attachments/751/1412/2021Sep23_The_Forefront_of_the_Development_for_NVDIMMM_on_Linux_kernel_Yasunori_Goto_Ruan_Shiyang.pdf)、特にXFSのreflink機能とFilesystem-DAXの間の問題点を解決するための必要なパッチが __おおむねkernel6.1に取り込まれました。__ このため、「これでようやくExperimentalとされていた状態も解除される」とを期待していたのですが、残念ながら開発コミュニティから新たに「warningメッセージが表示される」などのトラブルが報告されてしまいました。このため、 __もう少し障害修正に取り組まないといけなくなっている状態です。__

この中でも、コミュニティで一番問題視されている障害について一言でまとめると、
__Filesystem側のブロックの利用状況の状態変化__ を __メモリ管理機構が追い切れていない__
という問題だと言えます。

Filesystem DAXは「ストレージであると共にメモリでもある」ので、両方の間をうまく取り持つ必要があるのですが、ソフト側のFilesystem DAXへのアクセスパターンによっては、そのファイルシステム側の変化に対してメモリ側がついていけないパターンがあったということです。つまり考慮漏れですね[^filesystem_dax_problem]。

これらの問題については以下のパッチをはじめ、現在私のチームで修正に取り組んでいます。いくつかはメインラインにマージされてきていますが、まだ完全に問題が解決したわけではないので、今少し時間が必要な状態です。
https://lore.kernel.org/lkml/1669908538-55-1-git-send-email-ruansy.fnst@fujitsu.com/

[^filesystem_dax_problem]: 正直これは私にも責任があると思っていて、網羅性確認とかのチェックとか、再発防止の施策をしていかないといけないと思っています。ツライ…。

プロプラエタリな製品であれ、OSSであれ、システムを最後まで完成させるまでには __生みの苦しみ__ みたいなものがありますが、どうやらFilesystem DAXの開発も最終局面においてそのような状態になってきているようです。

## 2.2 不揮発メモリ向けRDMAの新仕様の状況

[昨年の記事](https://qiita.com/YasunoriGoto1/items/d30786d8560f4ece5e3a)では、InfinibandのRDMAに不揮発メモリ向けの新仕様、flushとatomic writeについて解説しましたが、皆さん覚えていらっしゃいますでしょうか？

それらの仕様は基本的にinfinibandのハードベンダーが対応するべき仕様であるため、、対応した製品およびそのドライバが提供されるまでは、我々は使うことができません。また、そのようハードを購入するというのは稟議など社内手続きを踏まえて買わないといけないとなると、利用するまでのハードルは高いと言えるでしょう。

そこで、SoftRoCEにこれらの新仕様をエミュレーションするということを私は昨年の段階で考えていて、今年はその機能の実装に取り組んでいました。これついては、以下のようにメインラインにドライバがマージされてきていて、時期的におそらく6.2-rc1には入りそうな感触です。

```
commit 124011e6e933bead5852c3f69b32dec43919fe1a
Author: Li Zhijian <lizhijian@fujitsu.com>
Date:   Tue Dec 6 21:02:01 2022 +0800

    RDMA/rxe: Enable RDMA FLUSH capability for rxe device

    Now we are ready to enable RDMA FLUSH capability for RXE.
    It can support Global Visibility and Persistence placement types.

    Link: https://lore.kernel.org/r/20221206130201.30986-11-lizhijian@fujitsu.com
    Reviewed-by: Zhu Yanjun <zyjzyj2000@gmail.com>
    Signed-off-by: Li Zhijian <lizhijian@fujitsu.com>
    Signed-off-by: Jason Gunthorpe <jgg@nvidia.com>
:
:
commit 4cd9f1d320f905e7bc60f030566d15003745ba91
Author: Xiao Yang <yangx.jy@fujitsu.com>
Date:   Thu Dec 1 14:39:28 2022 +0000

    RDMA/rxe: Enable atomic write capability for rxe device

    The capability shows that rxe device supports atomic write operation.

    Link: https://lore.kernel.org/r/1669905568-62-4-git-send-email-yangx.jy@fujitsu.com
    Signed-off-by: Xiao Yang <yangx.jy@fujitsu.com>
    Signed-off-by: Jason Gunthorpe <jgg@nvidia.com>
```

さらに、infinbandのインタフェースでSoftRoCEのこれらの新仕様対応の機能を利用するためには、この上位のライブラリ[libibverbs(rdma-core)](https://github.com/linux-rdma/rdma-core)パッケージの中の一つ)も修正する必要がありますが、そちらの方も現在対応が進んでいます。atomic writeについてはもうすでに[pull requestがマージ](https://github.com/linux-rdma/rdma-core/pull/1179)されており、flushについても[あともう少し](https://github.com/linux-rdma/rdma-core/pull/1181)といった状況です。

さらに不揮発メモリ向けのRDMAライブラリである[librpma](https://github.com/pmem/rpma)についても[新仕様対応を始めています](https://github.com/pmem/rpma/pull/2085)。

これらについては、今年は大きく前進したと言えるでしょう。

# 3. RDMAのOn Demand Paging機能

## 3.1 RDMAを使った時の課題

[昨年のAdvent Calendarの記事](https://qiita.com/YasunoriGoto1/items/d30786d8560f4ece5e3a)では不揮発メモリに対するRDMAの重要性や、不揮発メモリ向けの新仕様について解説しました。しかし、上記のLinux Plumbers Confrenceの発表の16pageにおいて、以下のように記載していたことに気が付いていた人はどれくらいいるでしょうか？ "On Demand Paging"がworkaroundになっているとしています。また、これについては[2020年のAdvent Calendar](https://qiita.com/YasunoriGoto1/items/d75b271596e86313ac5b)の記事でも取り上げていました。

![ODP_IS_needed.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/101351/ba3888d5-a783-6ff6-e558-9508a1749af0.png)

今回は、このOn Demand Pagingについてもう少し詳しく取り上げたいと思います。このため、まずはこのworkaroundが出るまでの背景についておさらいしましょう。

そもそも、これまで述べてきたように、Filesystem-DAXは不揮発メモリ上にファイルシステムを構築して、あるユーザプロセスがそのファイルをmmap()すると、そのファイルのブロックはそのユーザプロセスのメモリ領域として直接割り当てられ、その後はユーザプロセスがはカーネルやドライバを介さず直接不揮発メモリにread/writeする機能です。

さてここで「もしものケース」を考えてみましょう。

- あるプロセスが利用中のファイルに対して、
- 同時に隣でtruncate()やpunch holeなどの操作をユーザが行うことで、「そのブロックを使わない」という処理

が行われたらどうなるでしょうか？ 本当はまだデータ転送中のブロックがファイルシステムからは未使用扱いにされてしまいます。その結果、そのブロックに転送したはずのデータが無くなってしまったり、ファイルシステムがそのブロックを再利用することで、ユーザのデータ破壊や最悪ファイルシステムの破壊なども起こりえる...というわけです。

![RDMA_vs_truncate.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/101351/71e41ec6-505e-314d-f340-52f9f6708794.png)

もちろん普通はそんなことができないように、デバイスドライバが対象のブロックを固定して回避するようにしますが、それはそれで今度はファイルシステムとしてはいつまでもtruncate()やpunch holeといった操作ができないわけですから困ってしまいます。

特に、これまでInfinibandやVideo for linux(V4L2)のようにkernel/driverレイヤのバッファを経由するのではなく、ユーザーのプロセス領域に直接DMA/RDMAで書き込みを行うデバイスでは、そのような領域にいつまでもデータ転送が実行され続け、ユーザが良しというまで終わらないという問題があります。これを待つというのは非常に困難です[^ira_effort]。

ではどうすればよいのか？というところで、事実上最後の手段として挙げられているのがOn Demand pagingという機能というわけです。

[^ira_effort]: 実はIntelはRDMA対象となっている場合、truncate()やpunch holeのようなファイルのブロックを変更するような操作をリジェクトするという[layout leaseというインタフェースを提案](https://lore.kernel.org/linux-fsdevel/20190821190904.GD5965@iweiny-DESK2.sc.intel.com/T/)していました。しかし、コミュニティでは事実上提案がrejectされた状態になっています。使い方が難しいというのが主な理由ですが、より詳しくはDave Chinnerの[このあたりの意見を参照すると良いかもしれません](https://patchwork.kernel.org/project/linux-mm/cover/20190809225833.6657-1-ira.weiny@intel.com/#22838661)

## 3.2 On Demand Pagingとは何か

On Demand Pagingとは何かというと、もともとは「DMA/RDMAでデータ転送する領域に対して、kernelによってページ回収されることをデバイスが許す機能」といえるでしょう。
通常のハードウェアはDMAやRDMAでハードからメモリにデータ転送中の間に、その対象となっているメモリがLinuxのメモリ回収処理などによってSwap outされたりしないように、pageをpin downすなわちページ固定します。もしデータ転送中にkernelがそれを知らずにswap outしてしまわないようにする必要があるのです。
しかし、そういう領域があまりに大きいサイズだと、Linux Kernelから見ると「ページ回収ができない領域が無視できないぐらい大量に存在する」状態になります。これはシステム全体から見ると健全な状態とは言えません。このため、kernelがページ回収したらそのことをデバイス側で検知し、デバイスがもう一度その領域にデータ転送したい場合はデバイス側が起点となってpage faultの処理を起こすことができれば良い…という発想が出てきます。これがOn Demand pagingです。

On Demand Pagingの動作については以下の資料が分かりやすいので、この図を使って説明しましょう。
[On Demand Paging for User-level Networking](https://openfabrics.org/images/eventpresos/workshops2013/2013_Workshop_Tues_0930_liss_odp.pdf)

kernelのメモリ回収処理が動作したときのために、デバイスドライバーはmmu_notifierという機能のハンドラーをあらかじめ登録しておくことができます。実際に回収処理が走った(invalidation)時にこのハンドラーが呼ばれますが、ドライバ内部のメモリ領域の管理を更新するほか、デバイス側にも対象のページがpage回収処理によってメモリ上からなくなることを伝達します。

![ODP_Invalidation.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/101351/cce14dd5-7a96-93c3-355d-7bd41c41c5c4.png)

逆に今度はデバイス側から、回収されたページのデータにもう一度DMA/RDMAなどで転送したい場合は、デバイス側が起点となってpage faultの処理をキックすることができます。

![ODP_Page_in.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/101351/eada58fc-014a-98b8-819f-be79eea94094.png)

Infinibandといえば、現在はNVIDIAに買収されたMellanoxの製品が有名ですが、現在On Demand PagingをサポートしているハードウェアはこのNVIDIAの比較的新しめの製品だけです。

## 3.3 Filesystem DAXとOn Demand Paging

さて、このOn Demand Paging,ここまで読んで疑問に思った方もいるかもしれません。swap outするのはmalloc()で確保したメモリなど、ファイルに結び付けられていない所謂 __匿名ページ__ のはずで、Filesystem DAXは関係ないのでは？と考えるのは自然なことです。

実は、このpageを無効化するinvalidateのハンドラーはFilesystem DAXの場合でも有効で、前述のtruncate()やpunch holeなどの操作をユーザーが行った時に、これが呼び出されるようになっています。

このため、もしtruncate()やpunch holeなどで転送中のページが無くなる場合でも、このハンドラー経由でデバイス側が転送先が無くなったことを検知できます。逆に転送先にデバイスが新たにアクセスしたくなった場合には、デバイス手動でpage faultを起こして領域を再確保できるというわけです。

## 3.4 SoftRoCEでOn Demand Paging

さて、Mellanox(現NVIDIA)の機能はプロプラエタリドライバのほか、OSSとしてlinux kernelに付随したドライバもあるのですが、このドライバの素晴らしい所は、このOn Demand Pagingの機能を有しているのはMallanoxだけだったにもかかわらず、他のハードウェアでもOn Demand Pagingを搭載した場合にも対応できるように、ソースコード上共通化できる個所をきちんと切り分けていていることです。
Infinibandのドライバは以下のような構成になっていますが、このうち、core/umem_odp.cのところに、共通で使えるOn Demand Pagingのソースコードが分離されています。

```ascii art
drivers/inifinband/core -----------------> infinband 共通層のソースが入るディレクトリ
                  /core/umem_odp.c ------> 共通層のOn Demand Pagingのソースコード
                  /hw   -----------------> 各デバイスベンダー固有のドライバのディレクトリ
                  /hw/mlx5 --------------> Mellanoxの比較的新しいカードのドライバのディレクトリ
                  /hw/mlx5/odp.c --------> Mellanox固有のドライバのOn Demand Pagingのソースコード
                  /sw      --------------> SoftRoCEのように他の機能とソフト的に連携して機能を実現するためのディレクトリ
                  /sw/rxe  --------------> SoftRoCEのドライバのディレクトリ
```

この実装に気が付いた筆者は、 __「これ、共通化部分をうまく使えばSoftRoCEのドライバでもOn Demand Pagingの機能を実現できるんじゃね？」__ と思い立ちました。前述したように、SoftRoCEなら新しいハードウェアを購入するといった手間が無くても気軽に試せるからです。そこで大まかな設計方針を筆者の方で決めて、そのあとの具体的な論理設計からは筆者のチームのホープであるMatsudaさんに任せ、パッチを[コミュニティに投稿](https://lore.kernel.org/lkml/cover.1668157436.git.matsuda-daisuke@fujitsu.com/)してもらっています。

もっとも、現在の段階ではまだ機能的に不足しているところがあります。

- 前述の不揮発メモリ向け新仕様(flush, atomic write)に未対応
- page faultする分、性能的には遅くなることが考えられるので、それを緩和するためのprefecth機能の実装が必要となるが、今の段階では未実装
- etc.

可能であれば、上記についても今後対応していきたいと考えています。

なお、このパッチについて、RDMA全体を統括するメンテナーの一人からは、[「こんなクールな機能なのに（レビューがあまりされていなくて）進捗していなくて残念だ。誰かレビューしてくれないか？」](https://lore.kernel.org/lkml/Y3UmaJil5slosqjA@unreal/)といったコメントが出ています。実はSoftRoCEはレビューワーが慢性的に不足気味で、筆者も困っているところです。
丁度つい先日[v3版を投稿](https://lore.kernel.org/lkml/cover.1671772917.git.matsuda-daisuke@fujitsu.com/)したところでもあるので、もしよろしければSoftRoCEやこのパッチのレビューに参加していただける方がいらっしゃると、大変ありがたく思います。

# 4.まとめ

今年の状況をまとめた…というよりは筆者の周辺の状況をまとめた形になっていますが、いかがだったでしょうか？来年は本格的にCXL周りの開発をしたいと考えていますが、まだまだ検討中の段階です。引き続きLinux Kernelに貢献していきたいと思うのでよろしくお願いします。

それではよいお年を！