NanoPi NEO3 Plus のカーネルビルド

起動が遅い…(比較対象が悪い)

i7、32GB、SSD な環境だと5秒か6秒かで立ち上がる。(UEFIの時間除く)
それに比べて NanoPi NEO3 Plus は21~22秒というところ。
CPU もメモリも比較するのがかわいそうなレベルだし、SSD ではなく SD カードなのも、ねぇ。

けれど、10秒くらいに短縮出来たらいいよなぁ、と思い、カーネルビルドに乗り出してみた。


結果、どのくらい速くなったの?

上記の通り「10秒くらいになったらいいな~」とは思っていたけど、それは夢のまた夢。
5秒ちょっと速くなって、平均16秒というのが良いところ、か。

それでも、一般的な PC の場合はメモリチェックに SATA 接続チェックといった UEFI が立ち上がってチェックしている時間がそれなりに長い。

そうするとトータルでほぼほぼ同じくらいの時間になったかな。
若干、NEO3 Plus のほうが速いまである?

ちなみに、カーネルの起動だけなら3秒は切れるっぽい。
SD カードブートなのによく頑張った!w

ので、圧倒的に快適感を得られた!! …まぁ、「速度」なんてのにはすぐに慣れてしまうのだけれどね…

カーネルビルド:準備

やることはほぼほぼ公式の Wiki に書いてある。

ビルドするPCを別途用意出来ればいいけど、別に仮想マシンでも大丈夫だろうし、WSL2 でも良いと思う。
が、Ubuntu 20.04 が公式に動作確認取れている環境なので、24.04 とかはやめて 20.04 を用意した方が無難。
尚、26.04は明確に使えない。(ツール類の準備中に「対応してないバージョンやで」と怒られる)

などと言いながら、既に使用中の Ubuntu で22.04 があったからそれを使ってしまってはいるけれど…

NanoPi NEO3 Plus – FriendlyELEC WiKi

公式 Wiki のままとはいえ、ちょっと自分用におさらい。

$ sudo apt update
$ sudo apt install curl ccache

$ sudo bash -c "$(curl -fsSL https://raw.githubusercontent.com/friendlyarm/build-env-on-ubuntu-bionic/master/install.sh)"

(それなりに時間がかかるのでコーヒーでも飲みながら待つ…)

$ export PATH=/opt/FriendlyARM/toolchain/11.3-aarch64/bin:$PATH
$ export GCC_COLORS=auto
$ . ~/.bashrc

$ aarch64-linux-gcc -v
Using built-in specs.
COLLECT_GCC=aarch64-linux-gcc
COLLECT_LTO_WRAPPER=/opt/FriendlyARM/toolchain/11.3-aarch64/libexec/gcc/aarch64-cortexa53-linux-gnu/11.3.0/lto-wrapper
Target: aarch64-cortexa53-linux-gnu
Configured with:(長いので省略)
Thread model: posix
Supported LTO compression algorithms: zlib
gcc version 11.3.0 (ctng-1.25.0-119g-FA)

これでカーネルのビルドに必要な下準備は完了

カーネルのビルド

最初だけやる準備

公式の手順とちょっとだけ変えてて、wget でやってるファイル集めは最初にまとめてしまった。
後でやっても最初にやっても別に変わらないので。(後半は作業:コマンド実行に集中したい)

$ git clone https://github.com/friendlyarm/kernel-rockchip --single-branch --depth 1 -b nanopi6-v6.1.y kernel-rockchip
$ cd kernel-rockchip
$ touch .scmversion
$ make CROSS_COMPILE=aarch64-linux-gnu- ARCH=arm64 nanopi-r2_linux_defconfig

$ wget https://raw.githubusercontent.com/friendlyarm/sd-fuse_rk3528/kernel-6.1.y/tools/mkkrnlimg
$ chmod 755 mkkrnlimg
$ wget https://raw.githubusercontent.com/friendlyarm/sd-fuse_rk3528/kernel-6.1.y/tools/resource_tool
$ chmod 755 resource_tool
$ wget https://raw.githubusercontent.com/friendlyarm/sd-fuse_rk3528/kernel-6.1.y/prebuilt/boot/logo.bmp
$ wget https://raw.githubusercontent.com/friendlyarm/sd-fuse_rk3528/kernel-6.1.y/prebuilt/boot/logo_kernel.bmp

カーネルの設定変更

自分は使わないであろうドライバとかが色々入っているので、今回のような軽量化目的だと、Device Driver あたりの項目をどんどん削る。
ただまぁ、カーネルなのでね、むやみやたらに削除したりすると当然起動しなくなる。
とりあえず、トライアル&エラーになるかな。

以下のようにすれば選択形式でいろいろと有効/無効/モジュール化みたいな形で組み込むかどうか選択できる。

$ make CROSS_COMPILE=aarch64-linux-gnu- ARCH=arm64 menuconfig

いざ、ビルド

$ make CROSS_COMPILE=aarch64-linux-gnu- \
       ARCH=arm64 \
       CC="ccache aarch64-linux-gnu-gcc" \
       -j$(nproc)

初回だけは結構時間かかる…

Core i7-11700 な PC で VMwareWorkstation を使い、2コア割当、メモリ8GB割当した仮想PCで1時間くらいはかかったかな。

が、下記の通りメモリはともかく CPU が完全に不足してますねぇ…

環境がちょっと違うけど、割り当てコア数を4にしたら20分くらいにはなったかな。
20分くらいなら、まぁ、許容範囲だし、2回目以降 ccache が予想以上に頑張ってくれたので短時間化できるし、とりあえずこれで頑張ってみよう。

モジュールのビルド

処理時間的な山は越えたかな?と思ったら2つ目の山が来る。モジュールのビルド。
これもそこそこ時間かかるので実行したらとにかく待つ。
ただし、これも長いのは最初の1回だけ。2回目以降は ccache が凄い。

$ mkdir -p out-modules && rm -rf out-modules/*
$ make CROSS_COMPILE=aarch64-linux-gnu- \
       ARCH=arm64 \
       CC="ccache aarch64-linux-gnu-gcc" \
       INSTALL_MOD_PATH="$PWD/out-modules" modules \
       -j$(nproc)

次は make とはなっているけどすぐ終わるやつ。

$ make CROSS_COMPILE=aarch64-linux-gnu- \
       ARCH=arm64 \
       INSTALL_MOD_PATH="$PWD/out-modules" \
       modules_install

後処理

$ KERNEL_VER=$(make CROSS_COMPILE=aarch64-linux-gnu- ARCH=arm64 kernelrelease)
$ [ ! -f "$PWD/out-modules/lib/modules/${KERNEL_VER}/modules.dep" ] && \
  depmod -b $PWD/out-modules -E Module.symvers -F System.map -w ${KERNEL_VER}
$ (cd $PWD/out-modules && find . -name \*.ko | xargs aarch64-linux-strip --strip-unneeded)

出来上がったものをパッキング

カーネルのビルドやモジュールのビルド、各種設定ファイル的なものがここまでで出来上がった。

それらをまとめて、SD カードに書き込むためのファイルを作る。
NEO3 Plus の Ubuntu では役割毎に SD カードのパーティションを複数に分けているので、そのパーティション毎にイメージファイルを作成する感じ。

カーネルが入ったパーティションには kernel.img というファイルを一つ
設定ファイル等がまとめられたパーティションには resource.img というファイルを一つ

といった感じに。

また、モジュール類も対象としているシステムの /lib 配下にコピーする必要があるので、tar でまとめる。

$ ./mkkrnlimg arch/arm64/boot/Image kernel.img
$ ./resource_tool --dtbname arch/arm64/boot/dts/rockchip/rk3528-nanopi*-rev*.dtb \
  logo.bmp logo_kernel.bmp
$ tar -czpf modules_arch.tar.gz -C ./out-modules/lib/ modules

これで、kernel.img と resource.img 、modules_arch.tar.gz が完成。

SDカードに焼き込む

さて、以上で次の4つのファイルが出来上がった。

  • kernel.img
  • resource.img
  • modules_arch.tar.gz

これを SD カードに書き込んでいけばいいわけだ。
そして、この SD カードは現状で NEO3 Plus 上で FE 公式の Ubuntu が動いているという前提なので、何とかしてこれらのファイルを SD カードにコピーしたい。

ということで、Ubuntu 間のファイルのやり取り、しかも SSHで 接続ができるのなら SCP を使うのが手軽。

カーネルをビルドしたPCから下記のようにすれば、NEO3 Plus にファイルを転送できる。

$ scp [送りたいファイル] [送信先のuserName]@[送信先のIP address]:[送信先のパス]

hoge というユーザーが 192.168.10.20 いたとすると以下のようにする。

$ scp kernel.img hoge@192.168.10.20:/home/hoge
$ scp secure.img hoge@192.168.10.20:/home/hoge
$ scp modules_arch.tar.gz hoge@192.168.10.20:/home/hoge
$ scp kernel.img hoge@192.168.10.20:/home/hoge

で、送信が完了したら、操作を開発 PC から NEO3 Plus に変える。

念の為、パーティションの状態を確認。

# parted /dev/mmcblk0 print
Model: SD SE064 (sd/mmc)
Disk /dev/mmcblk0: 61.9GB
Sector size (logical/physical): 512B/512B
Partition Table: gpt
Disk Flags:

Number  Start   End     Size    File system  Name      Flags
 1      8389kB  12.6MB  4194kB               uboot
 2      12.6MB  16.8MB  4194kB               misc
 3      16.8MB  21.0MB  4194kB               dtbo
 4      21.0MB  37.7MB  16.8MB               resource      ← 書き換えるターゲット
 5      37.7MB  79.7MB  41.9MB               kernel       ← 書き換えるターゲット
 6      79.7MB  113MB   33.6MB               boot
 7      113MB   147MB   33.6MB               recovery
 8      147MB   2496MB  2349MB  ext4         rootfs
 9      2496MB  61.9GB  59.4GB  ext4         userdata

パーティション番号の4、5が書き換えるターゲット。

ということで、それぞれに対して dd コマンドで新しい img ファイルを焼いてみよう。

※いや、マジでパーティション番号の間違いとか、そもそもストレージの取り違えとかには気を付けてね…

$ sudo dd if=resource.img of=/dev/mmcblk0p4 bs=1M
$ sudo dd if=kernel.img of=/dev/mmcblk0p5 bs=1M

次にモジュールを展開する。

$ cp /home/hoge/modules_arch.tar.gz /lib/modules_arch.tar.gz
$ cd /lib
$ tar -xzpf modules_arch.tar.gz

終わったら、起動してくることを祈りつつ…

$ sudo reboot

トライアル・アンド・エラー:カーネルの設定とビルドを繰り返す方法

さて、1回目のカーネルビルドは上記のような感じでいいでしょう。
けど、うまくいった、いかないにかかわらず、2回目、3回目と設定を変えて試すとき、どこからどうやればいい!?

となるのでそれをまとめたのが以下。

やることを大まかに

  1. カーネルの設定変更
  2. カーネルのビルド
  3. モジュールの作成
  4. パッキング
  5. SDカードへの書き込み

打ち込むコマンドは以下のようになる

開発環境の Ubuntu が再起動されていたり SSH 接続をいったん切ってしまった場合に必要な操作。

$ export PATH=/opt/FriendlyARM/toolchain/11.3-aarch64/bin:$PATH
$ export GCC_COLORS=auto
$ . ~/.bashrc

$ aarch64-linux-gcc -v
Using built-in specs.
COLLECT_GCC=aarch64-linux-gcc
COLLECT_LTO_WRAPPER=/opt/FriendlyARM/toolchain/11.3-aarch64/libexec/gcc/aarch64-cortexa53-linux-gnu/11.3.0/lto-wrapper
Target: aarch64-cortexa53-linux-gnu
Configured with:(長いので省略)
Thread model: posix
Supported LTO compression algorithms: zlib
gcc version 11.3.0 (ctng-1.25.0-119g-FA)

環境変数は再起動等で元に戻ってしまうので再設定し、 . ~/.bashrc で反映するというところまで実行し、
続いて、aarch64-linux-gcc -v を実行してクロスコンパイルのターゲット設定が間違っていないか確認しておこう。

カーネルの再設定

$ make CROSS_COMPILE=aarch64-linux-gnu- ARCH=arm64 menuconfig

この時、カーネルのバージョンを追記しておくと良い。
ロールバックする時や比較などに超便利だから。

次にモジュールのビルドを行うとき、このバージョンごとのサブフォルダを作ってモジュール類を用意してくれるので、極力、バージョンを設定しておいた方が良さそう。
設定する場所は以下の通り。

General setup -> Local version – append to kernel release

ここに日付でも通し番号でも何かわかりやすいものを設定しておけば、カーネルのバージョンに反映される。

また、併せて設定内容も同じように上で追記した名前と同じ名前で保存しておく。

カーネル設定ファイルをコピー

$ cp mybuild_20260719.config .config

カーネルのビルド

$ make CROSS_COMPILE=aarch64-linux-gnu- \
       ARCH=arm64 \
       CC="ccache aarch64-linux-gnu-gcc" \
       -j$(nproc)

モジュールのビルド&インストール

$ make CROSS_COMPILE=aarch64-linux-gnu- \
            ARCH=arm64 \ CC="ccache aarch64-linux-gnu-gcc"\
            INSTALL_MOD_PATH="$PWD/out-modules" modules \
            -j$(nproc)

$ make CROSS_COMPILE=aarch64-linux-gnu- \
       ARCH=arm64 \
       INSTALL_MOD_PATH="$PWD/out-modules" \
       modules_install

モジュールのインストール時に流れる文字列を見ていると、カーネル設定時にバージョン設定を追記したディレクトリに保存してくれているのがわかる。

各イメージファイルの作成

$ KERNEL_VER=$(make CROSS_COMPILE=aarch64-linux-gnu- ARCH=arm64 kernelrelease)
$ [ ! -f "$PWD/out-modules/lib/modules/${KERNEL_VER}/modules.dep" ] && \
  depmod -b $PWD/out-modules -E Module.symvers -F System.map -w ${KERNEL_VER}
$ (cd $PWD/out-modules && find . -name \*.ko | xargs aarch64-linux-strip --strip-unneeded)

$ ./mkkrnlimg arch/arm64/boot/Image kernel.img
$ ./resource_tool --dtbname arch/arm64/boot/dts/rockchip/rk3528-nanopi*-rev*.dtb \
  logo.bmp logo_kernel.bmp
$ tar -czpf modules_arch.tar.gz -C ./out-modules/lib/ modules

出来上がったファイルを開発 PC から NEO 3 PLUS に転送

$ scp kernel.img hoge@192.168.10.20:/home/hoge
$ scp modules_arch.tar.gz hoge@192.168.10.20:/home/hoge
$ scp resource.img hoge@192.168.10.20:/home/hoge

転送が完了したら、操作を NEO3 Plus に切り替える。

一応、パーティションを確認しておく。

$ sudo parted /dev/mmcblk0 print
Model: SD SE064 (sd/mmc)
Disk /dev/mmcblk0: 61.9GB
Sector size (logical/physical): 512B/512B
Partition Table: gpt
Disk Flags:

Number  Start   End     Size    File system  Name      Flags
 1      8389kB  12.6MB  4194kB               uboot
 2      12.6MB  16.8MB  4194kB               misc
 3      16.8MB  21.0MB  4194kB               dtbo
 4      21.0MB  37.7MB  16.8MB               resource
 5      37.7MB  79.7MB  41.9MB               kernel
 6      79.7MB  113MB   33.6MB               boot
 7      113MB   147MB   33.6MB               recovery
 8      147MB   2496MB  2349MB  ext4         rootfs
 9      2496MB  61.9GB  59.4GB  ext4         userdata

対象となるパーティションに正しくイメージファイルを焼いていく…

$ sudo dd if=resource.img of=/dev/mmcblk0p4 bs=1M
$ sudo dd if=kernel.img of=/dev/mmcblk0p5 bs=1M

モジュールを展開

$ sudo cp /home/hoge/modules_arch.tar.gz /lib/modules_arch.tar.gz
$ cd /lib
$ sudo tar -xzpf modules_arch.tar.gz

再起動

$ sudo reboot

こうやって試行錯誤を繰り返し…と言っても、ChatGPT あたりに聞くとどの辺削ればいいかは教えてくれるので、その辺りを削っていったら割とカーネルはスリム化できると思う。

起動しなくなった場合の対処法

まぁ、カーネルのビルドなんかやってるとしばしば起こる沈黙やカーネルパニック。
昔々のはるか昔…そりゃぁ復旧は面倒だった。
けど、今は SD カードだもんねぇ…

引っこ抜いて、別の端末に挿して、動いていた時のイメージ焼けば復旧。

だもの。
いい時代になったもんだ…

その別の端末というのも、そもそも開発用 PC が Ubuntu で用意したから、そいつに繋げればいいわけで。

そして、実際繋げてみると下記のように認識される。
※もちろん、繋がっているHDDやSSDによって増減するので要注意。マジ要注意。

$ ls /dev/sd*
/dev/sda  /dev/sda1  /dev/sda2  /dev/sda3
/dev/sdb  /dev/sdb1  /dev/sdb2  /dev/sdb3  /dev/sdb4  /dev/sdb5  /dev/sdb6  /dev/sdb7  /dev/sdb8  /dev/sdb9

パーティションの数が多いから取り違えにくいとはいえ、本当に要注意やで。

で、これが見えてしまえば、あとは dd コマンドで起動してた時のイメージを焼けばいい。

ここで効くのさ、menuconfig でカーネルの設定をいろいろ変更していた時、「バージョン名に独自のものを付けた」「config ファイルの名前もそれと同じものを付けた」とやったことが。

例えば、「mybuild_20270718_04.config」といったように前の設定ファイルが残っていたら、これを「.config」とコピーして、カーネルとモジュールを再ビルドする上記手順をもう一度やってあげれば、その時のイメージがまた作れる。

んで、出来上がったイメージをパーティションの指示を間違えないように、慎重に実行する。

$ sudo dd if=resource.img of=/dev/sdb4 bs=1M
$ sudo dd if=kernel.img of=/dev/sdb5 bs=1M

また、モジュール類を /lib に展開しなくて良いのか!?

となるけど、tar でアーカイブしたモジュールはディレクトリ構造が上で付けた独自のバージョン名の部分を含んだ形で構成される。

なので、/lib/modules/ 配下に[本来のバージョン名]+[独自のバージョン名]のディレクトリが展開されているので、これを意図的に消していない限りは既にモジュールは”そこにある”状態になっている。

$ ls -al /lib/modules/6.1.141* -d
drwxr-xr-x 4 root root 4096 Apr  2 12:24 /lib/modules/6.1.141
drwxrwxr-x 3 hoge hoge 4096 Jul 18 18:48 /lib/modules/6.1.141-mybuild_20260718_01
drwxrwxr-x 3 hoge hoge 4096 Jul 20 18:57 /lib/modules/6.1.141-mybuild_20260718_02
drwxrwxr-x 3 hoge hoge 4096 Jul 20 19:18 /lib/modules/6.1.141-mybuild_20260718_03
drwxrwxr-x 3 hoge hoge 4096 Jul 20 19:44 /lib/modules/6.1.141-mybuild_20260719_01

もちろん、「カーネルはこれでOK」となったら過去に作ってきたモジュール類は削除でOK

だけど、ロールバックする可能性があるならば、多少残しておいた方が吉。

あと、これを書いてて気になったけど、/lib/module のパーミッションがちょっとアレなことになってるね…

どこでどう変更されてしまったかわからないけど、本来は下記のようになっているべきだと思う。

$ ls -al /lib/modules/6.1.141* -d
drwxr-xr-x 4 root root 4096 Apr  2 12:24 /lib/modules/6.1.141
drwxr-xr-x 3 root root 4096 Jul 18 18:48 /lib/modules/6.1.141-mybuild_20260718_01
drwxr-xr-x 3 root root 4096 Jul 20 18:57 /lib/modules/6.1.141-mybuild_20260718_02
drwxr-xr-x 3 root root 4096 Jul 20 19:18 /lib/modules/6.1.141-mybuild_20260718_03
drwxr-xr-x 3 root root 4096 Jul 20 19:44 /lib/modules/6.1.141-mybuild_20260719_01

まぁ、ここは最後に直しましょう。

ということで。
イメージを変更できたら、またまた SD カードを引っこ抜いて、NEO3 Plus に挿し直して起動してみる。
ちゃんと前のカーネルで起動してきたらOK

この記事にコメントしてみる