daisukeの技術ブログ

AI、機械学習、最適化、Pythonなどについて、技術調査、技術書の理解した内容、ソフトウェア/ツール作成について書いていきます

【解説】Raspberry Pi 4 カーネルビルド

Raspberry Pi 4 カーネルビルドについて、手順について書きます。

Linuxのカーネルのビルドは、以下のような理由で必要になる場合があります。

  • 新しい機器(例えば、USBのWi-Fiアダプタなど)を使いたいとき ※Linuxカーネルビルドが必要ない場合もある
  • Linuxカーネルに、新しい機能を追加したいとき(ラズパイなどの組み込み機器の場合、記憶容量が多くないため、デフォルトで無効になっている機能が多い)
  • Linuxカーネルの動作が分からない、カーネルソースが複雑すぎて、ソースを見ても分からない場合に、値を確認したり、通ってるかどうかをデバッグログを入れて確認したいとき

普通にLinuxを使ってるだけなら、カーネルビルドが必要になることはないだけに、すぐにやり方を忘れてしまうので、記事にしておこうと思います。

また、カーネルの解析を行うときのデバッグログの入れ方なども書こうと思います。

カーネルビルド(セルフビルド)の手順

ここでは、公式サイトのカーネルビルドの手順が書かれたページ(https://www.raspberrypi.com/documentation/computers/linux_kernel.html)に基づいて、具体的な手順と、注意点を記載します。

具体的なカーネルビルド(セルフビルド)の手順

公式サイトの手順が優秀すぎて、書くことがなかったです(笑)。

セルフビルド(ページではネイティブビルドと記載されている)とクロスビルドの両方について、丁寧に書かれているので、ここの手順に従ってカーネルビルドを行えばOKです。

ただし、以下の「Building the kernel」の手順については、私の環境では動作しなかった(動作しなくなっていた)。

$ make -j4 Image.gz modules dtbs
$ sudo make modules_install
$ sudo cp arch/arm64/boot/dts/broadcom/*.dtb /boot/firmware/
$ sudo cp arch/arm64/boot/dts/overlays/*.dtb* /boot/firmware/overlays/
$ sudo cp arch/arm64/boot/dts/overlays/README /boot/firmware/overlays/
$ sudo cp arch/arm64/boot/Image.gz /boot/firmware/$KERNEL.img

現在のラズパイの最新のカーネルバージョンは、カーネル6.6です。私の環境はカーネル6.1であり、この手順は最近更新されたみたいです。

以前の「Building the kernel」の手順を書いておく。

$ make -j4 Image.gz modules dtbs
$ sudo make modules_install
$ sudo cp arch/arm64/boot/dts/broadcom/*.dtb /boot/
$ sudo cp arch/arm64/boot/dts/overlays/*.dtb* /boot/overlays/
$ sudo cp arch/arm64/boot/dts/overlays/README /boot/overlays/
$ sudo cp arch/arm64/boot/Image.gz /boot/$KERNEL.img

カーネル6.1では、「/boot/firmware」というディレクトリは存在しないので、コピー先のパスから「/firmware」を削除すればいいだけです。

セルフビルドとクロスビルドの比較

ここではクロスビルドについては詳しく書かないが、ビルド時間をなるべく短くしたいという場合は、クロスビルドを選択すればいいと思います。

個人的には、クロスビルドは、ホスト環境にいくつかのパッケージをインストールしなければならない(私は、なるべく不要なパッケージは入れたくない)のと、ビルド後に、何らかの手段で、カーネルイメージ、モジュールを、ターゲットのラズパイに送信しなければならないのが手間です。あと、私の場合は、ホストPCが非力であることも理由です。

一方、ビルド時間は多少長い(だいたい2~3時間ぐらい)が、何か失敗しても、ラズパイ自体を再インストールすればいいという気楽なところもあり、私はいつもセルフビルドを選択しています。

変更した差分だけをカーネルビルド(セルフビルド)

上の「Building the kernel」を実施すると、2回目以降も1時間ぐらいかかってしまう。これでは効率が悪いので、カーネルソースにデバッグログを入れたぐらいなら、カーネルイメージだけの更新で大丈夫です。

具体的には、以下の手順だけでいい。変更した量にもよるが、だいたい5分以内にはビルドが完了します。

$ make -j4 Image.gz
$ KERNEL=kernel8
$ sudo cp arch/arm64/boot/Image.gz /boot/$KERNEL.img

Linuxカーネルにデバッグログを入れる方法

Linuxカーネルでは、printf()は使えません。代わりに、printk()を使う必要があります。

最近のカーネルソースを見てると、printk()が直接使われてるところは見たことがありません。通常は、代わりに、pr_xxx()が使われています。xxxには、重要度に応じて、err、warnなどを選択することができます。具体的には、include/linux/printk.hに定義があります。

#define pr_emerg(fmt, ...)  printk(KERN_EMERG pr_fmt(fmt), ##__VA_ARGS__)
#define pr_alert(fmt, ...)  printk(KERN_ALERT pr_fmt(fmt), ##__VA_ARGS__)
#define pr_crit(fmt, ...)   printk(KERN_CRIT pr_fmt(fmt), ##__VA_ARGS__)
#define pr_err(fmt, ...)    printk(KERN_ERR pr_fmt(fmt), ##__VA_ARGS__)
#define pr_warn(fmt, ...)   printk(KERN_WARNING pr_fmt(fmt), ##__VA_ARGS__)
#define pr_notice(fmt, ...) printk(KERN_NOTICE pr_fmt(fmt), ##__VA_ARGS__)
#define pr_info(fmt, ...)   printk(KERN_INFO pr_fmt(fmt), ##__VA_ARGS__)
#ifdef DEBUG
#define pr_devel(fmt, ...)  printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__)
#else
#define pr_devel(fmt, ...)  no_printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__)
#endif

低い優先度ものを選択した場合は、OSによっては、もしくは、コンパイルオプションや起動モードなどによって、ログは出力されないかもしれません。

目的が、デバッグログの場合、一時的に追加して、必要なくなったら削除するはずなので、pr_err()や、pr_warn()といった優先度の高めのものを選択しておけばいいと思います。

よく使うデバッグログの記述例

よく使うデバッグログの記述例について書いておきます。

pr_err( "%s %s %d", __FILE__, __func__, __LINE__ );

表示例

[  497.607358] kernel/verifier.c check 12451

この記述をベースにして、確認したい変数などがあれば、下記のように追加すればOKです。

記述例

pr_err( "%s %s %d ret=%d cnt=%d", __FILE__, __func__, __LINE__, ret, cnt );

表示例

[  500.031087] kernel/verifier.c check 12451 0 1122

Linuxカーネルのデバッグログを確認する方法

ログレベルの定義

カーネルソースのinclude/linux/kern_levels.hに定義されており、数値が小さいほど緊急度の高いレベルです。

#define LOGLEVEL_EMERG    0  /* system is unusable */
#define LOGLEVEL_ALERT    1  /* action must be taken immediately */
#define LOGLEVEL_CRIT     2  /* critical conditions */
#define LOGLEVEL_ERR      3  /* error conditions */
#define LOGLEVEL_WARNING  4  /* warning conditions */
#define LOGLEVEL_NOTICE   5  /* normal but significant condition */
#define LOGLEVEL_INFO     6  /* informational */
#define LOGLEVEL_DEBUG    7  /* debug-level messages */

次に、printkの出力の条件(ログレベル)を確認します。以下を実行すると、現在のログレベルが出力されます。

$ cat /proc/sys/kernel/printk
3       4       1       3

一番左の数値がコンソールのログレベルであり、その数値より小さい(3の場合は、2と1と0)のログが出力されます。つまり、この場合は、CRITとALERTとEMERGのログが出力されます。

ログレベルの変更方法

以下のようにすれば、一時的にログレベルを変更することができます(再起動すると元に戻ります)。今回は、とにかく全部のログを確認したい、という設定に変更しています。なお、ルート権限が必要なので、一時的にrootになって設定します。

$ sudo su
# echo '7 7 7 7' > /proc/sys/kernel/printk
# exit

また、コンソールに表示されない場合であっても、dmesgを使えば、全てのレベルのログが確認できます。

$ dmesg
[    0.000000] Booting Linux on physical CPU 0x0000000000 [0x410fd083]
[    0.000000] Linux version 6.1.55-v8+ (xxxx@raspberrypi) (gcc (Debian 10.2.1-6) 10.2.1 20210110, GNU ld (GNU Binutils for Debian) 2.35.2) #11 SMP PREEMPT Wed Nov 15 11:55:39 JST 2023
(以降、割愛)