2023-02-15

Linux kernel を自分仕様に再コンパイルしてみた

何がきっかけだったのかは忘れてしまいましたが、生まれて初めての Linux kernel の再コンパイルをすることになりました。

目的は -o2 でコンパイラーの最適化をかけることと、-march=native を指定して自分の PC に最適な kernel にすることです。

しかし、前者の -o2 での最適化については Makefile の中身を見ている時に偶然、以下の構文を発見し、さらに /boot/config-〜  には CONFIG_CC_OPTIMIZE_FOR_PERFORMANCE=y が指定されているのを発見してしまいました。
(Linux Mint 21.1 の 5.15、5.19 とも =y だったので、だいぶ前から?)

ifdef CONFIG_CC_OPTIMIZE_FOR_PERFORMANCE
KBUILD_CFLAGS += -O2

この時点でやりたかったことの半分は達成できてしまったので、時間のかかる再コンパイルに手を出すのはやめようかと思ったのですが、せっかく途中まで進めていたので最後までやり切ることにしました。

 

今回は Linux Mint 21.1 上で Linux kernel 5.19 を再コンパイルします。 

また、参考にしたのはこちらの記事です。

Debian/Ubuntuでカーネルの設定(.config)を変えて再構築(ビルド)する

 

まずは準備からです。

・ソースのダウンロード


「アップデートマネージャー」の「ソフトウェアソース」から「ソースコードリポジトリ」を有効化します。

その後、apt install linux-source-5.19.0 を実行します。

すると、/usr/src 配下に tar.gz がダウンロードされているので、それを適当な場所に圧縮解除しておきます。

今回は /home の直下に置きました。(ソースのサイズは約 30GBもありました)


・開発ツールのインストール 

本当に必要な最小限というものがわからないのですが、今回は以下をインストールしました。

clang、lld、llvm、build-essential、libncurses-dev、fakeroot、dpkg-dev

 

・コンパイル設定の配置

今回は Linux kernel の起動オプションを変更したいわけではなかったので、既存のものをそのままコピーしてきました。

cp /boot/config-$(uname -r) ~/linux-source-5.19.0/.config


・ちょっとお試し

以下のコマンドを実行すると GCC で -march=native を指定した場合のコンパイルオプションをチラ見することができます。
(適当な位置に改行を入れてます)

$ echo | gcc -E -v -march=native - 2>&1 | grep cc1

/usr/lib/gcc/x86_64-linux-gnu/11/cc1 -E -quiet -v -imultiarch x86_64-linux-gnu - -march=znver2
  -mmmx -mpopcnt -msse -msse2 -msse3 -mssse3 -msse4.1 -msse4.2 -mavx -mavx2 -msse4a -mno-fma4 
  -mno-xop -mfma -mno-avx512f -mbmi -mbmi2 -maes -mpclmul -mno-avx512vl -mno-avx512bw -mno-avx512dq 
  -mno-avx512cd -mno-avx512er -mno-avx512pf -mno-avx512vbmi -mno-avx512ifma -mno-avx5124vnniw 
  -mno-avx5124fmaps -mno-avx512vpopcntdq -mno-avx512vbmi2 -mno-gfni -mno-vpclmulqdq -mno-avx512vnni
  -mno-avx512bitalg -mno-avx512bf16 -mno-avx512vp2intersect -mno-3dnow -madx -mabm -mno-cldemote 
  -mclflushopt -mclwb -mclzero -mcx16 -mno-enqcmd -mf16c -mfsgsbase -mfxsr -mno-hle -msahf -mno-lwp 
  -mlzcnt -mmovbe -mno-movdir64b -mno-movdiri -mmwaitx -mno-pconfig -mno-pku -mno-prefetchwt1 -mprfchw 
  -mno-ptwrite -mrdpid -mrdrnd -mrdseed -mno-rtm -mno-serialize -mno-sgx -msha -mno-shstk -mno-tbm 
  -mno-tsxldtrk -mno-vaes -mno-waitpkg -mwbnoinvd -mxsave -mxsavec -mxsaveopt -mxsaves -mno-amx-tile 
  -mno-amx-int8 -mno-amx-bf16 -mno-uintr -mno-hreset -mno-kl -mno-widekl -mno-avxvnni 
  --param l1-cache-size=32 --param l1-cache-line-size=64 --param l2-cache-size=512 
  -mtune=znver2 -fasynchronous-unwind-tables -fstack-protector-strong -Wformat -Wformat-security 
  -fstack-clash-protection -fcf-protection -dumpbase -


・いざコンパイル

LLVM Clang でコンパイルする場合

time KCFLAGS="-march=native -pipe" KCPPFLAGS="-march=native -pipe" make -j 6 LLVM=1 LLVM_IAS=1 bindeb-pkg LOCALVERSION=-zen2

GCC でコンパイルする場合

time KCFLAGS="-march=native -pipe" KCPPFLAGS="-march=native -pipe" make -j 6  bindeb-pkg LOCALVERSION=-zen2

-pipe
コンパイル時に外部ファイルではなくメモリーを潤沢に使うことでコンパイル速度を向上させる指定です。
-j 6
コンパイルの並列度?なんですかね。4C8T の CPU でコンパイルしたのですが、この指定で CPU 使用率は 80% くらいでした。
bindeb-pkg
モジュールの deb パッケージだけを作ってね、という指示です。deb-pkg を指定するとソースの deb パッケージも作ってくれるそうです。
LOCALVERSION
ファイル名の末尾を指定するためのものです。普通の kernel だと -generic という部分です。uname -a の表示結果にもここで指定した値が埋め込まれます。

 

・当然のようにコンパイルエラー 

ええ、予想はしていましたが、一投目はエラーでしたよ。

make[2]: *** [debian/rules:7: build-arch] エラー 2
dpkg-buildpackage: error: debian/rules binary subprocess returned exit status 2
make[1]: *** [scripts/Makefile.package:83: bindeb-pkg] エラー 2
make: *** [Makefile:1556: bindeb-pkg] エラー 2

最初は GCC ではなく LLVM Clang でコンパイルしたのですが、これが良くなかったのかと思い、二投目からは GCC に切り替えました。
ただ、/boot/config の先頭に GCC-12 でコンパイルしたという記載があったのですが、Linux Mint 21.1 は Ubuntu 22.04 相当なので GCC-11 です。これは update-alternatives で GCC のシンボリックリンク先を GCC-12 に付け替えてます。

 

さて、このエラーはコンパイラーの種類には関係がなく、最終的には .config 内の以下の 2箇所を修正することで解消しました。

CONFIG_SYSTEM_TRUSTED_KEYS="debian/canonical-certs.pem"
CONFIG_SYSTEM_REVOCATION_KEYS="debian/canonical-revoked-certs.pem"

CONFIG_SYSTEM_TRUSTED_KEYS=""
CONFIG_SYSTEM_REVOCATION_KEYS=""

エラーメッセージから原因を推測することはかなり困難です。もう先人の知恵を検索で探すしかありません。


・なかなかコンパイルが終わらない

1時間ほどしてコンパイルが終わったっぽい所までは見届けました。building package ・・・というメッセージが登場してきました。

  HDRINST usr/include/asm/poll.h
  HDRINST usr/include/asm/sockios.h
  HDRINST usr/include/asm/ioctls.h
  INSTALL debian/linux-libc-dev/usr/include
dpkg-deb: building package 'linux-libc-dev' in '../linux-libc-dev_5.19.17-zen2-1_amd64.deb'.
dpkg-deb: building package 'linux-image-5.19.17-zen2' in '../linux-image-5.19.17-zen2_5.19.17-zen2-1_amd64.deb'.
dpkg-deb: building package 'linux-image-5.19.17-zen2-dbg' in '../linux-image-5.19.17-zen2-dbg_5.19.17-zen2-1_amd64.deb'.
 dpkg-genbuildinfo --build=binary -O../linux-upstream_5.19.17-zen2-1_amd64.buildinfo
 dpkg-genchanges --build=binary -O../linux-upstream_5.19.17-zen2-1_amd64.changes
dpkg-genchanges: info: binary-only upload (no source code included)
 dpkg-source --after-build .
dpkg-buildpackage: info: binary-only upload (no source included)

real	136m31.276s
user	454m11.392s
sys	57m10.345s

しかし、3つ目の dpkg-deb: building package 'linux-image-5.19.17-zen2-dbg' という部分がなかなか終わらないのです。time コマンドの出力結果から 136分で終わったみたいですが、何かが失敗したのかと思って Ctrl + C を押すしかないかと諦めかけたところ、1スレッドだけを使用して細々と何かをしているのが確認できたので、そのまま放置しておいたら終わったみたいです。

通常の linux-image- のサイズが 102MB 程度でしたが、linux-image-dbg のサイズは 1.1GB と巨大な deb モジュールが出来上がっていました。時間がかかるわけですね。

 

・では、インストール

無事にコンパイルが終わったみたいなのですが、出来上がったはずの deb ファイルが見当たりません。


ずっと make コマンドを実行した linux-source 配下のディレクトリーを探していたのですが、なんと、その上のディレクトリーに deb ファイルが置かれているではありませんか。
(ん?もしかして make コマンドをここで実行したかな?)

さて、捜索が完了したので dpkg コマンドでインストールします。headers、libc をインストール後、最後に image という順番でインストールする必要があるようです。

$ sudo dpkg -i linux-headers-5.19.17-zen2_5.19.17-zen2-1_amd64.deb 
以前に未選択のパッケージ linux-headers-5.19.17-zen2 を選択しています。
(データベースを読み込んでいます ... 現在 629873 個のファイルとディレクトリがインストールされています。)
linux-headers-5.19.17-zen2_5.19.17-zen2-1_amd64.deb を展開する準備をしています ...
linux-headers-5.19.17-zen2 (5.19.17-zen2-1) を展開しています...
linux-headers-5.19.17-zen2 (5.19.17-zen2-1) を設定しています ...

$ sudo dpkg -i linux-libc-dev_5.19.17-zen2-1_amd64.deb 
(データベースを読み込んでいます ... 現在 646151 個のファイルとディレクトリがインストールされています。)
linux-libc-dev_5.19.17-zen2-1_amd64.deb を展開する準備をしています ...
linux-libc-dev:amd64 (5.19.17-zen2-1) で (5.15.0-60.66 に) 上書き展開しています ...
linux-libc-dev:amd64 (5.19.17-zen2-1) を設定しています ...

$ sudo dpkg -i linux-image-5.19.17-zen2_5.19.17-zen2-1_amd64.deb 
以前に未選択のパッケージ linux-image-5.19.17-zen2 を選択しています。
(データベースを読み込んでいます ... 現在 646162 個のファイルとディレクトリがインストールされています。)
linux-image-5.19.17-zen2_5.19.17-zen2-1_amd64.deb を展開する準備をしています ...
linux-image-5.19.17-zen2 (5.19.17-zen2-1) を展開しています...
linux-image-5.19.17-zen2 (5.19.17-zen2-1) を設定しています ...
 * dkms: running auto installation service for kernel 5.19.17-zen2                                               [ OK ] 
update-initramfs: Generating /boot/initrd.img-5.19.17-zen2
W: Possible missing firmware /lib/firmware/amdgpu/ip_discovery.bin for module amdgpu
W: Possible missing firmware /lib/firmware/amdgpu/vega10_cap.bin for module amdgpu
W: Possible missing firmware /lib/firmware/amdgpu/sienna_cichlid_cap.bin for module amdgpu
W: Possible missing firmware /lib/firmware/amdgpu/navi12_cap.bin for module amdgpu
W: Possible missing firmware /lib/firmware/amdgpu/aldebaran_cap.bin for module amdgpu
W: Possible missing firmware /lib/firmware/amdgpu/gc_11_0_1_imu.bin for module amdgpu
W: Possible missing firmware /lib/firmware/amdgpu/gc_11_0_1_rlc.bin for module amdgpu
W: Possible missing firmware /lib/firmware/amdgpu/gc_11_0_1_mec.bin for module amdgpu
W: Possible missing firmware /lib/firmware/amdgpu/gc_11_0_1_me.bin for module amdgpu
W: Possible missing firmware /lib/firmware/amdgpu/gc_11_0_1_pfp.bin for module amdgpu
W: Possible missing firmware /lib/firmware/amdgpu/gc_11_0_0_toc.bin for module amdgpu
W: Possible missing firmware /lib/firmware/amdgpu/sdma_6_0_1.bin for module amdgpu
W: Possible missing firmware /lib/firmware/amdgpu/sienna_cichlid_mes1.bin for module amdgpu
W: Possible missing firmware /lib/firmware/amdgpu/sienna_cichlid_mes.bin for module amdgpu
W: Possible missing firmware /lib/firmware/amdgpu/navi10_mes.bin for module amdgpu
W: Possible missing firmware /lib/firmware/amdgpu/gc_11_0_1_mes1.bin for module amdgpu
W: Possible missing firmware /lib/firmware/amdgpu/gc_11_0_1_mes.bin for module amdgpu
Sourcing file `/etc/default/grub'
Sourcing file `/etc/default/grub.d/50_linuxmint.cfg'
Sourcing file `/etc/default/grub.d/init-select.cfg'
Generating grub configuration file ...
Found linux image: /boot/vmlinuz-5.19.17-zen2
Found initrd image: /boot/initrd.img-5.19.17-zen2
Found linux image: /boot/vmlinuz-5.19.0-28-generic
Found initrd image: /boot/initrd.img-5.19.0-28-generic
Found linux image: /boot/vmlinuz-5.15.0-60-generic
Found initrd image: /boot/initrd.img-5.15.0-60-generic
Warning: os-prober will be executed to detect other bootable partitions.
Its output will be used to detect bootable binaries on them and create new boot entries.
Found Windows Boot Manager on /dev/sda1@/EFI/Microsoft/Boot/bootmgfw.efi
Adding boot menu entry for UEFI Firmware Settings ...
done

おお、GRUB のエントリーはどうしたらいいのかと心配していましたが、勝手に追加してくれました。ステキです。

 

・で、どうなの?

CPU の最大クロックやら governor を変更するために cpupowerコマンドを愛用していたのですが、これが動かなくなりました。「linux-tools-zen2 をインストールしろ」というエラーが出てしまいますが、そんなパッケージはこの世に存在していません。

代替策として cpufrequtils をインストールして事なきを得ましたが、こちらは CPU のコア毎に指示を出してあげないといけないようで、ちょっと使いづらかったです。

sysbench で generic 版と zen2 版の比較をしてみたのですが、これもまた微妙な結果でした。

なんかブラウザーの動作がキビキビした気もしますが、これは先ほどの cpupower で最大クロックが制御できていなかったからのような気もします。
(だったら最大クロックを上げてやればよいのでは?と思い始めました)

Linux Mint に新しい kernel が配信される度に、この 2〜3時間かかる再コンパイル作業をし続けるのも面倒だなぁ、というのが正直なところです。
(何かモチベーションがあれば続けられそうですが・・・)