【Raspberry Pi 4B】RAMディスク、CPUオーバークロックを併用してもカーネルローカルビルド時間は大して変わらなかったって話

## 【前文】
[公式](https://www.raspberrypi.org/documentation/linux/kernel/building.md "Kernel building - Raspberry Pi Documentation") にもある通り、カーネルのビルド方法は大きく分けて以下の 2パターン。

- Raspberry Pi本体でのローカルビルド
- PC や 仮想環境（VirtualBox や VMWare）でのクロスビルド

前者は Raspberry Pi本体だけで済むし、ビルド、インストールをシームレスに行うことができて初学者向け。
しかし Pi4B でもビルドに 1時間強もかかるのが。膨大な量のソースをコンパイルするから、せめて RAMディスク上で作業すれば速くなるのかな？
という素朴な疑問が出発点。

しかし目を見張るほどの時間短縮はせず。何がボトルネック？ やはり CPUパワー？ ならばオーバークロックでしょ。
と試してみるも、やはりあまり変わらなかった。

残念な結果だったけど折角なので記録として残す。
ビルド時間は以下。

| ケース | ビルド時間 |
| ---- | ---- |
| microSDカード上ビルド | 61m42.817s |
| RAMディスク上ビルド | 57m33.457s |
| RAMディスク上 + オーバークロックでビルド | 56m42.724s |

作業環境は以下。

| Pi | OS | Kernel |
| ---- | ---- | ---- |
| Raspberry Pi4B 4G | Raspberry Pi OS Lite / Raspbian GNU/Linux 10 (buster) | 5.10.17-v7l+ |

と書いていて気が付いたけど、「Raspberry Pi OS」に名称が変更された筈なのに、内部的にはまだ「Raspbian」なんだね。

```shell-session
pi@raspberrypi:~ $  lsb_release -a
No LSB modules are available.
Distributor ID: Raspbian
Description:    Raspbian GNU/Linux 10 (buster)
Release:        10
Codename:       buster
pi@raspberrypi:~ $
```
CUI環境での作業になるので Raspberry Pi OS Lite を使用。
RAMディスク設定、CPUオーバークロック設定、カーネルビルド方法はぐぐれば沢山出てくるけど、ワンセットとなると見かけないので以下に手順を記す。

## 【RAMディスク設定】
microSDカードの寿命延命目的でRAMディスクの利用は Raspberry Pi で鉄板ネタ。
解説サイトは沢山あるけど、[こちら](https://www.rickmakes.com/setting-up-a-ram-disk-on-a-raspberry-pi-4/ "Kernel building - Raspberry Pi Documentation") がシンプルで分かりやすい。必要にして十分な内容。これを参考に設定した。

```shell-session
pi@raspberrypi:~ $ cat /etc/fstab
proc            /proc           proc    defaults          0       0
PARTUUID=b8f462a2-01  /boot           vfat    defaults          0       2
PARTUUID=b8f462a2-02  /               ext4    defaults,noatime  0       1
# a swapfile is not a swap partition, no line here
#   use  dphys-swapfile swap[on|off]  for that
tmpfs /mnt/ramdisk tmpfs defaults,noatime,mode=755,uid=pi,gid=pi,size=3g,nofail 0 0
pi@raspberrypi:~ $
```

但し、カーネルビルドに 2G では足りないので、3Gに設定している。
因みに `size`パラメータに小数指定は許可されていないようで、「3G じゃなくて 2.7G」とか思って設定するとコケるので注意。
更に fstab って記述にミスがあるとそれだけで起動に失敗する（Linuxあるある）。念の為、オプションに `nofail`を追加した。

4G中、3G も RAMディスクに割り当てるのはバランスが悪い気もするけど、ソースだけで 1Gを超えるしデバッグオプションを有効にしたビルドとなれば 3Gでも足りない。8G版で 4～5G を RAMディスクに割り当てての作業になると思う。
8G版なんて、そんなにメモリ必要ないでしょ、と個人的には思っていたけど、RAMディスクを活用しようとすると考え方も変わってくるね。

fstab に RAMディスクの設定を追記し再起動したら、`df`コマンドで `/mnt/ramdisk`にマウントされていることを確認。

```shell-session
pi@raspberrypi:~ $ df -h
ファイルシス   サイズ  使用  残り 使用% マウント位置
/dev/root        3.9G  2.6G  1.1G   71% /
devtmpfs         1.8G     0  1.8G    0% /dev
tmpfs            1.9G     0  1.9G    0% /dev/shm
tmpfs            1.9G  8.5M  1.9G    1% /run
tmpfs            5.0M  4.0K  5.0M    1% /run/lock
tmpfs            1.9G     0  1.9G    0% /sys/fs/cgroup
tmpfs            3.0G     0  3.0G    0% /mnt/ramdisk
/dev/mmcblk0p1   253M   48M  205M   19% /boot
tmpfs            383M     0  383M    0% /run/user/1000
pi@raspberrypi:~ $
```

ついでに以下のコマンドでベンチマーク。

```shell-session
dd if=/dev/zero of=temp.txt bs=1M count=1024
sync; echo 3 > /proc/sys/vm/drop_caches
dd if=temp.txt of=/dev/null bs=1M count=1024

dd if=/dev/zero of=temp.txt bs=1G count=1
sync; echo 3 > /proc/sys/vm/drop_caches
dd if=temp.txt of=/dev/null bs=1G count=1
```

コマンドはやはり前述のサイトを参考にさせて頂いた。しかしキャッシュクリア処理が無かったので追加した。
これをやらないと microSDカード上の読込が RAMディスクと変わらないという結果になるので。（キャッシュの有用性が良く分かる事例ではあるけど）

結果は以下。

| デバイス | データ量 | 書込 | 読込 |
| ---- | ---- | ---- | ---- |
| microSD | 1.0GiB(1M×1024) | 18.2 MB/s | 45.4 MB/s |
| microSD | 1.0GiB(1G×1) | 16.0 MB/s | 45.5 MB/s |
| RAMディスク | 1.0GiB(1M×1024) | 310 MB/s | 583 MB/s |
| RAMディスク | 1.0GiB(1G×1) | 180 MB/s | 265 MB/s |

厳密には 5～10回計測しその平均、とかだろうから、上記は参考記録程度に。

## 【オーバークロック設定】
これも鉄板ネタなので解説サイトは沢山あるけど、[こちら](https://note.com/npaka/n/n286b2979fdc2 "Raspberry Pi 4をオーバークロックする方法｜npaka｜note") を参考にさせて頂いた。
`/boot/config.txt` の設定値は以下。

```shell-session
over_voltage=2
arm_freq=1750
```

CPUクロック数、温度、電圧確認コマンドは以下。

```shell-session
vcgencmd measure_clock arm && vcgencmd measure_temp && vcgencmd measure_volts
```

アイドル時の計測結果は以下。

```shell-session
frequency(48)=600169920
temp=45.7'C
volt=0.9125V
```

ビルド中の計測結果は以下。

```shell-session
frequency(48)=1750412160
temp=58.4'C
volt=0.9125V
```

[CPU冷却ファンクーラ付きケース](https://qiita.com/god19/items/e8277b1cbced62cc6877 "Raspberry Pi4B CPU冷却ファンクーラ付きケース使用レポート - Qiita") が早速役に立った。常時運用でも問題ないかな？
というかそもそも 1.75GHz ってのが弱いのかなぁ。2GHz はいけるって話もあるけどそこまでは未確認。

## 【RAMディスク上でのカーネルビルド】
### 1. ビルドツールインストール

[公式](https://www.raspberrypi.org/documentation/linux/kernel/building.md "Kernel building - Raspberry Pi Documentation") に従い、以下のコマンドでGitとビルドの依存関係をインストール。

```shell-session
sudo apt install git bc bison flex libssl-dev make -y
```

更に [こちら](https://www.tssol.net/blog/2020/03/28/raspberrypi-how-to-build-kernel/ "Raspberry Pi：カーネルのビルド＆更新手順 _ 森羅万象テーブル") によれば、以下も必要らしいので実行。

```shell-session
sudo apt-get install -y -qq --no-install-recommends make libncurses-dev kernel-package
sudo apt-get install -y -qq --no-install-recommends bison flex
```

### 2. バックアップ

必要があれば現在の microSDをイメージバックアップ。
カーネル関連のファイル単位でバックアップする手もあるけど、リカバリの際カーネルドライバはファイルだけ戻せば良いって話でもないし。
ここら辺を分かっていない場合はイメージバックアップの方が確実。

### 3. カーネルソース取得

明示的に取得パスを指定しない場合、`/usr/src/`配下にダウンロードされる。
今回は RAMディスク上での作業なので、以下のようにパスを指定し実行。

```shell-session
sudo git clone --depth=1 https://github.com/raspberrypi/linux /mnt/ramdisk/linux
```

実行結果は以下。

```shell-session
pi@raspberrypi:~ $ sudo git clone --depth=1 https://github.com/raspberrypi/linux /mnt/ramdisk/linux
Cloning into '/mnt/ramdisk/linux'...
remote: Enumerating objects: 75497, done.
remote: Counting objects: 100% (75497/75497), done.
remote: Compressing objects: 100% (72055/72055), done.
remote: Total 75497 (delta 6055), reused 15825 (delta 2645), pack-reused 0
Receiving objects: 100% (75497/75497), 199.16 MiB | 3.25 MiB/s, done.
Resolving deltas: 100% (6055/6055), done.
Checking out files: 100% (71110/71110), done.
pi@raspberrypi:~ $
```

取得したソースディレクトリに移動し取得したブランチを確認。

```shell-session
cd /mnt/ramdisk/linux
git branch
```

実行結果は以下。

```shell-session
pi@raspberrypi:~ $ cd /mnt/ramdisk/linux
pi@raspberrypi:/mnt/ramdisk/linux $ git branch
* rpi-5.10.y
pi@raspberrypi:/mnt/ramdisk/linux $
```

`rpi-5.10.y` であることが分かる。
因みに明示的にブランチを指定し取得する場合は以下。

```shell-session
sudo git clone --depth=1 --branch rpi-5.10.y https://github.com/raspberrypi/linux /mnt/ramdisk/linux
```

リモート側のブランチの一覧を確認したい場合は、`git ls-remote`コマンドで。

```shell-session
pi@raspberrypi:/mnt/ramdisk/linux $ git ls-remote | grep rpi-5.
From https://github.com/raspberrypi/linux
0222b82b2f1fcf1e0c477f77c50688c98f35e2bf        refs/heads/rpi-5.0.y
29ee73a82540e8b94c6bde0ad1acae65f73653ea        refs/heads/rpi-5.1.y
13c43880f2ee6db7d26949cb6a3e8db1a4b76736        refs/heads/rpi-5.10.y
2cbd302b1e200073ecf52b2f96e619c9561625a1        refs/heads/rpi-5.11.y
425ed5a960386760d811aee0ad7fd5b0a6e37b22        refs/heads/rpi-5.12.y
94dbfa7606fc2f3df44979ed9c2d1d3676f2c159        refs/heads/rpi-5.2.y
32ba05a62a8071d091d7582cc37b4bac2962b1dd        refs/heads/rpi-5.3.y
93349cdffc3fbb446c7c1fc7354215a5b8e30b97        refs/heads/rpi-5.4.y
96a3278497cc6540d32dc4a7dba9100da6df16f0        refs/heads/rpi-5.5.y
ff0aa78166a59d44bc8d472386699624c8db6da5        refs/heads/rpi-5.6.y
fe872d026d3a280a5151af08eb949eddf586fa1b        refs/heads/rpi-5.7.y
9b3199ca416d89b1f810fc65d8ffe1ad2a03ac0e        refs/heads/rpi-5.8.y
104137d44c6365fafc36ec1ea59db06f58ef5dfc        refs/heads/rpi-5.9.y
e850ab437a2d0fa607d410b7bae62f90fb941430        refs/tags/rpi-5.10.y_20201122
pi@raspberrypi:/mnt/ramdisk/linux $
```

rpi-5系は 12 まである模様。

取得したカーネルソースのバージョン確認。

```shell-session
pi@raspberrypi:/mnt/ramdisk/linux $ head Makefile -n 5
# SPDX-License-Identifier: GPL-2.0
VERSION = 5
PATCHLEVEL = 10
SUBLEVEL = 23
EXTRAVERSION =
pi@raspberrypi:/mnt/ramdisk/linux $
```

`5.10.23` ってことになるだんけど、現在最新の安定バージョンは `5.10.17`。

```shell-session
pi@raspberrypi:/mnt/ramdisk/linux $ uname -r
5.10.17-v7l+
pi@raspberrypi:/mnt/ramdisk/linux $
```

最新の安定バージョンのソースを取得したい場合は？ という疑問が湧いてくるけどそれは後述。

### 4. カーネルソースバックアップ

取得したカーネルソースは RAMディスク上なので電源を落とせば消えてしまう。後日再実行する場合など、再取得は面倒で時間がかかる。必要があればバックアップを取り、再実行時はそれを展開する。
`/usr/src/linux.rpi-5.10.y.tar.gz` として圧縮バックアップするコマンド例は以下。

```shell-session
sudo tar -C /mnt/ramdisk -zcf /usr/src/linux.rpi-5.10.y.tar.gz linux
```

それをリストアするコマンド例は以下。

```shell-session
sudo rm -rf /mnt/ramdisk/linux
sudo tar -zxf /usr/src/linux.rpi-5.10.y.tar.gz -C /mnt/ramdisk
```

と書いていて思ったんだけど、だったら最初っから圧縮形式でダウンロードした方が速くないかい？ って話もあり、その場合は以下。

```shell-session
curl -L https://github.com/raspberrypi/linux/archive/refs/heads/rpi-5.10.y.zip > rpi-5.10.y.zip
```

### 5. カーネルビルド

ローカルビルドの場合、公式配布のカーネルと区別する為、`Makefile`の `EXTRAVERSION`定義にそれとわかる文字列を設定しておくのがお約束。ログイン時や `uname`コマンド等でカーネルバージョンが表示される場合、この定義も表示される。
設定コマンド例は以下。

```shell-session
cd /mnt/ramdisk/linux
sudo sed -i 's/EXTRAVERSION =/EXTRAVERSION = -god/g' Makefile
grep 'EXTRAVERSION =' Makefile
```

実行結果は以下。

```shell-session
pi@raspberrypi:/mnt/ramdisk/linux $ cd /mnt/ramdisk/linux
pi@raspberrypi:/mnt/ramdisk/linux $ sudo sed -i 's/EXTRAVERSION =/EXTRAVERSION = -god/g' Makefile
grep 'EXTRAVERSION =' Makefilepi@raspberrypi:/mnt/ramdisk/linux $ grep 'EXTRAVERSION =' Makefile
EXTRAVERSION = -god
pi@raspberrypi:/mnt/ramdisk/linux $
```

以下コマンドでビルド。

```shell-session
KERNEL=kernel7l
sudo make bcm2711_defconfig
time sudo make -j4 zImage modules dtbs
```

ビルド時間が分かるよう `time`コマンドで実行している。ここで 1時間前後かかる。不要であれば勿論単に `sudo make -j4 zImage modules dtbs` で構わない。

実行結果は以下。

```shell-session
pi@raspberrypi:/mnt/ramdisk/linux $ KERNEL=kernel7l
pi@raspberrypi:/mnt/ramdisk/linux $ sudo make bcm2711_defconfig
  HOSTCC  scripts/basic/fixdep
  HOSTCC  scripts/kconfig/conf.o
  HOSTCC  scripts/kconfig/confdata.o
  HOSTCC  scripts/kconfig/expr.o
  LEX     scripts/kconfig/lexer.lex.c
  YACC    scripts/kconfig/parser.tab.[ch]
  HOSTCC  scripts/kconfig/lexer.lex.o
  HOSTCC  scripts/kconfig/parser.tab.o
  HOSTCC  scripts/kconfig/preprocess.o
  HOSTCC  scripts/kconfig/symbol.o
  HOSTCC  scripts/kconfig/util.o
  HOSTLD  scripts/kconfig/conf
#
# configuration written to .config
#
pi@raspberrypi:/mnt/ramdisk/linux $ time sudo make -j4 zImage modules dtbs
(中略)
  LD [M]  sound/usb/hiface/snd-usb-hiface.ko
  LD [M]  sound/usb/misc/snd-ua101.ko
  LD [M]  sound/usb/snd-usb-audio.ko
  LD [M]  sound/usb/snd-usbmidi-lib.ko

real    56m42.724s
user    193m28.695s
sys     29m5.682s
pi@raspberrypi:/mnt/ramdisk/linux $
```

### 6. カーネル更新

カーネルビルドに成功したら、以下コマンドで自身のカーネルを上書き更新。

```shell-session
sudo make modules_install
sudo cp arch/arm/boot/dts/*.dtb /boot/
sudo cp arch/arm/boot/dts/overlays/*.dtb* /boot/overlays/
sudo cp arch/arm/boot/dts/overlays/README /boot/overlays/
sudo cp arch/arm/boot/zImage /boot/$KERNEL.img
```

因みに最初の 1行目はカーネルドライバの更新。
[make modules_install](https://wiki.onakasuita.org/pukiwiki/?make%20modules_install "make modules_install - おなかすいたWiki！")
> コンパイルされたモジュールが /lib/modules/\<kernel version>-\<config local version> にコピーされます

手動でインストールしていたドライバがあれば入れ直すこと。

`sudo reboot` で再起動。
再起動後、ログインしカーネルのバージョン確認。

```shell-session
Linux raspberrypi 5.10.23-god-v7l+ #1 SMP Sat Mar 20 23:10:25 JST 2021 armv7l
(中略)
pi@raspberrypi:~ $ uname -a
Linux raspberrypi 5.10.23-god-v7l+ #1 SMP Sat Mar 20 23:10:25 JST 2021 armv7l GNU/Linux
pi@raspberrypi:~ $ uname -r
5.10.23-god-v7l+
pi@raspberrypi:~ $
```

## 【任意のカーネルソースの取得】

ピンポイントでの任意のカーネルソースの取得方法が良く分からず悩んだ。
2021年3月現在、安定バージョンの最新は `5.10.17`。これを取得したいとする。

最初は [こちら](https://qiita.com/newtypehiro919/items/336e09340e728a94a9f7 "Raspberry Pi でカーネルモジュールビルドの準備 - Qiita") を参考にさせて頂いたが上手くいかず。
糸口になったのが [こちら](https://raspberrypi.stackexchange.com/questions/94849/how-to-find-a-specific-kernel-version-in-the-github-repository "linux - How to find a specific kernel version in the Github repository_ - Raspberry Pi Stack Exchange")。そしてやっと辿り着いたのが [こちら](https://github.com/raspberrypi/linux/commit/13b6016e96f628ac1cfb3c0b342911fd91c9c005 "Linux 5.10.17 · raspberrypi_linux@13b6016")。
ハッシュ値 `13b6016e96f628ac1cfb3c0b342911fd91c9c005` が分かれば以下のように取得できる。

```shell-session
pi@raspberrypi:/mnt/ramdisk $ curl -L https://github.com/raspberrypi/linux/archive/13b6016e96f628ac1cfb3c0b342911fd91c9c005.tar.gz > linux.rpi-5.10.17.tar.gz
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   159  100   159    0     0    398      0 --:--:-- --:--:-- --:--:--   398
100  176M    0  176M    0     0  3522k      0 --:--:--  0:00:51 --:--:-- 3524k
pi@raspberrypi:/mnt/ramdisk $
```

更に確認すると、これって要するに [リリース一覧](https://github.com/raspberrypi/linux/releases "Releases · raspberrypi_linux") の最新である `raspberrypi-kernel_1.20210303-1 `。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254888/0427df18-b3ca-3f97-09c2-25627ff0155e.png" width=80%>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/254888/8ffc150f-7799-f17e-ff7b-60e6031f5229.png" width=50%>

リリース一覧の最新がつまり安定板の最新って当たり前か。
でもこの年月日系のタグからカーネルのバージョンはどうやって判断するの？ 月一で `SUBLEVEL` が上がっているっぽいからそこから判断？
ここら辺は運用を知らないと分からないよね。慣れていくしかないのかな。

それと最後に。
速度面ではあまり真価を発揮できなかった RAMディスクだけど、microSDカードの消耗を抑える点では有用なので活用すべきでは。