【RaspberryPi】Dockerを使用してRaspberryPiのカーネルをクロスコンパイルする

前回のエントリーではWindows10 HomeエディションDocker Desktopをインストールしました。そこで、このDockerを使用して、RaspberryPiのカーネルのクロスコンパイルを行ってみたいと思います。これまではVirtualBoxなどをインストールして環境を整えましたが、Dockerが使えればこれまでよりも軽量サイズに環境設定を整えることができます。

今回はRaspberryPi 4Bとコンテナ側はUbuntuを使用しています。

参考

uepon.hatenadiary.com

公式のカーネルコンパイルの情報

www.raspberrypi.org

コンテナを起動する

Ubuntuをベースに今回はクロスコンパイルを行うので、以下のようにコンテナを起動します。起動はPowershellで行います。

PS > docker run -it --name 【コンテナ名】 ubuntu

これでコンテナが起動します。 コンテナは起動するとrootユーザーでログインされるので、作業はrootユーザーで行っていきます。公式ページでは一般ユーザーでの設定になっているのでsudoがついていますが、コンテナでは不要となります。

コンテナ側のUbuntuの事前設定

コンテナ側のOSが立ち上がったらカーネルの構築に必要なパッケージをインストールしていきます。make menuconfigカーネルやドライバの設定をしないのであれば、libncurses5-devは不要かもしれませんが、モジュール関係の設定ではほとんど使用するので、インストールしたほうが無難です。

必要なパッケージのインストールができたら、RaspberryPiのカーネルを構築するツールチェーンのインストールを行っています。ツールチェーンがインストールできたら、.bashrcにパスを追加しておきます。

コンテナ側での操作

# cd
# apt update
# apt install git bc bison flex libssl-dev make libc6-dev libncurses5-dev
# git clone https://github.com/raspberrypi/tools ~/tools
# echo PATH=\$PATH:~/tools/arm-bcm2708/arm-linux-gnueabihf/bin >> ~/.bashrc
# source ~/.bashrc

カーネルソースの取得とconfigファイルの作成

カーネルソースをgitでクローンしていますが、大きくなってしまうので--depth=1を追加しています。これでcloneの対象を最新のコミットのみに限定して取得しサイズの大きさを軽減しています。

# git clone --depth=1 https://github.com/raspberrypi/linux
# cd linux

configファイルの作成を行いますが、以下が使用するRaspberryPiの機種によって内容が異なります。今回はRaspberryPi 4Bを使用するので以下のようにします。

# KERNEL=kernel7l
# make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- bcm2711_defconfig

その他機種は公式ページにかかれている以下のようになります。

Raspberry Pi 1, Pi Zero, Pi Zero W, and Compute Module default build configuration

# cd linux
# KERNEL=kernel
# make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- bcmrpi_defconfig

Raspberry Pi 2, Pi 3, Pi 3+, and Compute Module 3 default build configuration

# cd linux
# KERNEL=kernel7
# make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- bcm2709_defconfig

Raspberry Pi 4 default build configuration

# cd linux
# KERNEL=kernel7l
# make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- bcm2711_defconfig

カーネルコンフィギュレーションを行う必要がある場合には以下のコマンドのmenu形式での修正を行います。現時点では修正箇所はないので、このまま起動せず、コンパイル作業を行っていくことにします。

# make menuconfig

f:id:ueponx:20200621204543p:plain

また、再構築したカーネルの名前が同じ担ってしまうことを防ぐため、MakefileEXTRAVERSIONパラメータ(エクストラバージョン)を変更しておくと、変更後のカーネルで起動後にunameコマンドで表示されるバージョン名をつけることができます。今回は以下のようにしてみました。

Makefile編集後

# SPDX-License-Identifier: GPL-2.0
VERSION = 4
PATCHLEVEL = 19
SUBLEVEL = 127
EXTRAVERSION = uepon #この部分を編集するとunameコマンドの表示に反映される
(略)

これでカーネルコンパイルの準備ができました。

カーネルコンパイル

以下の様に実行すれば、コンパイルが行われます。

# make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- zImage modules dtbs

さらに-jオプションをつけることで並列でコンパイルすることができます。公式のドキュメントによればCPUのコア数の1.5倍までの数値を指定可能のようです。今回使用しているPCをのCPUがCore i7の第4世代のものを使用しているので4コア8論理プロセッサとなっています。 そこで論理プロセッサ数である-j8と論理プロセッサ数の1.5倍の値である-j12を指定してみました。

ただ、時間がどの程度かかったのかを調べるので今回はtimeコマンドを使用しています。

-j8指定時

# time make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- zImage modules dtbs -j8
(中略)
real    16m24.398s
user    109m33.360s
sys     9m58.042s

-j12指定時

# time make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- zImage modules dtbs -j12
(中略)
real    14m48.798s
user    106m4.135s
sys     8m55.342s

f:id:ueponx:20200618002939p:plain

-j8では16分、-j12では14分でした。RaspberryPi実機ではかなりかかったような気がしますが、仮想環境でのクロスコンパイルではこの程度で終了するとは!かなり短くて助かりますね!-j8の場合にはCPU負荷は100%まで行きませんでしたが、-j12では100%近くまでいっていました。ドキュメントの最適値は割と当たっているようです。

Dockerコンテナ作業による制限

あとは、出来上がったカーネルをRaspberryPi側に転送しなければいけないのですが、公式のドキュメントではRaspberryPiのSDカードメモリをマウントして転送するという形になるのですが、DockerのコンテナはSDカードメモリをマウントすることができません。そこで、ホストPC(Windows10)と共有ディレクトリを設定して、ファイルをそちらにコピーしてからSDカードメモリへ転送しようと思います。

ただ、既に作成したコンテナでホスト側のディレクトリをマウントする方法を探してみたのですが、どうも無いようです。

DockerとホストPCとのファイル共有を行う

docker runなどで作成したコンテナは、作成後後は変更できません。この場合には一度、コンテナをdocker commitを使用して、イメージ化して、再度docker runすることでrun時のオプションを変更(ホスト側のディレクトリをマウントすることも)できます。

PS > docker stop 【コンテナ名】
PS > docker commit 【コンテナ名】 【イメージ名】
PS > docker run -v /c/Users/【ユーザー名】/Documents/workspace:/data/workspace -it --name 【新規コンテナ名】 【イメージ名】

これでDockerのホストのWindows10とコンテナのUbuntuでファイルの共有ができるようになりました。今回はWindows側のC:/Users/【ユーザー名】/Documents/workspaceUbuntu側の/data/workspaceにマウントしています。

では引き続きDockerコンテナで操作を行っていきます。最初からマウントすることができていれば、先程の作業は不要です。ではコンテナ側で以下の作業を行っていきます。 今回はコンパイル結果を/data/workspace/fat32(/bootに相当)/data/workspace/ext4(/に相当)に格納して行きます。(公式ドキュメントでマウントするディレクトリ名似合わせています)

途中でkmodというパッケージをapt installしていますが、これを行わないとモジュールのコンパイルmodules_install)でdepmodコマンドがないというエラーが出力されます。当然入っているものだと思っていたので少し驚きましたが、こちらインストールしておきましょう。

コンテナ側での操作

# cd linux
# KERNEL=kernel7l
# mkdir /data/workspace/fat32
# mkdir /data/workspace/ext4
# mkdir /data/workspace/fat32/overlays/
# apt install kmod

# env PATH=$PATH make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- INSTALL_MOD_PATH=/data/workspace/ext4 modules_install

# cp arch/arm/boot/zImage /data/workspace/fat32/$KERNEL.img
# cp arch/arm/boot/dts/*.dtb /data/workspace/fat32/
# cp arch/arm/boot/dts/overlays/*.dtb* /data/workspace/fat32/overlays/
# cp arch/arm/boot/dts/overlays/README /data/workspace/fat32/overlays/

これでコンパイルしたカーネルとモジュールは共有したディレクトリに格納されます。

RaspberryPi側でカーネル、モジュールなどの置き換え

あとはRaspberryPi側を起動してファイルの置き換えを行っていきます。 SDカードを挿入してコピーするというのもありですが、今回はRaspberryPiを起動して対応してみます。(ext4のフォーマットのSDカードへの書き込みができるかわからなかったのでこのようにしています)

RaspberryPi 4Bでの操作

まずはscpなどで先程作成したファイルを/home/piにコピーしておきます。

自分が使用しているrloginというコンソールアプリはscp機能をGUI操作できるのでかなり楽でした。

f:id:ueponx:20200621214743p:plain

  • C:/Users/【ユーザー名】/Documents/workspace/fat32/(コピー元) → /home/pi/fat32/(コピー先)
  • C:/Users/【ユーザー名】/Documents/workspace/ext4/(コピー元) → /home/pi/ext4/(コピー先)

ではあとは規定場所にコピーを行います。

$ KERNEL=kernel7l
$ sudo cp /boot/$KERNEL.img /boot/$KERNEL-backup.img
$ sudo cp /home/pi/fat32/$KERNEL.img /boot/$KERNEL.img
$ sudo cp /home/pi/fat32/*.dtb /boot/
$ sudo cp /home/pi/fat32/overlays/*.dtb* /boot/overlays/
$ sudo cp /home/pi/fat32/overlays/README /boot/overlays/
$ sudo cp -r /home/pi/ext4/* /

再起動&確認

コピーが終わったらRaspberryPiを再起動します。

$ sudo reboot

無事起動できたら、unameコマンドで今回作成したカーネルか確かめてみます。

$ uname -a
Linux raspberrypi 4.19.127uepon-v7l+ #1 SMP Thu Jun 18 14:14:39 UTC 2020 armv7l GNU/Linux

やりました!無事成功です!

ちゃんと差し替えたカーネルで起動しています! カーネルのクロスコンパイルは成功しました。中身は何も変わっていませんがw

おわりに

とりあえず、カーネルのクロスコンパイルがDockerのコンテナでできるようになりました。何かあればこちらで対応できそうです。Windows HomeでもDockerが動作するようになったので、RaspberryPiのカーネルのクロスコンパイルもOSによるハードルが下がりました。Dockerなので、かなりコンパクトに環境を保存しておけるのもいいですね。

これをやろうとした当初、カーネルコンパイルしないと対応されていなかったものがあったのですが、RaspberryPiのバージョンアップにより、デフォルトでの対応がなされてしまい、カーネルの再コンパイルの必要性はなくなってしまいました。最近は対応が早いので、この手法も必要があまりないのかもしれません。

Windows10の今回のアップデートはかなり助かる機能が増えた印象です。多分不具合もあるんでしょうけどね。

/* -----codeの行番号----- */