0.はじめに
0.1 今年を振り返って
この記事はFujitsu Advent Calendar 2023 の24日目の記事です。
なお、本記事は個人の意見に基づくものであり、組織を代表するものではありません。
今年もAdvent Calendarの季節となりましたね。皆様お変わりないでしょうか? 私の方はというと、全体的に多忙ではありましたが、夏には国際学会IEEE NVMSAでの発表、秋から年末にかけて海外出張やOpen Source Summit Japanでの講演など、今年後半は怒涛のように仕事がくる状態になってました。おまけに 「Linus Torvaldsと会談せよ」 というミッションまで降ってきてしまい、正直どうなることかと思いました。人生、何が起こるかわからないものです。学生時代、英語は超苦手だったのに…。
0.2 今年の記事について
さて、例年このAdvent Calendarでは、不揮発メモリやCXLについて私はまとめてきたのですが、今年は前述のとおり、IEEE NVMSA 2023やOpen Source Summit Japan 2023などの国際カンファレンスなど色々な場でCXLについて発表する機会があり、新たにQiitaで記載できることはあまり多くはありません。
(とりあえず、OSSJの発表資料はこちらに置いておきます。)
このため、CXLそのものの動向や解説などは一旦おいておいて、今年はCXLのメモリプール1機能でも使うであろう、Linuxのメモリホットプラグのインタフェースについて改めて解説しておこうと思います。
このLinuxのメモリホットプラグという機能は、実はもう10年以上前から存在する機能です。私をはじめとして様々な人が2004年頃2から開発していて2007年頃にはkernelに取り込まれています。そのときに体験した苦労話について「七転び八起きのLinuxカーネルコミュニティ開発体験記」として日経の記事3にまとめたこともあります。このため、私にとっては非常に思い入れのある機能です。
更に、現在でもコミュニティで機能強化されていて、ドキュメントも以下のようにきちんとメンテナンスされています。最初にこのファイルを作ったのは恐らく私なのですが、いろいろあったのか、私の名前は空行にしか残っていないという状態です。
ac3332c44767b Documentation/admin-guide/mm/memory-hotplug.rst (David Hildenbrand 2021-09-07 19:54:49 -0700 1) ==================
ac3332c44767b Documentation/admin-guide/mm/memory-hotplug.rst (David Hildenbrand 2021-09-07 19:54:49 -0700 2) Memory Hot(Un)Plug
ac3332c44767b Documentation/admin-guide/mm/memory-hotplug.rst (David Hildenbrand 2021-09-07 19:54:49 -0700 3) ==================
6867c9310d5da Documentation/memory-hotplug.txt (Yasunori Goto 2007-08-10 13:00:59 -0700 4)
ac3332c44767b Documentation/admin-guide/mm/memory-hotplug.rst (David Hildenbrand 2021-09-07 19:54:49 -0700 5) This document describes generic Linux support for memory hot(un)plug with
ac3332c44767b Documentation/admin-guide/mm/memory-hotplug.rst (David Hildenbrand 2021-09-07 19:54:49 -0700 6) a focus on System RAM, including ZONE_MOVABLE support.
6867c9310d5da Documentation/memory-hotplug.txt (Yasunori Goto 2007-08-10 13:00:59 -0700 7)
6bf53999a3a26 Documentation/admin-guide/mm/memory-hotplug.rst (Mike Rapoport 2018-10-05 01:11:00 +0300 8) .. contents:: :local:
6bf53999a3a26 Documentation/admin-guide/mm/memory-hotplug.rst (Mike Rapoport 2018-10-05 01:11:00 +0300 9)
ac3332c44767b Documentation/admin-guide/mm/memory-hotplug.rst (David Hildenbrand 2021-09-07 19:54:49 -0700 10) Introduction
ac3332c44767b Documentation/admin-guide/mm/memory-hotplug.rst (David Hildenbrand 2021-09-07 19:54:49 -0700 11) ============
c18c1cce0c1a0 Documentation/memory-hotplug.txt (Mauro Carvalho Chehab 2017-05-14 22:52:22 -0300 12)
ac3332c44767b Documentation/admin-guide/mm/memory-hotplug.rst (David Hildenbrand 2021-09-07 19:54:49 -0700 13) Memory hot(un)plug allows f
しかしながら、そのわりにはこの機能が 一般の人にはこの機能があまり知られていない ということに最近気が付きました。memory hotplugを使えば簡単に実験できることを、その機能の存在自体を知らなかったため、研究職の方でもこれを使わずに苦労して実験いたことを耳にしたぐらいです。さすがにこれは良くないと感じたというのが今年の記事を書く気になった動機です。
それでははじめましょう。
1. 想定とする読者やハードウェアについて
1.1 対象読者
今から述べるインタフェースは、どちらかというとある程度kernelについて知識のある人向けのインタフェースです。例えば以下のような方です。
- この機能を使うシステム、ユーザプロセスやコマンドなどの開発者
- この機能でシミュレーションなど実験を行うことができる研究・開発者
これらのインタフェースはエンドユーザ向けのものとはなっていません。メモリホットプラグに対応したハードウェアのエンドユーザの方は、そのハードウェアに添付されたエンドユーザ向けの案内やコマンドなどがあるかもしれません。ご確認の上、そちらに従ってください。
1.2 対応ハードウェア
意外と知られていませんが、これらのメモリホットプラグの機能の一部は、実は一般的なサーバやデスクトップマシン、VMゲストなどでも試すことは可能 です。
テストなどで、一時的にメモリの容量を減らしたいとか、システム内のメモリ構成を変更したい時などにこのメモリホットプラグの機能を使うことができます。本記事では、一般的なPCで使い方を試す方法を中心にご紹介します。
もちろん実際にmemoryがhotplugできる対応ハードがあった方や、より実践的な操作ができますが、上記のような身近な環境でも使えることは覚えておいてください。
また、CXLでの操作についても簡単にご紹介します。ただ実際には、この機能の完成度が上がってくるのはこれからになると思われるので、新しい情報が今後出てくるかもしれません。(私もこの辺りはまだ十分に試せていません)。
本記事の実行例における動作環境はディストリビューションはubuntu 20.04LTSですが、RedHat系列のOSでも大きくは変わらないはずです。また、ハードは20GiBのメモリを搭載した筆者のNote PCで実施しています。
2. メモリホットプラグの大まかなシーケンス
大きく分けて以下の2段階になっています。
Memory Hotaddの場合
- ハード的にメモリを認識(a)
この段階では、追加されたメモリに物理アドレスが割り当てられた状態です。しかしOSのメモリ管理機構がこの領域を管理するためのデータ構造を初期化していない状態です。したがって、OSやユーザープロセスからはこのメモリはまだ利用することができません。 - OSへメモリを組み込み、kernel/driverやプロセスに利用可能にさせる(b)
OSのメモリ管理機構が上記の初期化を終えた状態です。この段階を踏まえることで、OS自身やユーザープロセスから、追加されたメモリを利用することが可能となります。
Memory Hotremoveの場合
-
OSの管理下からメモリを解放し、プロセスから利用できないようにする(b)
上記とは逆に、OSの制御からメモリを解放する段階です。プロセスが利用中の場合、可能であればmemory migration(後述)の力で他のメモリ領域にこっそりデータを移動します。その後、メモリ管理機構からこの領域を解放します。CPUからはまだこのメモリの物理アドレスは認識できていますが、OSからは切り離されたため、このメモリを利用することはできなくなります。 -
ハード的にメモリを切り離し(a)
ハード・ファームウェアにこの領域の返却を通知します。これにより、CPUから当該領域の物理アドレスにはアクセスできなくなります。
上記を分類しなおすと、メモリホットプラグの操作は以下の2段階に分かれていることになります。
操作a. ハード・ファーム的なつけ外しの段階
操作b. OSへの組み込み・切り離し
先にbについて解説しますが、bはOSの仕組みとして動作し、ハード・ファームは介入しません。この操作が前述した、 特殊なハード・ファームは必要ではなく一般的なPCやサーバー、VMゲストなど一般的な環境でも動作する 箇所になります。
一方、aについてはハード・ファームウェアの力でメモリをつけ外しする機能なので、基本的にはプラットフォームがメモリーホットプラグをサポートしていないと動作できません。また、そのしくみに対応するためのドライバ(PCIeのhotplugのドライバや、ACPIのhotplugのドライバなど)が必要です。
ただし本記事では、HotAddについてちょっとした設定や操作によって、それに近い体験ができる方法を紹介します。
筆者は試したことはありませんが、QEMUの操作でもmemory hotplugの操作が可能なようです。本記事内では使いませんが、その方法が記載されたドキュメントについては後述します。
なお、このa.ハード・ファーム的なつけ外しについては、CXLでは以下の2通りの仕組みが仕様上定義されています。(本記事ではこの仕様についてはあまり触れません)
- PCIeのhotplugの仕様に基づいた、デバイス単位のhotplug
- CXL 3.0以降で導入されたDynamic Capacity Deviceによる、メモリデバイスの一部をあり当てる形のhotplug
3. メモリホットプラグのインタフェース
では、インタフェースを少しずつ触って体験してみましょう。
3.1 OSへの組み込み・切り離しの基本的な操作
まず、身近に確認できる「b.OSへの組み込み・切り離し」について、そのインタフェースを見ていきましょう。以下のようなsysfsのディレクトリの配下にmemory hotplug関係のファイルやディレクトリが配置されてます。これがインタフェースとなります。
これ以降の操作はroot権限で行っていることに注意してください
# pwd
/sys/devices/system/memory
# ls
ls
auto_online_blocks memory114 memory133 memory152 memory171 memory47 memory66 memory85
block_size_bytes memory115 memory134 memory153 memory172 memory48 memory67 memory86
hard_offline_page memory116 memory135 memory154 memory18 memory49 memory68 memory87
memory0 memory117 memory136 memory155 memory2 memory5 memory69 memory88
memory1 memory118 memory137 memory156 memory3 memory50 memory7 memory89
(中略)
memory109 memory128 memory147 memory166 memory41 memory60 memory8 memory99
memory11 memory129 memory148 memory167 memory42 memory61 memory80 power
memory110 memory13 memory149 memory168 memory43 memory62 memory81 probe
memory111 memory130 memory15 memory169 memory44 memory63 memory82 soft_offline_page
memory112 memory131 memory150 memory17 memory45 memory64 memory83 uevent
memory113 memory132 memory151 memory170 memory46 memory65 memory84
まずは、上記のうち以下のファイルの内容を出力してみます。
# cat block_size_bytes
8000000
これは、メモリホットプラグの(b)の操作をどれくらいの単位で行うかの表示です。以後、この単位を便宜上、メモリブロックと記述します。この環境では0x8000000と表示されているわけですから1つのメモリブロックのサイズが128MiByteとなっていることがわかります。このサイズは基本的にアーキテクチャごとに異なり、 基本的には 変更不可能な固定値です。
筆者がmemory hotplugの開発に関わっていた頃、言い換えるとLinuxのmemory hotplug機能が初めて搭載された頃はx86-64はこの128MiB固定、ia64では1GiB、powerpcでは(筆者の記憶では)16MiB固定のサイズとなっていました。この値は固定値のほうが、以下のような理由でいろいろな面で都合がよかったためです。
- kernel内部の実装がシンプルになり、高速な処理が可能となるため都合がよいこと
- powerpcではプラットフォームの力で、16MiB単位という小さな単位でmemory hotplugが出来ること
- ia64ではもっと大きな単位でメモリがhotplugされ、またメモリの最大容量が当時はx86-64よりもかなり大きかったこと
しかし、現在では当時とは状況がかわってきており、x86-64サーバのメモリ搭載量もずいぶん大きくなってきました。このため、今ではkernelが起動時にメモリブロックサイズとしてどれくらいのサイズがちょうど良いかをある程度判定して決めるという形になっています。搭載されているメモリ量など様々な状況によって適切なサイズが変わってくるためです。
例えば、筆者の別の環境ではこのサイズが2GiBのサイズになっています。
# cat block_size_bytes
80000000
x86_64では以下の判定箇所で決めています。興味が有る人は調べてみてください。
arch/x86/mm/init_64.c
static unsigned long probe_memory_block_size(void)
{
unsigned long boot_mem_end = max_pfn << PAGE_SHIFT;
unsigned long bz;
/* If memory block size has been set, then use it */
bz = set_memory_block_size;
if (bz)
goto done;
/* Use regular block if RAM is smaller than MEM_SIZE_FOR_LARGE_BLOCK */
if (boot_mem_end < MEM_SIZE_FOR_LARGE_BLOCK) {
bz = MIN_MEMORY_BLOCK_SIZE;
goto done;
}
(以下略)
さて、/sys/devices/system/memory
のディレクトリには、memory###
の数字を持つディレクトリがありますが、この###
の数字の部分は、物理アドレスに対して、このblock_size_bytes
の値を割った値となっています。つまり、memory0のディレクトリは物理アドレス0x0の位置のメモリから128MiByteのサイズの領域を、memory8のディレクトリは 8 x 128MiB = 1024MiBから128MiBのサイズの物理アドレスの領域を管理するディレクトリとなっているわけです。
それでは、例としてmemory160のディレクトリの中身を見てみましょう。160*128MiB=20GiBなので、物理アドレスとして20GiBの物理アドレスから128MiByteのサイズのメモリブロック領域を管理するためのディレクトリです4。
# pwd
/sys/devices/system/memory/memory160
# ls -al
合計 0
drwxr-xr-x 3 root root 0 12月 23 11:14 .
drwxr-xr-x 163 root root 0 12月 23 11:14 ..
lrwxrwxrwx 1 root root 0 12月 23 11:27 node0 -> ../../node/node0
-rw-r--r-- 1 root root 4096 12月 23 11:27 online
-r--r--r-- 1 root root 4096 12月 23 11:27 phys_device
-r--r--r-- 1 root root 4096 12月 23 11:27 phys_index
drwxr-xr-x 2 root root 0 12月 23 11:27 power
-r--r--r-- 1 root root 4096 12月 23 11:27 removable
-rw-r--r-- 1 root root 4096 12月 23 11:27 state
lrwxrwxrwx 1 root root 0 12月 23 11:14 subsystem -> ../../../../bus/memory
-rw-r--r-- 1 root root 4096 12月 23 11:14 uevent
-r--r--r-- 1 root root 4096 12月 23 11:27 valid_zones
メモリホットプラグにおいて、もっとも重要なファイルがこの中のstateというファイルです。まずはこの値を表示してみましょう。
# cat state
online
これは、この160*128MiB=20GiBの物理アドレスから始まる128MiBのメモリ領域がonline、すなわちOSによって管理されていて、プロセスにメモリが利用できる状態になっていることを示します。それでは、(b)の操作、すなわちこの領域に対してOSの切り離し操作をしてみましょう。
# echo offline > state
# cat state
offline
このように、stateのファイルに対してofflineの文字列を書き込むことにより切り離しの操作を行うことができます。ユーザープロセスがこの物理アドレスのメモリを使っていた場合は、この時にmemory migrationの機能によってほかのアドレスに移動されています。
それでは元に戻してみましょう。
# echo online > state
# cat state
online
これで、再びOSに組み込まれて、この領域が利用できる状態になりました。
これが、(b)OSへの組み込み、切り離しの最も基本的な操作になります。
3.2 切り離しを確実にするために
3.2.1 メモリが抜けなくなる要因
実は、offlineの操作は失敗することがあります。試しにmemory0のメモリブロックをofflineにしてみましょう。
# pwd
/sys/devices/system/memory/memory0
# echo online > state
-bash: echo: 書き込みエラー: 無効な引数です
# cat state
online
エラーになってしまいました。状態もonlineのままですね。これはmemoryが以下のような条件などにより、memory migrationが動作できず、offlineできない状態になっていることが原因です。
カーネルやドライバがその領域を利用している
一般的にユーザープロセスが使っているメモリについては、kernelは図に示すようなmemory migrationという機能を使って、hotplugで抜こうとしている領域からほかの場所に移動させます。これは仮想アドレスはそのままにしつつ、データが保持されているメモリの物理アドレスを変えるという機能です。
ですが、カーネルやドライバのメモリ空間はこの機能が使えません。カーネルやドライバが使うメモリの仮想アドレスは物理アドレス+固定値(PAGE_OFFSET)という固定値で決められていて、物理アドレスをこっそりかえるということができない場合があります。また、そうでなくても割り込み禁止で動作している場合など、migraitionの時間が待てないというケースもあります。このため、これらが使っているメモリブロックをofflineしようとするとエラーになってしまうのです
ユーザプロセスのメモリが動かされないように、ドライバなどが固定している
速度を優先して、kernelをスキップして直接プロセスが使っているメモリにDMA/RDMAなどの手段でデータ転送中の場合、memory migrationを行うことはできません。物理アドレスを移動しても、転送中のハードウェアを止めることができないからです。
また、VMゲストが速度を優先してメモリを固定する場合もあるようです。このような場合もmigrationを行うことはできません。
その他
Linux Kernelのソースコードの中の ドキュメントDocumentation/admin-guide/mm/memory-hotplug.rst
には、他にもメモリが抜けない状態になるような原因について、この記事よりくわしく書いてあります。興味があるかたは参照してみてください。(私もこの記事を書くにあたって初めて知ったものもあります)。
3.2.2 ZONE_MOVABLE
このような抜けないケースをできるだけ避けるため、LinuxではZONE_MOVABLEという領域を定義することができます。ZONE_MOVABLEはkernelやドライバが使うことができない領域です。
また、ユーザプロセスのメモリを動かないようにを固定する場合、利用者が適切なパラメタ(FOLL_LONGTERM)を指定していれば、固定する前にZONE_MOVABLEの外にmigrateしてから固定するように動作します。
この領域はデフォルトでは存在しませんが、kernelのブートオプションを指定することで作成できます。kernelのソースコードに付属しているドキュメントには、その指定方法が書かれています。
$ pwd
/Documentation/admin-guide
$ less kernel-parameters.txt
:
:
kernelcore= [KNL,X86,IA-64,PPC]
Format: nn[KMGTPE] | nn% | "mirror"
This parameter specifies the amount of memory usable by
the kernel for non-movable allocations. The requested
(中略)
:
movablecore= [KNL,X86,IA-64,PPC]
Format: nn[KMGTPE] | nn%
This parameter is the complement to kernelcore=, it
specifies the amount of memory used for migratable
allocations. If both kernelcore and movablecore is
(後略)
:
movablecoreはZONE_MOVABLEの領域の量を指定する方法です。kernelcoreはZONE_MOVABLE 以外 の領域の量を指定する方法です。通常はどちらかを使えば十分です。具体的な容量を指定するか、あるいは全体の容量に対する比率を%で指定します。
この指定をするということは、kernelやドライバが使えるメモリの量が通常より少なくなるということです。どれくらいの容量を指定すればよいかについては条件によって異なります。本番環境などで使いたいという場合には、この値の量の指定には本番移行前までに検証を行うなど、十分注意して使うようにしてください。
なお、ここの記述でkernelcoreにはmirrorという指定がありますが、これは特殊なプラットフォームのための指定であり、通常はまず使うことがないオプションです。この文の注釈でどういうオプションなのかを補足しておきます5。
では、このブートオプションの効果を確認してみましょう。
まず、これを指定する前のメモリの状態を確認して見ましょう。例としてmemory block 100の値を確認します。
(再起動前)
# pwd
/sys/devices/system/memory/memory100
# cat valid_zones
Normal
ではこのブートオプションを指定してシステムを起動してみましょう。50%のメモリをkernelやドライバが利用できる領域とし、 それ以外の領域 を ZONE_MOVABLE の領域として設定します。
(再起動後)
# cat /proc/cmdline
BOOT_IMAGE=/vmlinuz-5.15.0-91-generic root=(中略) kernelcore=50%
# pwd
/sys/devices/system/memory/memory100
# cat valid_zones
Movable
memory100のブロックがZONE_MOVABLEに代わったことが分かります。このような設定をすることで、memoryを抜きやすくすることが可能です。
また、メモリブロックをonlineするときに、ZONE_MOVABLEにするか、それ以外の通常の領域(ZONE_NORMALなど)にするかも指定可能です。この環境において、Normalだったメモリブロックで、たまたまofflineにできるメモリブロックがありました。これをZONE_MOVABLEに変えてみましょう。以下のように操作します。
# pwd
/sys/devices/system/memory/memory85
# cat state
online
# cat valid_zones
Normal <------------------------------- この時点ではZONE_NORMALの領域です
# echo offline > state
# cat state
offline <------------------------------- 一旦offlineにすることが出来ました。
# echo online_movable > state <--------- onlineする領域をZONE_MOVABLEにすることを指定します
# cat state
online
# cat valid_zones
Movable <------------------------------ MOVABLE_ZONEの領域になりました
逆にZONE_NORMAL、すなわちkernelやドライバが使えるメモリ領域にしたい場合はonline_kernel
を指定してstateに書き込むと良いです。
4. ハード・ファーム的な取付、つけ外しについて
4.1 手動での操作
基本的にはハード・ファーム的なつけ外しはメモリホットプラグに対応したプラットフォームでないと実行できないと述べましたが、以下の条件が揃っている場合、取り付けすなわちhotaddについてエミュレーション的な動作をさせることが出来ます。
- アーキテクチャがこのエミュレーションに対応していること(kernel-6.7-rc5の時点ではx86, powerpc, sh, LoogArchが対応しているようです)
- CONFIG_ARCH_MEMORY_PROBEがkernelコンパイル時にonになっていること。
では、このエミュレーションをちょっと実験してみましょう。20GiBあるマシンに対して、mem=
のブートオプションによって、システム起動時にkernelにはメモリの半分の10GiBのみを認識させるオプションを追加し、残りはkernelが認識していない状態としています。
なお、ここでmemhp_default_state=offline
という指定も追加していますが、これについては後で説明します。
# cat /proc/cmdline
BOOT_IMAGE=/boot/vmlinuz-6.2.0-39-generic root=(中略) kernelcore=50% mem=10G memhp_default_state=offline
# pwd
/sys/devices/system/memory
# ls
auto_online_blocks memory18 memory41 memory53 memory65 memory77
block_size_bytes memory2 memory42 memory54 memory66 memory78
hard_offline_page memory3 memory43 memory55 memory67 memory79
memory0 memory32 memory44 memory56 memory68 memory8
memory1 memory33 memory45 memory57 memory69 memory9
memory10 memory34 memory46 memory58 memory7 power
memory11 memory35 memory47 memory59 memory70 probe
memory12 memory36 memory48 memory6 memory71 soft_offline_page
memory13 memory37 memory49 memory60 memory72 uevent
memory14 memory38 memory5 memory61 memory73
memory15 memory39 memory50 memory62 memory74
memory16 memory4 memory51 memory63 memory75
memory17 memory40 memory52 memory64 memory76
ここで80番目のメモリを認識させるには以下のように、そのメモリのブロック番号に対応する物理アドレスを指定します。block_size_bytesが128MiBの環境なので、128MiB*80=0x280000000の物理アドレスを指定します。
# echo 0x280000000 > probe <------ 80番目のブロックを認識させます。
# ls
auto_online_blocks memory18 memory41 memory53 memory65 memory77
block_size_bytes memory2 memory42 memory54 memory66 memory78
hard_offline_page memory3 memory43 memory55 memory67 memory79
memory0 memory32 memory44 memory56 memory68 memory8
memory1 memory33 memory45 memory57 memory69 memory80 <--- 指定した領域のメモリが認識され、ディレクトリが追加された!
memory10 memory34 memory46 memory58 memory7 memory9
memory11 memory35 memory47 memory59 memory70 power
memory12 memory36 memory48 memory6 memory71 probe
memory13 memory37 memory49 memory60 memory72 soft_offline_page
memory14 memory38 memory5 memory61 memory73 uevent
memory15 memory39 memory50 memory62 memory74
memory16 memory4 memory51 memory63 memory75
memory17 memory40 memory52 memory64 memory76
# cd memory80
# cat state
offline <--- まだoffline状態のままですが、これは後で説明します
上記のようにmemory80のメモリブロックが認識され、ディレクトリが追加されました。このようにハード・ファームがメモリをhotaddで追加したときには、このメモリブロックのディレクトリが新たに追加されるという形になります。上記のprobeによる操作は、これをエミュレートするような動作というわけですね。
4.2 CXLでの操作
CXLではcxlコマンドによって、デバイスを認識させることができます。
なお、CXLコマンドのrepositoryは不揮発メモリの管理コマンドであるndctlと同じ場所 ( https://github.com/pmem/ndctl ) にあります。
また、コマンドの解説も( https://docs.pmem.io/ndctl-user-guide/cxl-man-pages/cxl-enable-memdev-1 )6とpmem.ioに置いてあり、不揮発メモリのときの開発者がそのままCXLの開発へ移行した名残がのこっています。
解説によるとCXLデバイスのデバイスの認識、取り外しは以下のコマンドで実行できるようです。
cxl enable-memdev <memory device>
cxl disable-memdev <memory device>
なお、CXLではデバイスを取り付けただけではだめで、仕様上regionやinterleaveなどの設定などを行う必要があります。interleaveの設定方法などについては、私もまだ勉強中なのですが、CXLではデバイスの認識のあとでonline操作の前に必要な設定や、offline後で取り外し前にも操作が必要なことがわかれば、とりあえずは充分です。
本記事ではregionの設定( https://manpages.ubuntu.com/manpages/lunar/man1/cxl-enable-region.1.html )についてだけ示しておきます。
cxl enable-region <region> [<options>]
cxl disable-region <region> [<options>]
また、筆者はまだ試していないのですが、QEMUによるCXLエミュレーションの開発も盛んに行なわれています。以下の手順でCXLのエミュレーション環境を試すことができるようなので、興味の有る人は挑戦してみてください。
4.3 QEMUでの操作
QEMUでもmemory hotplugについてドキュメントが書かれており、この配下のゲストへのmemory hotplugの操作ができるようです。こちらも参考になるでしょう。
https://github.com/qemu/qemu/blob/master/docs/memory-hotplug.txt
5. aとbの動作の連動について
5.1 aとbとの連動とは
現在のmemory hotplugのデフォルトの動作では、aのハードウェアの認識後、自動的にb.すなわちkernelがonlineするようになっています。しかし、かつては自動ではなく一旦kernelからユーザランドのエージェントに通知して、エージェント側からonlineするような動作になっていました。そうすると、そもそもなぜこのような2段階に分かれていたのでしょうか?
正直言うと、なぜこうなったか?という点については私もだいぶ記憶から薄れているのですが、理由の一つとして以下のようなものがあったと記憶しています。
世の中には、メモリだけでなく、次の図のように複数のCPUとDIMMが複数刺さったマザーボードのような基盤がまるごとhotplugできるケースがあります。
一般的に、hotplugされたデバイスに対してudevと呼ばれる仕組みでユーザランドのエージェントなどが起動し、hotplugされたデバイスを識別して特定のデバイス名をつけたりすることに使われています。
udevについてはdebianの解説などが参考になるので、そちらをご覧ください。
このように、基盤ごとhotplugされる場合、例えばhotaddの動作は以下のようになります。
- aの動作として、ドライバ経由で基板上のCPUとメモリがすべて認識される。
- ホットプラグされる単位として、その基盤まるごと1つがhotplug eventとしてudev経由で通知される。(この通知はCPUやメモリごとではなく、基盤丸ごと1つの通知となる)
- ユーザー空間のエージェントが通知に応じて動作する。エージェントは基板上に載っているCPUsやメモリブロックを判別する。
- 判別した基板上のCPU群とメモリブロックを、エージェントがすべてonlineにする。
このように、hotplugされるデバイス構成が複雑な場合、ユーザー空間で動作するエージェントからデバイス自身やその内部の構成を認識したうえで、システムに組み込む方が都合が良いというケースがあるわけです。
このため、memory hotplugが初めて開発されたときは、初期状態のメモリブロックはofflineとなっており、エージェントやベンダーが提供するコマンドからオンライン化するのがデフォルトとなっていました。
しかし、私がmemory hotplugの開発から離れた後、他にもmemory hotplugのユーザが増えるにつけ、a.のあとにb.がudev経由で行なわれなければならないというのは、「難しい」と言われるようになったようです。このため、現在ではa.のメモリの認識のあと直接b.のonline操作が連続で行なわれるのがデフォルトの動作となっています。
5.2 自動でonlineしたときのデフォルトのZONEの指定
このようにデフォルトでは、上記のようにメモリデバイスの認識後すぐにonlineするようになったので、sysfsからstateへの入力が不要となりましたが、逆にZONE_MOVABLEにするかそれ以外の領域にするかどうかを決めるチャンスがなくなってしまいました。このため、memhp_default_state
というkernelのbootオプションで、onlineしたときのデフォルトのZONEを決めることが出来るようになりました。
memhp_default_state
には以下の値を指定することができます。online_movableを指定すると、a.の操作でメモリが接続されると、そのままZONE_MOVABLEの領域としてb.のonline化の動作がkernel側で行われます。
memhp_default_state=online/offline/online_kernel/online_movable
4.1節ではこの値をofflineに指定しましたが、こうするとa.の動作の後は一旦offlineで待機する動作となります。なので、probeした直後の80番のメモリブロックは連動してonlineの動作をせず、offlineのままだったということです。あとからエージェント経由で動作させたいという場合は、この設定が役に立つでしょう。今後CXLによるリソースプールが実現されるときも、もしかすると役に立つかもしれません。
また、この値は、以下のsysfsのファイルでも変えることが出来ます。どう変わるか実験してみましょう。
# pwd
/sys/devices/system/memory
# cat auto_online_blocks
offline
# echo online_movable > auto_online_blocks
# cat auto_online_blocks
online_movable <------------------ 設定値が変わりました
# echo 0x288000000 > probe <------ 81番目のブロックを認識させます。
# cat memory81/state
online <------------------------ 81番めのブロックが一気にonlineにされたことがわかります。
root@racailum:/sys/devices/system/memory# cat memory81/valid_zones
Movable <------------------------ ZONE_MOVABLEとしてonlineされたことがわかります。
このように、sysfsの設定でもauto onlineの動作を変えることができます。
6. まとめ
メモリホットプラグの操作方法について最も重要な点を述べてきました。
ほかにも細かい話は色々あるのですが、ここまでの説明でもかなり長くなってしまったので、それについて興味のある方はkernelのソースコード内にある
- memory hotplugのドキュメント Documentation/admin-guide/mm/memory-hotplug.rst
- bootパラメータの解説 Documentation/admin-guide/kernel-parameters.txt
なども参照してください。
それでは良いお年を!
-
メモリプールとはあるサーバーのメモリを抜いて、他のサーバにメモリを追加するような仕組みです。つけ外しの際にOSをshutdownさせておくのが確実ですが、片方のサーバーからmemory hotremoveの機能でメモリを削除して、そのメモリを他のサーバにmemory hotaddすることで、shutdownに伴うダウンタイムなしに資源の移動が可能となります。 ↩
-
私が開発に着手したのは2003年頃なので、それを含めるともう20年経過したわけですね…。 ↩
-
当時のkernel内のデータ構造の問題や、それをどう克服したかの技術的な内容については、こちらの記事のほうがくわしいです。 ↩
-
搭載メモリが20GiBのはずなのに20GiB以上の物理アドレスがあることに不思議に思う人がいるかもしれません。しかし、x86-64のアーキでは物理アドレスの途中に、メモリではなくデバイスのために割り当てられている領域があったりします。そういう領域をスキップしてメモリのアドレスが割り当てられるため、物理アドレスとしては20GiBより少しはみ出たところまでが割り当てられます。実際このマシンでは21.5GiBぐらいまでが物理アドレスとして割り当てられていました。このような物理アドレスのマップ情報はdmesg中に出力されています。 ↩
-
一部のエンタープライズ向けサーバにはDDR DRAMのメモリの一部をmirroringして冗長化することができるものがあるのですが、その領域をkernelが使えるようにし、それ以外の領域をZONE_MOVABLEにしてhotplug可能にするための指定です。(その場合、kernelはファームウェアからmirrorされた範囲をファームウェア、より正確にはEFI経由で教えてもらうことになります)。 ↩
-
2023/12/16日現在は、ちょっと記載が変になっていますね…。 ↩