Ubuntu Weekly Recipe

第578回メインラインカーネルにパッチをあてる

何度か紹介しているように、UbuntuはLinuxディストリビューションのひとつです。つまりカーネルとしてLinuxを採用しています。新しいハードウェアを使うなら新しいカーネルが必要になりますし、特定のデバイスで問題が起きるならカーネルを修正することになります。今回は最新のカーネルに修正を加えてパッケージ化する方法を紹介します。

カーネルをビルドするさまざまな方法

本連載ではこれまでにも、さまざまな方法でカーネルをビルド・インストールする手段を紹介してきました。代表的なものだけでも、次の3つの記事が存在します。

  • 第333回「カーネルパッケージをビルドしよう」
    Ubuntuカーネルのgitリポジトリもしくはカーネルソースパッケージを利用した、Ubuntuのカーネルパッケージのビルド方法。
  • 第524回「Hades Canyon/Kaby Lake GのdGPUを有効化する」
    Ubuntuのメインラインビルドを利用したビルド済み最新カーネルのインストール方法。
  • 第526回「Ubuntuで最新のカーネルをお手軽にビルドする方法」

    VanillaカーネルからDebianパッケージをビルドする方法。

個々のビルド方法についての詳細は各記事を参照してもらうことにして、まずは今回使用する方法との違いを説明しておきましょう。

Ubuntuにおいて「カーネル」と呼ぶとき、そのソースコードの取得元に応じて大まかに次のように分けられます。

Ubuntuカーネル

Ubuntuで正式に採用されているカーネルもしくは採用される予定のカーネルです。特定のカーネルバージョンに対して、各種Ubuntuに必要なパッチを適用した上で、パッケージとして提供されています。

ソースコードの取得元はkernel.ubuntu.comです。Ubuntu用なので、カーネルはUbuntuのリリースに合わせたバージョンのみが提供されます。たとえばUbuntu 19.04であればKernel 5.0ですし、Ubuntu 18.04 LTSならKernel 4.15になります(HWEなら4.18や5.0です⁠⁠。

既存のカーネルを少しだけ変更して利用したいなら、Ubuntuカーネルをベースにすることになるでしょう。第333回ではUbuntuカーネルに少し修正を加えてビルドする方法を紹介しました。

メインラインカーネル(Mainline Kernel)

Ubuntuでは不具合の原因追求や次期リリースに向けてのバージョン選定のために、メインラインカーネルと呼ばれるオリジナルのLinuxカーネルをほぼそのまま利用した、ビルドインフラストラクチャーを用意しています。

つまりKernelのmasterブランチの最新コミット特定のタグを毎日ビルドし、そのパッケージを公開しているのです。メインラインビルドは、不具合がどのリリースから発生するようになったのか、新しいリリースでは修正されているのかを追求するのに役に立ちます。リリースごとに再ビルドの必要がなく、パッケージをダウンロードしてインストールするだけでトライアンドエラーを繰り返せます。

なおメインラインカーネルは、基本的にLinux Kernelの内容そのままですが、リリースごとに「Ubuntu化」用のパッチセットも併せて公開しています。つまり利用者は比較的簡単にUbuntu化されたカーネルを入手可能です。

また第524回では、最新のカーネルでのみサポートしていたハードウェアを使うために、メインラインビルドのパッケージをインストールしました。

バニラカーネル(Vanilla Kernel)

Ubuntuによる修正がまったく加えられていないアップストリームのカーネルをバニラカーネルと呼んでいます。もちろん、このバニラカーネルをビルドすれば、Ubuntuで利用可能です。最近はMakefileにDebianパッケージを構築するターゲットが追加されているため、比較的簡単にDebianパッケージ化も可能です。

ただし各種パッチが適用されていないこと、コンフィグを自分で構築する必要があることから、Ubuntuの各種コンポーネントが動かなかったり、起動すらできないこともあります。あくまでカーネル開発者向けのオプションです。

第526回ではカーネルパッケージをビルドするひとつの方法として、バニラカーネルを利用する手法を紹介しました。

どのカーネルをベースにするかは、ユースケースに合わせて変更する必要があります。

たとえば今回は第576回で紹介したようにGPD MicroPC向けのカーネルを作ることを考えてみましょう。GPD MicroPC向けのカーネルには次の制約が存在しました。

  • 古いBIOSは5.2.0-rc5以降のカーネルを使わないと画面の回転がおかしくなる
  • 新しいBIOSは5.2.0にさらにパッチを適用しないと画面の回転がおかしくなる

執筆当時はUbuntuの開発版であるeoanでさえも、カーネルは5.0ベースでした[1]⁠。さらに新しいBIOS向けの修正はまだLinux Kernelには取り込まれていないため、パッチを適用する必要があることには変わりありません。

つまりUbuntuカーネルだとバージョンアップが必要です。なおかつメインラインカーネルにしろバニラカーネルにしろ、パッチの適用が必須であるため、メインラインカーネルのビルド済みパッケージは使えません。

第526回のようにバニラカーネルをビルドしても良いのですが、より「Ubuntuらしい」カーネルがほしかったので、今回はメインラインカーネルにパッチを適用する方法を採用します。

メインラインビルドの落とし穴

メインラインカーネルには、Ubuntuのあれやこれやの修正を適用してビルドします。実はここにさまざまな落とし穴が存在します。

  • Ubuntuパッチ適用済みのリポジトリが存在するわけではない。メインラインカーネルから必要なタグをブランチして、手作業でパッチを適用する必要がある。
  • Ubuntuパッチを適用したからといって、そのままDebianパッケージを構築できるわけではない。debian/rules cleanを実行してはじめて、Debianパッケージ用のディレクトリが構築される。
  • Ubuntuパッチを適用すると、MakefileなどにUbuntu固有のターゲットが追加されるため、そのままだと普通のカーネルのmakeに失敗する。debian/rules cleanを実行してはじめて、カーネルのビルドのための準備が整う。
  • 最近のUbuntuパッチには、メインラインビルドだと動かない仕組み(Spectre緩和用のretpolineビルドを行うためのスクリプトの修正など)がいくつか追加されている。環境変数などでこれらの仕組みを無効化する必要がある。
  • バージョンを重ねるにつれAPIが変わったり、古いAPIが削除されたりする上に、⁠5.x」という表記に対応していなかったりすることもあるため、Kernel 5.0あたりを境に、Ubuntu由来の動かなくなるものが稀にある。
  • カーネルビルド時もDKMSを使う。

要するに「うまくビルドできるまでに試行錯誤が必要」な状態です。とりあえず下準備のためにdebian/rules cleanが重要です[2]⁠。今回はできるだけ「きちんと動く」方法を紹介しますが、実際のところ「現時点では」という注釈が付くレベルで、半年後にはまた別の手順が必要になっている可能性は高いと思います。

最後の「カーネルビルド時のDKMS」は、何を言っているかよくわからないかもしれません。実はUbuntuカーネルには、本来out-of-treeとなっているモジュールのいくつかが、in-treeなモジュールとしてビルドされています。代表的なものがZFSとSPLです。

これはビルド時のスクリプトの中で、ZFSやSPLなど外部パッケージモジュールをダウンロードし、DKMSの仕組みでビルドし、その成果物をカーネルパッケージの中に組み込んでしまう仕組みです。またUbuntu Weekly Topicsの2019年5月31日号で言及されていた「NVIDIAのプロプライエタリなドライバーがインストーラーに組み込む」という話も、Ubuntuカーネルビルド時にDKMSを利用するようです。

もともとバージョン依存性の強い外部モジュールを、カーネルのビルド時に一度にビルドしてしまうため、ビルドのタイミングによっては「外部モジュール側が新しいカーネルの変更に追いついておらずビルドがこける」ことが発生してしまうのです。それどころか参照しているモジュールパッケージが存在しないこともあります。

メインラインビルド時は、後述するように、Ubuntuカーネルのいくつかのビルド機能をオフにしたほうが安全です。

ビルド環境の準備

まずはビルド環境を準備しましょう。

最初に、最低限必要になる可能性が高いパッケージをインストールします。

$ sudo apt install git build-essential kernel-package fakeroot \
    libncurses5-dev libssl-dev

次にビルド方法に応じて次のいずれかのパッケージをインストールします。

ホスト上で直接ビルドを行いたい場合

ビルドのトライアンドエラーを繰り返すなら、ホスト上で直接ビルドできるほうが確実です。よってカーネルパッケージをビルドするための一式を用意します。ただし、量が多いためストレージのサイズには余裕をもたせておきましょう。

まずはソースパッケージリポジトリを有効化しておきます。デスクトップ環境があるなら「ソフトウェアとアップデート」「Ubuntuのソフトウェア」から「ソースコード」をチェックしてください。CLIから操作するなら次のコマンドを実行すれば良いでしょう。

$ sudo sed -i "s/^# deb-src/deb-src/" /etc/apt/sources.list
$ sudo apt update

次にlinuxパッケージをビルドするために必要なパッケージ一式をインストールします。

$ sudo apt build-dep -s --no-install-recommends linux

これで準備は完了です。

パッケージとしてクリーンビルドしたい場合

前述の環境でもビルドは可能ですが、パッケージとしてクリーンビルドしたいなら、pbuilderなどを用意するのが確実です。ただしあまりトライアンドエラーには向かないので、確実にビルドできることを確認してからのほうが時間を節約できるでしょう。

クリーンビルドする場合は次のパッケージをインストールします。

$ sudo apt install packaging-dev

次にビルド用のルートファイルシステムを構築します。

$ pbuilder-dist bionic create

「bionic」はビルド対象のリリース名です。ビルドしたいリリースに応じて変更してください。

カーネルソースコードの取得

ビルド環境の準備の最後に、カーネルソースコードを取得します。今回はメインラインカーネルをビルドするので、Ubuntuのカーネルチームのリポジトリをcloneします。

$ mkdir linux && cd $_
$ git clone git://git.launchpad.net/~ubuntu-kernel-test/ubuntu/+source/linux/+git/mainline-crack linux-gpd
$ cd linux-gpd
$ git checkout -b linux-gpd v5.2

最新リリースのKernel 5.2ベースでビルドするために、v5.2をチェックアウトしています。こちらも必要に応じて別のタグだったり、masterだったりを指定してください。

パッケージとしてビルドするために、各種パッチを適用します。メインラインビルドのリリースごとのページから必要なパッチをダウンロードしてください。v5.2ならこちらになります。

$ cd ..
$ wget https://kernel.ubuntu.com/~kernel-ppa/mainline/v5.2/0001-base-packaging.patch
$ wget https://kernel.ubuntu.com/~kernel-ppa/mainline/v5.2/0002-UBUNTU-SAUCE-add-vmlinux.strip-to-BOOT_TARGETS1-on-p.patch
$ wget https://kernel.ubuntu.com/~kernel-ppa/mainline/v5.2/0003-UBUNTU-SAUCE-tools-hv-lsvmbus-add-manual-page.patch
$ wget https://kernel.ubuntu.com/~kernel-ppa/mainline/v5.2/0004-debian-changelog.patch
$ wget https://kernel.ubuntu.com/~kernel-ppa/mainline/v5.2/0005-configs-based-on-Ubuntu-5.2.0-8.9.patch
$ wget -O gpd.patch https://patchwork.freedesktop.org/patch/313782/mbox/
$ cd linux-gpd

最後にGPD MicroPC向けのパッチもダウンロードしておきます。

すべてのパッチを一通り適用しましょう。

$ git am ../000*.patch
$ git am ../gpd.patch

これでソースコード一式の準備完了です。

カーネルパッケージをビルドする

カーネルパッケージをビルドする方法は、ホスト上で直接ビルドする方法とパッケージをクリーンビルドする方法の2種類が存在します。

ホスト上で直接ビルドを行いたい場合

ホスト上で直接ビルドする場合、準備さえ行えば第333回と対して違いはありません。

まずあらかじめメインラインビルド用のオプションを有効化しておきます。

$ export do_mainline_build=true

これによりメインラインビルドでは対応しにくいいくつかの機能が無効化されます。ZFSなども無効化されるので、これが無効化されると困る場合は上記設定をせずにビルドできるよう適宜調整してください。

あとは次の手順でもろもろのパッケージをビルドするだけです。

debian/controlやビルドディレクトリのセットアップ
$ fakeroot debian/rules clean

アーキテクチャに依存するパッケージのビルド
$ fakeroot debian/rules binary-generic

アーキテクチャに依存しないパッケージのビルド
$ fakeroot debian/rules binary-indep

これにより次のようなパッケージが作られます。

linux-buildinfo-5.2.0-050200-generic_5.2.0-050200.201907072331_amd64.deb
linux-cloud-tools-common_5.2.0-050200.201907072331_all.deb
linux-doc_5.2.0-050200.201907072331_all.deb
linux-headers-5.2.0-050200-generic_5.2.0-050200.201907072331_amd64.deb
linux-headers-5.2.0-050200_5.2.0-050200.201907072331_all.deb
linux-image-unsigned-5.2.0-050200-generic_5.2.0-050200.201907072331_amd64.deb
linux-modules-5.2.0-050200-generic_5.2.0-050200.201907072331_amd64.deb
linux-source-5.2.0_5.2.0-050200.201907072331_all.deb
linux-tools-common_5.2.0-050200.201907072331_all.deb
linux-tools-host_5.2.0-050200.201907072331_all.deb

普通に使うだけなら、linux-image-unsignedとlinux-modulesのみがあれば十分です。DKMSを使うならlinux-headersも必要になります。Hyper-Vなどの上で使うなら、linux-cloud-toolsが必要になるでしょう。

ちなみにカーネルパッケージはバージョン名まで含めてパッケージ名になります。これは複数のバージョンを共存してインストールするための措置です。

パッケージとしてクリーンビルドしたい場合

パッケージとしてクリーンビルドしたい場合は、まずソースパッケージを作る必要があります。ソースパッケージを作るためにはdebianディレクトリを構築する必要があるのですが、カーネルの場合はまずdebian/rules cleanを実行する必要があるのです。

$ fakeroot debian/rules clean

あとは次のコマンドでソースパッケージを構築できます。

$ debuild -S -d --no-tgz-check -uc -us

-dオプションはBuild-Dependsなパッケージがインストールされているかどうかをチェックしないためのオプションです。これがないとapt build-dep linuxなどによりビルドに必要なパッケージをインストールしていない環境ではエラーになります。

--no-tgz-checkはソースコードのアーカイブがあるかどうかをチェックしないためのオプションです。今回はgitをクローンしたので、ソースコードのアーカイブは自前で生成する必要があります。よってこのオプションを付けて、確認なしに自動的に作ってもらいます。

-uc -usはソースパッケージやchangesファイルに署名しないオプションです。ローカルでビルドしてローカルでインストールするなら署名は不要です。最終的にPPAなどを利用する場合は、このオプションを外して署名してください。署名をする場合は、GPG鍵を用意したりdebian/changelogを更新したりといろいろ事前設定が必要です。詳細は本連載の第46回などを参照してください。

pbuilderを使う場合、メインラインビルド用の環境変数は~/.pbuilderrcに書く必要があります。

$ echo "export do_mainline_build=true" >> ~/.pbuilderrc

ようやく準備が整いましたのでビルドしてみましょう。

$ pbuilder-dist bionic build --use-network yes ../linux_5.2.0-050200.201907072331.dsc

ホストで直接ビルドするときに比べるとこちらはフルビルドになるため、時間もかかります。また、多くのパッケージが作られます。ちなみに成果物は~/pbuilder/bionic_result/に作られます。

Debianパッケージのみに限定すれば次のような感じです。

linux-buildinfo-5.2.0-050200-generic_5.2.0-050200.201907072331_amd64.deb
linux-buildinfo-5.2.0-050200-lowlatency_5.2.0-050200.201907072331_amd64.deb
linux-cloud-tools-common_5.2.0-050200.201907072331_all.deb
linux-doc_5.2.0-050200.201907072331_all.deb
linux-headers-5.2.0-050200-generic_5.2.0-050200.201907072331_amd64.deb
linux-headers-5.2.0-050200-lowlatency_5.2.0-050200.201907072331_amd64.deb
linux-headers-5.2.0-050200_5.2.0-050200.201907072331_all.deb
linux-image-unsigned-5.2.0-050200-generic_5.2.0-050200.201907072331_amd64.deb
linux-image-unsigned-5.2.0-050200-lowlatency_5.2.0-050200.201907072331_amd64.deb
linux-libc-dev_5.2.0-050200.201907072331_amd64.deb
linux-modules-5.2.0-050200-generic_5.2.0-050200.201907072331_amd64.deb
linux-modules-5.2.0-050200-lowlatency_5.2.0-050200.201907072331_amd64.deb
linux-source-5.2.0_5.2.0-050200.201907072331_all.deb
linux-tools-common_5.2.0-050200.201907072331_all.deb
linux-tools-host_5.2.0-050200.201907072331_all.deb

ホストで直接ビルドしたときと同じく、普通に使うだけなら、linux-image-unsignedとlinux-modulesのみがあれば十分です[3]⁠。DKMSを使うならlinux-headersも必要になります。Hyper-Vなどの上で使うなら、linux-cloud-toolsが必要になるでしょう。

おまけ:LXD上でビルドしたい場合

カーネルのビルドにはさまざまなパッケージのインストールが必要です。ホスト環境をできるだけきれいかつシンプルに保ちたいなら、カーネルのビルドもLXDのコンテナ上で行いたいことでしょう。

ホスト上で直接ビルドするケースなら、LXDのコンテナをそのまま利用できます。もしpbuilderを利用するケースなら、追加でコンテナに次の設定が必要です。

$ lxc config set builder security.privileged true
$ lxc config set builder security.nesting true
$ lxc restart builder

ここではコンテナ名を「builder」と仮定しています。必要に応じて変更してください。一度ビルドした環境を第574回で紹介している方法でスナップショットをとっておいたり、他のマシンにインスタンスをコピーしておけば、gitのクローンなどの時間が省けて便利です。

おすすめ記事

記事・ニュース一覧