- 物理機リージョン/AZ
- ハイパーバイザVM exit で捕まえる
- VMゲストOS
- コンテナnamespace / cgroup
- プロセスアプリ / DB今ここ
第10期の最終回です。カーネルが何を代わりにやっているかを見てきた最後に、メモリという土地の形を見ます。
まず結論
メモリを「どこを触っても同じ一枚の板」と見なすのは便利な近似であって、事実ではありません。触りに行くCPUと触られる場所の位置関係でアクセス時間が変わる構成があり(NUMA)、さらにCPU以外もメモリを読み書きします。
装置がメモリを読み書きする経路は、CPUの仮想メモリの仕組みを通りません。だからCPUのアドレスをそのまま渡せず、いま誰がそのバッファを触ってよいかを受け渡しで管理することになります。
ネットワークカードがパケットをメモリに書き込むとき、そのカードはCPUのプロセスが使っているのと同じ形式のアドレスを使っているでしょうか。
当てることが目的ではありません。外した予測ほど記憶に残るので、直感で選んでください。
平らなメモリは、特定の構成を前提にした仮定
プログラムを書いているとき、メモリは「どこを触っても同じ一枚の板」に見えます。便利な近似ですが、一次ソースを読むと平らなほうが前提付きの仮定だと分かります。numa(7) は Non-Uniform Memory Access をこう定義しています。
Non-Uniform Memory Access (NUMA) refers to multiprocessor systems whose memory is divided into multiple memory nodes. The access time of a memory node depends on the relative locations of the accessing CPU and the accessed node. (This contrasts with a symmetric multiprocessor system, where the access time for all of the memory is the same for all CPUs.)
注目すべきは括弧の中です。「すべてのCPUにとってすべてのメモリのアクセス時間が同じ」という状態が、対比として 名指しされています。平らであることは自明ではなく、対称型マルチプロセッサという構成の性質です。
第7週『キャッシュと記憶階層 — なぜ何段も重ねるのか』で、CPUから見た距離が段ごとに違うことを見ました。NUMA はその延長で、同じ主記憶の中にも距離の差があるという話です。
この環境では、NUMA の枠組みごと見えなかった
正直に書きます。本教材の検証環境では NUMA を実測できませんでした。 linuxkit カーネル 6.10.14(aarch64)で調べた結果です。
見たもの 結果lscpu NUMA node(s) の行が出ない/sys/devices/system/node/ No such file or directory/proc/self/numa_maps No such file or directorynumactl --hardware コマンド自体が存在しない
ノードが1個に見えたのではありません。NUMA の枠組みごと存在しない状態です。カーネルが CONFIG_NUMA を無効にしてビルドされていると、これらの入口が作られません。ただし同じ環境でも、次は出ます。
Node 0, zone DMA 877 614 270 187 92 49 23 9 1 0 0
Node 0, zone Normal 7206 8241 3878 2607 141 48 19 15 4 1 0
Node 0 と書かれていますが、この環境に NUMA はありません。カーネルは NUMA 非対応でも内部的に単一ノードの形で管理するため、この表示を「NUMA ノードが1つ見えた」証拠に使えません。 もう1つ、zone DMA はメモリ区画の名前で、これから話す転送機構の DMA とは別物です。
numa(7) が「アクセス時間はすべてのCPUにとってすべてのメモリで同じ」と書いているのは、どの構成についてですか。
もう一つの非一様性 — 触る主体が複数いる
距離の差とは別に、もう一つの非一様性があります。メモリを触るのはCPUだけではありません。
ここで第9週『仮想メモリ — なぜプロセスは互いのメモリを壊さないのか』とつながります。あの回で見たのは、CPUが出した仮想アドレスをページテーブルが物理アドレスへ変換する仕組みでした。装置がメモリを読み書きするとき、その変換は使われません。kernel.org の DMA API 文書は、関わるアドレスが3種類あると明示しています。
- ・原文: The driver can use virtual address X to access the buffer, but the device itself cannot because DMA doesn't go through the CPU virtual memory system.
- ・バスアドレスが CPU 物理アドレスと一致する系もあるが、一般には一致しない
核心の一文はこれです。
The driver can use virtual address X to access the buffer, but the device itself cannot because DMA doesn't go through the CPU virtual memory system.
DMA は CPU の仮想メモリシステムを通らない。 だからアドレス X を、そのまま装置に渡せません。
住所が違うと、次に「順番」の問題が出てくる
住所が2系統あるとは、同じバッファを2者が別々の入口から触るということです。すると、いつ触ってよいかを決める必要が出ます。dma-api-howto.rst は、同じ領域を繰り返し使い、転送の合間にデータを触る場合についてこう書いています。
If you need to use the same streaming DMA region multiple times and touch the data in between the DMA transfers, the buffer needs to be synced properly in order for the CPU and device to see the most up-to-date and correct copy of the DMA buffer.
そして手順が続きます。
Then, if you wish to let the device get at the DMA area again, finish accessing the data with the CPU, and then before actually giving the buffer to the hardware call either::
dma_sync_single_for_device(dev, dma_handle, size, direction);
CPU 側で触り終えてから、ハードウェアに渡す前に呼ぶ。逆向きは dma_sync_single_for_cpu() です。API 名の _for_cpu / _for_device が、いま誰のものかを表しています。
受け渡しが不要になる条件も明示されています。
If you don't touch the data from the first dma_map_() call till dma_unmap_(), then you don't have to call the dma_sync_*() routines at all.
map から unmap まで一切触らないなら sync は要らない。つまり sync が要るのは途中で持ち主が入れ替わる場合だけ です。速いか遅いかではなく誰が触ってよい時間帯かの話であることが、この条件の書かれ方から読み取れます。
ドライバが `dma_sync_single_for_cpu()` と `dma_sync_single_for_device()` を呼び分けるのは、何を管理するためですか。
CPU がメモリを読む(第9週の仕組み)
プロセスが出した仮想アドレスを、TLB とページテーブルが物理アドレスへ変換します。触る主体は CPU だけなので、所有権の受け渡しという概念が出てきません。
デバイスがメモリを読む(DMA)
装置が使うのはバスアドレスで、`DMA doesn't go through the CPU virtual memory system` とあるとおり CPU 側の変換経路は通りません。IOMMU のある系では、そこで DMA アドレスから物理アドレスへの変換が入ります。
同じ1枚のメモリを指しているのに、この2つは何が違うのでしょうか。
「アドレス」という語が、両者で同じものを指しているかを考えてみてください。
この章のまとめ
- 「メモリは平ら」は前提付きの仮定。numa(7) は、アクセス時間が触りに行くCPUと触られるノードの位置関係で決まる構成を NUMA と呼び、すべてのCPUで一様な SMP を対比として挙げている。
- 本教材の環境(linuxkit カーネル 6.10.14)では NUMA を確認できなかった。ノードが1つなのではなく、枠組みごと存在しない。
/proc/buddyinfoのNode 0は NUMA の証拠にならず、zone DMAはメモリ区画の名前で転送機構の DMA とは別物。- DMA は CPU の仮想メモリシステムを通らない。 だからデバイスに CPU 仮想アドレスをそのまま渡せず、バスアドレスという別系統の住所が要る。
- 触る主体が複数になると時間の区切りが要る。
dma_sync_*_for_cpu()/_for_device()は、その所有権の受け渡しをAPI名で明示している。 - なお「DMA は速いから使う」は、kernel.org の DMA API 文書2本を出典にはできない。書かれていない。
- 第10期では、プロセス・シグナル・ファイル記述子・ロック・永続化・名前と実体と、カーネルが代わりに引き受けている取り決めを見てきました。どの回も形は同じで、アプリから見える素直な姿は近似であり、その近似がどの前提で成り立つかを言えることが切り分けの出発点になります。
自分の現場で確かめる
読んだだけでは身につきません。手元の環境で実際に見てみてください。
理解度チェック
読んだ直後は「分かった」と感じやすいものです。実際に解いて確かめてください。
「DMA は CPU より速くメモリを転送できるから使う」という説明を、kernel.org の DMA API 文書(dma-api.rst / dma-api-howto.rst)を出典として提示されました。妥当ですか。
用語集(3語)
- NUMA (Non-Uniform Memory Access)
- メモリが複数のメモリノードに分割されたマルチプロセッサ構成。numa(7) は、あるノードへのアクセス時間が触りに行くCPUと触られるノードの相対的な位置関係で決まる、と定義している。
- バスアドレス
- I/O デバイスが使う第3の種類のアドレス。CPU物理アドレスと同一の系もあるが一般には同一でなく、IOMMU やホストブリッジが両者の間に任意の対応づけを作れる。
- メモリゾーン
- カーネルが物理メモリを用途別に区切った区画。/proc/buddyinfo に DMA・DMA32・Normal の名前で現れる。転送機構としての DMA とは別の概念。