memmapカーネルパラメータで使用するメモリ領域を制限する

**この記事は続き物です、前半のお話は[こちら](https://qiita.com/dragoon2014/items/2b90ea539fbe8eb66e53)**

ここからはLinuxまわりのお話になります

前半の話を一行でまとめると、やむを得ずチャネルで非対称なメモリサイズのPC組んだよ、というお話です

```
（スロットを上から見た図）
(本来こうやりたいが)   　　(こうなってる、理由は前半のお話参照)
[ 32G ]                 [ 48G ]
[ 48G ]                 [ 48G ]
[ 32G ]                 [ 32G ]
[ 48G ]                 [ 32G ]
```

さてメモリをたっぷり搭載したPCができたわけですが、チャネルでサイズが違うために
早い領域と遅い領域ができてしまっています
これをごちゃまぜにして使うのは気持ち悪かったのでなんとかならないかとカーネルパラメータを調べてみたところ、
memmapパラメータなるものを発見しました

https://www.kernel.org/doc/html/v6.11/admin-guide/kernel-parameters.html

```
memmap=exactmap [KNL,X86,EARLY] Enable setting of an exact
                E820 memory map, as specified by the user.
                Such memmap=exactmap lines can be constructed based on
                BIOS output or other requirements. See the memmap=nn@ss
                option description.

memmap=nn[KMG]@ss[KMG]
                [KNL, X86,MIPS,XTENSA,EARLY] Force usage of a specific region of memory.
                Region of memory to be used is from ss to ss+nn.
                If @ss[KMG] is omitted, it is equivalent to mem=nn[KMG],
                which limits max address to nn[KMG].
                Multiple different regions can be specified,
                comma delimited.
                Example:
                        memmap=100M@2G,100M#3G,1G!1024G
```

まず現代的なOSはメモリをカーネルが預かり、ユーザースペースからの要求に応じて
適宜メモリの一部を割り当てていきます
Linuxの場合、起動時にカーネルがどこのメモリ領域を使えるのかといった情報を
起動ログ（journalctl等）から確認できます

起動直後にe820を含む行が連なって出てくるので見つけるのは容易です

```
kernel: BIOS-provided physical RAM map:
kernel: BIOS-e820: [mem 0x0000000000000000-0x000000000009ffff] usable
kernel: BIOS-e820: [mem 0x00000000000a0000-0x00000000000fffff] reserved
kernel: BIOS-e820: [mem 0x0000000000100000-0x0000000009e01fff] usable
kernel: BIOS-e820: [mem 0x0000000009e02000-0x0000000009ffffff] reserved
kernel: BIOS-e820: [mem 0x000000000a000000-0x000000000a1fffff] usable
kernel: BIOS-e820: [mem 0x000000000a200000-0x000000000a20ffff] ACPI NVS
kernel: BIOS-e820: [mem 0x000000000a210000-0x000000000affffff] usable
kernel: BIOS-e820: [mem 0x000000000b000000-0x000000000b020fff] reserved
kernel: BIOS-e820: [mem 0x000000000b021000-0x00000000a8292fff] usable
kernel: BIOS-e820: [mem 0x00000000a8293000-0x00000000ae292fff] reserved
kernel: BIOS-e820: [mem 0x00000000ae293000-0x00000000ae367fff] ACPI data
kernel: BIOS-e820: [mem 0x00000000ae368000-0x00000000b0367fff] ACPI NVS
kernel: BIOS-e820: [mem 0x00000000b0368000-0x00000000b847efff] reserved
kernel: BIOS-e820: [mem 0x00000000b847f000-0x00000000b85fefff] type 20
kernel: BIOS-e820: [mem 0x00000000b85ff000-0x00000000b9ff8fff] usable
kernel: BIOS-e820: [mem 0x00000000b9ff9000-0x00000000b9ffdfff] reserved
kernel: BIOS-e820: [mem 0x00000000b9ffe000-0x00000000b9ffffff] usable
kernel: BIOS-e820: [mem 0x00000000ba000000-0x00000000bbffffff] reserved
kernel: BIOS-e820: [mem 0x00000000bd7f3000-0x00000000bfffffff] reserved
kernel: BIOS-e820: [mem 0x00000000e0000000-0x00000000efffffff] reserved
kernel: BIOS-e820: [mem 0x00000000f7000000-0x00000000ffffffff] reserved
kernel: BIOS-e820: [mem 0x0000000100000000-0x000000243defffff] usable
kernel: BIOS-e820: [mem 0x000000243ef40000-0x00000028601fffff] reserved
kernel: BIOS-e820: [mem 0x000000fd00000000-0x000000ffffffffff] reserved
```

ここでusableになっている領域がありますがこの中の

```
kernel: BIOS-e820: [mem 0x0000000100000000-0x000000243defffff] usable
```

が一番大きいですね

```
>>> 0x000000243defffff/(1024**3)
144.96777343656868
```

なんかちょっと(実メモリ160G-VRAM16G=)144Gを超えてますが、この辺が使えるメモリの末尾の方で
その直後のreserved領域がVRAM領域と思われます

memmapパラメータを使うとこの領域の指定範囲を別の種類にマークできます
memmap=128G(memmap=128G@0の省略形)などとするとusable領域を0バイト目から128Gまでに制限できます

あとでまとめて貼りますが、memmapをパラメータを与えると
e820に続いてuser-defined physical RAM mapを追加で出してくれます

```
kernel: user: [mem 0x0000000100000000-0x0000001fffffffff] usable
```

これでusable領域をデュアルチャネルが効くであろう範囲に制限できました
単純に使用しなくするだけならこれでもいいのですが、
それはそれでもったいないと思いつつmanをよく見ると

```
        memmap=nn[KMG]!ss[KMG,EARLY]
                        [KNL,X86] Mark specific memory as protected.
                        Region of memory to be used, from ss to ss+nn.
                        The memory region may be marked as e820 type 12 (0xc)
                        and is NVDIMM or ADR memory.
```

なる記述があります

これはメモリ領域の一部をNVDIMM（不揮発性メモリ）とマークし、機能をエミュレートしてくれます
どう使うかはさておき、16GBも未使用のままとしておくのももったいないので
試しにこの領域に割り当ててみました
せっかくなので同量のメモリをさらに追加で割り当てみて、後ほど速度を見てみましょう

`memmap=112G,16G!112G,16G!128G`

```
kernel: BIOS-provided physical RAM map:
kernel: BIOS-e820: [mem 0x0000000000000000-0x000000000009ffff] usable
kernel: BIOS-e820: [mem 0x00000000000a0000-0x00000000000fffff] reserved
kernel: BIOS-e820: [mem 0x0000000000100000-0x0000000009e01fff] usable
kernel: BIOS-e820: [mem 0x0000000009e02000-0x0000000009ffffff] reserved
kernel: BIOS-e820: [mem 0x000000000a000000-0x000000000a1fffff] usable
kernel: BIOS-e820: [mem 0x000000000a200000-0x000000000a20ffff] ACPI NVS
kernel: BIOS-e820: [mem 0x000000000a210000-0x000000000affffff] usable
kernel: BIOS-e820: [mem 0x000000000b000000-0x000000000b020fff] reserved
kernel: BIOS-e820: [mem 0x000000000b021000-0x000000008738efff] usable
kernel: BIOS-e820: [mem 0x000000008738f000-0x000000008d38efff] reserved
kernel: BIOS-e820: [mem 0x000000008d38f000-0x000000008d47efff] ACPI data
kernel: BIOS-e820: [mem 0x000000008d47f000-0x000000008f47efff] ACPI NVS
kernel: BIOS-e820: [mem 0x000000008f47f000-0x000000009847efff] reserved
kernel: BIOS-e820: [mem 0x000000009847f000-0x00000000985fefff] type 20
kernel: BIOS-e820: [mem 0x00000000985ff000-0x0000000099ff8fff] usable
kernel: BIOS-e820: [mem 0x0000000099ff9000-0x0000000099ffbfff] reserved
kernel: BIOS-e820: [mem 0x0000000099ffc000-0x0000000099ffffff] usable
kernel: BIOS-e820: [mem 0x000000009a000000-0x000000009bffffff] reserved
kernel: BIOS-e820: [mem 0x000000009d7f3000-0x000000009fffffff] reserved
kernel: BIOS-e820: [mem 0x00000000e0000000-0x00000000efffffff] reserved
kernel: BIOS-e820: [mem 0x00000000f7000000-0x00000000ffffffff] reserved
kernel: BIOS-e820: [mem 0x0000000100000000-0x000000245defffff] usable
kernel: BIOS-e820: [mem 0x000000245ef40000-0x00000028801fffff] reserved
kernel: BIOS-e820: [mem 0x000000fd00000000-0x000000ffffffffff] reserved
kernel: e820: remove [mem 0x1c00000000-0xfffffffffffffffe] usable
kernel: NX (Execute Disable) protection: active
kernel: APIC: Static calls initialized
kernel: user-defined physical RAM map:
kernel: user: [mem 0x0000000000000000-0x000000000009ffff] usable
kernel: user: [mem 0x00000000000a0000-0x00000000000fffff] reserved
kernel: user: [mem 0x0000000000100000-0x0000000009e01fff] usable
kernel: user: [mem 0x0000000009e02000-0x0000000009ffffff] reserved
kernel: user: [mem 0x000000000a000000-0x000000000a1fffff] usable
kernel: user: [mem 0x000000000a200000-0x000000000a20ffff] ACPI NVS
kernel: user: [mem 0x000000000a210000-0x000000000affffff] usable
kernel: user: [mem 0x000000000b000000-0x000000000b020fff] reserved
kernel: user: [mem 0x000000000b021000-0x000000008738efff] usable
kernel: user: [mem 0x000000008738f000-0x000000008d38efff] reserved
kernel: user: [mem 0x000000008d38f000-0x000000008d47efff] ACPI data
kernel: user: [mem 0x000000008d47f000-0x000000008f47efff] ACPI NVS
kernel: user: [mem 0x000000008f47f000-0x000000009847efff] reserved
kernel: user: [mem 0x000000009847f000-0x00000000985fefff] type 20
kernel: user: [mem 0x00000000985ff000-0x0000000099ff8fff] usable
kernel: user: [mem 0x0000000099ff9000-0x0000000099ffbfff] reserved
kernel: user: [mem 0x0000000099ffc000-0x0000000099ffffff] usable
kernel: user: [mem 0x000000009a000000-0x000000009bffffff] reserved
kernel: user: [mem 0x000000009d7f3000-0x000000009fffffff] reserved
kernel: user: [mem 0x00000000e0000000-0x00000000efffffff] reserved
kernel: user: [mem 0x00000000f7000000-0x00000000ffffffff] reserved
kernel: user: [mem 0x0000000100000000-0x0000001bffffffff] usable
kernel: user: [mem 0x0000001c00000000-0x0000001fffffffff] persistent (type 12)
kernel: user: [mem 0x0000002000000000-0x00000023ffffffff] persistent (type 12)
kernel: user: [mem 0x000000245ef40000-0x00000028801fffff] reserved
kernel: user: [mem 0x000000fd00000000-0x000000ffffffffff] reserved
```

（細部が少し異なってますが最初のmapは大昔のjournalを引っ張り出してきたためHW構成がすこしずれてます）

多少ずれがありそうですが、おおよそデュアルチャネルな領域とシングルチャネルな領域がpersistentな領域として追加されました

メモリサイズはというと

```
$ free -h
               total        used        free      shared  buff/cache   available
Mem:           108Gi       7.2Gi       100Gi       269Mi       2.2Gi       100Gi
Swap:             0B          0B          0B
```

すばらしい、3桁Giもあると安心感が違いますね

この状態でlsblkをすると、pmem0およびpmem1が追加されています

```
# lsblk
NAME        MAJ:MIN RM   SIZE RO TYPE MOUNTPOINTS
...
pmem0       259:2    0    16G  0 disk 
pmem1       259:3    0    16G  0 disk 
```

速度もみてみます
なお4枚刺しの時点でシステムクロックが3600 MT/sに落ちてます

```
# dd if=/dev/pmem0 of=/dev/null bs=1M status=progress 
14856224768 bytes (15 GB, 14 GiB) copied, 3 s, 5.0 GB/s 
16384+0 records in
16384+0 records out
17179869184 bytes (17 GB, 16 GiB) copied, 4.33281 s, 4.0 GB/s
# dd if=/dev/pmem1 of=/dev/null bs=1M status=progress
14088667136 bytes (14 GB, 13 GiB) copied, 3 s, 4.7 GB/s 
16384+0 records in
16384+0 records out
17179869184 bytes (17 GB, 16 GiB) copied, 4.5266 s, 3.8 GB/s
```

あれ、あんまり差がない？と思いましたが

```
# dd if=/dev/pmem0 of=/dev/null bs=1M status=progress iflag=direct
16384+0 records in
16384+0 records out
17179869184 bytes (17 GB, 16 GiB) copied, 0.84416 s, 20.4 GB/s
# dd if=/dev/pmem1 of=/dev/null bs=1M status=progress iflag=direct
13478395904 bytes (13 GB, 13 GiB) copied, 1 s, 13.5 GB/s
16384+0 records in
16384+0 records out
17179869184 bytes (17 GB, 16 GiB) copied, 1.28736 s, 13.3 GB/s
```

はい、キャッシュを無効にすることで速度が出ました

pmem0が`0x0000001c00000000-0x0000001fffffffff`
pmem1が`0x0000002000000000-0x00000023ffffffff`
の割り当てに対応しているようです

ちなみにbsを1M〜32M（2^nMのみ）で試すと以下がおよそピークでした
この辺の最適値は環境とか負荷にもよると思います

```
# dd if=/dev/pmem0 of=/dev/null bs=8M status=progress iflag=direct
2048+0 records in
2048+0 records out
17179869184 bytes (17 GB, 16 GiB) copied, 0.547077 s, 31.4 GB/s
# dd if=/dev/pmem1 of=/dev/null bs=4M status=progress iflag=direct
4096+0 records in
4096+0 records out
17179869184 bytes (17 GB, 16 GiB) copied, 0.775147 s, 22.2 GB/s
```

ダブルスコアとはいかないものの、3分の2くらいになってますね
さらにちなみに同量のダミーファイルをshmにおいたところpmemより若干早くなりました
エミュレート機構のオーバーヘッドかもしれません

```
# dd if=/dev/shm/test of=/dev/null bs=4M status=progress iflag=direct
4096+0 records in
4096+0 records out
17179869184 bytes (17 GB, 16 GiB) copied, 0.469739 s, 36.6 GB/s
```


という感じでブラウザで動画見つつVM立てたりコンパイルかけたりといった処理を
28日間ほどつけっぱでさせてみたところ、
（amdgpuさんがハングしてXorgが1回死んだり
DisplayPortをつなぎ直さないとサウンドが出なくなることが数回ありましたが）
特にカーネルパニック等起こすことなく動いたので良しとしましょう
（結局pmem領域は捨てVMのディスク置き場になりました）
Linuxはトリッキーな構成でもチューニングしやすいのでおもしろいですね