Linuxカーネルビルド大全

# はじめに

（本記事は [Linux Advent Calendar 2020](https://qiita.com/advent-calendar/2020/linux) および [東京大学 品川研究室 Advent Calendar 2020](https://qiita.com/advent-calendar/2020/shina-lab)の11日目の記事として書かれました。）

カーネルのビルドについての情報はググると色々と出てきますが、「ここを見ればOK」と思えるサイトに巡り会えていないので、自分で書いてみることにしたのが本記事です。
いずれLinuxカーネルをビルドする必要にかられるであろう研究室・学科の後輩や、忘れっぽい将来の自分のためにも、改めてLinuxカーネルのビルド方法についてまとめてみたいと思います。

# 概要

さて、「カーネルをビルドしよう」という状況に置かれた場合、実際にすべきことは主に以下の４点だと思います。

- [ソースコードの取得](#ソースコードの取得)
- [ビルド環境の構築](#ビルド環境の構築)
- [カーネルコンフィグの準備](#カーネルコンフィグの準備)
- [ビルド及びインストール](#ビルド及びインストール)

そこで本記事では、カーネル本体のビルド上記の４点の他、関連して以下のトピックについても解説を行おうと思います。

- [カーネルモジュールのビルド](#カーネルモジュールのビルド)
- [ドキュメントのビルド](#ドキュメントのビルド)
- [付属ツールのビルド](#付属ツールのビルド)
- その他雑多トピックいくつか
 
### 環境設定
なお、本記事はx86_64を想定しており、他のアーキテクチャでの確認は行っておりません。
使用したディストリビューション及びカーネルのバージョンは以下のとおりです。

- Ubuntu20.04(カーネル: 5.4.0)
- Fedora33(カーネル: 5.9.12)



### コマンド例

それぞれの説明に入る前に、カーネルビルドの具体的なコマンドを手っ取り早く知りたい人（将来の自分）の為に一連の流れを置いておきます。
##### ubuntuでの例
```bash
$ git clone https://github.com/torvalds/linux.git # ソースコードを取得
$ cd linux
$ git checkout バージョン # ビルドしたいバージョンにcheckout

$ sudo sed -i 's/# deb-src/deb-src/' /etc/apt/sources.list # build-depのためソースパッケージを有効化
$ sudo apt update # build-depのためソースパッケージを有効化
$ sudo apt build-dep -y linux # カーネルビルドに必要なパッケージをインストール
$ sudo apt install libncurses-dev # make menuconfigに必要な追加パッケージをインストール

$ mkdir ../build # out-of-treeビルド（ソースコードと生成物を分離したビルド）の為のディレクトリを作成
$ make olddefconfig O=../build # 既存のコンフィグをコピー＆ビルド用ディレクトリに必要なものを生成
$ cd ../build # 以降、ソースコードの変更を伴わない場合、基本的にはビルド用ディレクトリで作業
$ make localmodconfig # 不要なモジュールを無効化
$ make menuconfig # 個別に機能の選択 

$ LOCALVERSION=-mybuild make -j8 # カーネル本体＆モジュールのビルド
$ sudo make modules_install # モジュールのインストール
$ sudo make install # カーネル本体のインストール(GRUBのエントリ生成もしてくれる）

$ sudo reboot # 再起動後、必要に応じてgrubでビルドしたカーネルを選択
```

# ソースコードの取得

カーネルのソースは様々な場所で手に入れる事ができますが、基本的には大本のソースを取得するか、各ディストリ向けにパッチの当たったソースを取得して使うかのどちらかだと思います。

- [アップストリームソースの取得](#アップストリームソースの取得)
- [Ubuntu向けソースの取得](#ubuntu向けソースの取得)
- [Fedora向けソースの取得](#fedora向けソースの取得)

### アップストリームソースの取得
#### The Linux Kernel Archivesからtarballを取得する
Linuxカーネルのソースコードを入手する方法として最も簡単なのは、
[The Linux Kernel Archives(kernel.org)](https://www.kernel.org/)からtarballをダウンロードするということだと思います。
トップページには主要なバージョンしかありませんが、https://kernel.org/pub/linux/kernel/ を探せば各バージョンのソースが手に入ります。

tarballを取得する方法は、gitで取得する場合と比べて格段に少ない転送量で済むので、手っ取り早くソースコードを取得したい場合は、この方法を利用すると良いでしょう。

### gitでリポジトリをｃloneする
tarballを取得する方がシンプルですが、開発を行う上ではやはりgitのリポジトリを取得したくなると思います。


Linuxカーネルに関わるgitのリポジトリは
https://git.kernel.org
にまとまっています。
ここにはカーネル本体以外や、開発の為のフォーク等が大量にあり混乱しますが、基本的にはLinux開発者の筆頭であるLinus Torvaldsが管理する以下のリポジトリをcloneすると良いでしょう。

https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git

なお、このURLは`git clone`にも使えますが、ブラウザで開くと以下のようにリポジトリの状況を確認することができます。
![スクリーンショット 2020-11-29 19-26-26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/156680/6bdbc726-69f6-f581-6930-28c627b779a3.png)

また、Linusの管理するリポジトリはGithubにもミラーがあるので、Githubでカーネルのコードを管理したい場合は、こちらからフォークするのも良いでしょう。
https://github.com/torvalds/linux

なお蛇足かとは思いますが、cloneしてきたmastarブランチは、直接ビルドに使うには不安定なため、適切なバージョン（tag）にcheckoutしてから使うと良いでしょう。

### Ubuntu向けソースの取得

Ubuntuを始めとした各ディストリビューションが使うカーネルは、独自のパッチを当たっている事があります。
そのため、特にメインストリームのカーネルを使う理由がない場合は、ディストリビューションの開発・提供するソースを使うのが良いと思います。

ここでは、 aptでlinuxのソースパッケージを取得する方法とUbuntu開発チームのリポジトリをgitでcloneする方法を紹介します。

#### aptを用いたUbuntu向けソースの取得

aptでのlinuxソースの取得も実は以下2種類あります。

- linux-source-* パッケージを `sudo apt install` で入れる方法
- linux ソースパッケージを `apt source`で取得する方法


##### linux-source-* パッケージを `sudo apt install` で入れる方法
ここでは5.4.0をインストールすることとします。
全バージョンが提供されているわけではないので、欲しいバージョンが存在するか [packages.ubuntu.com](https://packages.ubuntu.com/search?keywords=linux-source&searchon=names&suite=all&section=all) で検索して探してみてください。

```bash
sudo apt install linux-source-5.4.0
```

こうすると、Filesystem Hierarchy Standardに従って /usr/src/以下に linux-source-5.4.0.tar.bz2が落とされます。
使う場合は適宜解凍してください。

※ この方法で取得したソースコードに実際にUbuntu独自のパッチが当たっているかは直接確認できてはいません。いつか確認するつもりですが、その辺りの情報をご存知の方がいたらコメント頂けると嬉しいです。


##### linux ソースパッケージを `apt source`で取得する方法

正直理解が怪しいですが、apt系のパッケージ管理システムには、
パッケージのビルドに必要なファイルを取得するための仕組みがあり、
その仕組みを利用してlinuxのソースコードを取得するのがこの方法です。

この仕組みを使うためにはまず、aptのリポジトリを編集して deb-srcから始まるURLを有効化する必要があります。

```bash
$ sudo sed -i.bak 's/# deb-src/deb-src/' /etc/apt/sources.list # コメントアウトを外してソースパッケージを有効化
$ sudo apt update
```


さて、準備ができた所で以下のコマンドを実行すると、___カレントディレクトリ___ にLinuxのソースコードが落とされます。

```bash
$ apt source linux # sudoは不要
略
dpkg-source: info: extracting linux in linux-5.4.0
dpkg-source: info: unpacking linux_5.4.0.orig.tar.gz
dpkg-source: info: applying linux_5.4.0-56.62.diff.gz
dpkg-source: info: upstream files that have been modified:
略
```

今回はUbuntu20.04で試したのですが、以下の4つがカレントディレクトリに落とされていました。

- linux-5.4.0/ 
- linux_5.4.0-56.62.diff.gz
- linux_5.4.0-56.62.dsc
- linux_5.4.0.orig.tar.gz

特に理由がない場合は下３つのファイルは削除してよいと思います。

#### gitを用いたUbuntu向けソースの取得
例えば、Ubuntu20.04(focal)向けのUbuntu開発チームのLinuxソースは以下のリポジトリからcloneすることができます。

```
git://git.launchpad.net/~ubuntu-kernel/ubuntu/+source/linux/+git/focal 
```
なお、僕が試した際は、リクエストが詰まっているのか、コマンドを実行してから実際にcloneが始まるまで数分の待ちが発生しました。
スループット自体は10Mbps程度出ていましたが、Github等からアップストリームのリポジトリをcloneしてから `git remote add` で上記リポジトリを追加するなり、`git clone --reference --dissociate` を使って転送量を減らすのが無難かと思います。

### Fedora向けソースの取得

#### dnfを用いたFedora向けソースの取得
Ubuntuのaptでのソースの取得と同じ様に、dnfにもrpmパッケージのソースを取得する方法があり、それを使います。

```bash
$ dnf download --source kernel
$ ls
kernel-5.9.12-200.fc33.src.rpm 
```

ダウンロードしたrpm(ソース)パッケージはインストールするとHOMEディレクトリにrpmbuildディレクトリを作成します。
linuxのソースやパッチはその中に含まれています。
なお、rpmでのインストール時に各種warningが出るかと思いますが、無視して大丈夫でした。

```
$ rpm -i kernel-5.9.12-200.fc33.src.rpm 
$ cd ~/rpmbuild
$ ls
SOURCES SPECS
$ ls SURCES
略
linux-5.9.tar.xz
略
patch-5.9.12.xz
略
```
これらのファイルを直接使うこともできますが、
tarball展開及びパッチの適用を自分でするのは少し面倒なので、以下のようにして適用します。

```bash
$ rpmbuild -bp ~/rpmbuild/SPECS/kernel.spec 
# 出力を見ると展開され、パッチが当たっていることが分かる。
$ ls ~/rpmbuild/BUILD/kernel-5.9.fc33/
linux-5.9.12-200.fc33.x86_64  vanilla-5.9
```

以上で、ソースの準備は完了です。
ただし、直接ビルドしようとすると、「The source tree is not clean, please run 'make mrproper'」
と怒られたので、 `make mrproper` [^mrproper] を実行してからコンフィグ設定＆ビルドをすると良いでしょう。

[^mrproper]: mrproperは現地の洗剤のキャラクターであるMr.properというおじさんの名前らしい。 linuxビルド界隈？ーーがあるとすればーーでは言わずと知れた存在だと思いますが、こういうのはなんだかなぁという気がする。

補足：バージョンを指定する場合は以下のように探してダウンロードできます。
ただ、rpmのリポジトリが有効になっていないせいなのか？よくわかりませんが、
出てくるバージョンは少ないし、centos8で試しても上手く行かなかったので、その点はご了承ください...

```
$ dnf repoquery kernel
Last metadata expiration check: 0:00:04 ago on Fri 11 Dec 2020 03:12:43 AM UTC.
kernel-0:5.8.15-301.fc33.x86_64
kernel-0:5.9.12-200.fc33.x86_64
$ dnf download --source kernel-5.8.15
````



#### gitを用いたFedora向けソースの取得
前述のdnfでのソースの取得の際に用いた [kernelパッケージの公式ページらしきサイト](https://src.fedoraproject.org/rpms/kernel)
を見てみると、
> The kernel is maintained in a [source tree](https://gitlab.com/cki-project/kernel-ark) rather than directly in dist-git.

というわけで、GitLabのリンクを示されます。
各ブランチやtagの細かい意味まではわかりませんが、ここをcloneすれば良いでしょう。


# ビルド環境の構築
さて、ソースコードを手に入れたら次はカーネル開発に必要なパッケージをインストールする必要があります。
### Ubuntuでのカーネルビルド環境の構築
Ubuntuでは、各パッケージの開発に必要なパッケージをインストールするために、`apt build-dep` というコマンドを使うことができます。
ただし、より新しいバージョンのカーネルをビルドする際やmakeのターゲットに依っては追加のパッケージが必要になるので、適宜インストールしてください。

```bash
sudo sed -i.bak 's/# deb-src/deb-src/' /etc/apt/sources.list # ソースパッケージの有効化
sudo apt update
sudo apt build-dep -y linux # linuxの開発に必要なパッケージをインストール。
sudo apt install -y fakeroot # make bindeb-pkg等に必要
sudo apt install -y libncurses-dev # make menuconfigに必要
```

実際に、必要なパッケージが足りていない場合は、makeの出力を見れば分かるようになっています。
例えば、 `apt build-dep linux` をしただけで `make menuconfig` を実行するとncursesのパッケージが足りないので以下のような表示が出ます。

```
$ make menuconfig
略
* Unable to find the ncurses package.
* Install ncurses (ncurses-devel or libncurses-dev
* depending on your distribution).
略
``` 
`*-devel` はrpmパッケージ、`*-dev` はdebパッケージなので
Ubuntuの場合は`libncurses-dev`をインストールします


### Fedoraでのカーネルビルド環境の構築
Ubuntuでは `apt build-dep linux`を使ったのと同様に、
以下のコマンドを実行すればdnfでのビルド環境は構築できます。

```bash
sudo dnf builddep -y kernel
```

僕が試した限りでは、これだけでもビルドは可能でしたが、
`kernel-devel` パッケージも提供されており、それをインストールする旨を書いている記事も見るので、
インストールしておくと無難かなと思います。
ひょっとすると、builddepで入るパッケージの中に既にあるかも知れませんが...

# カーネルコンフィグの準備
※___注意 [^1]___
 
[^1]: このセクションでは、out-of-treeビルドは考慮しない説明をしています。必要がある場合は [out-of-treeビルド](#out-of-treeビルド)の章を適宜参照してください。



カーネルのビルドをする際は、ビルドを行うディレクトリに設定ファイル（`.config`)を置く必要があります。
また、このファイルを書き換えることで、ビルドするカーネルの各種機能の有効化/無効化を行います。
### 現在のconfigをコピーして使う
カーネルコンフィグには、基本的には現在利用しているカーネルをベースとすると簡単です。

`/boot/config-[カーネルバージョン]`　にあるので、
ビルドを行うマシンとインストールするマシンが同じ場合は、以下のようにしてコピーします

```bash
cp /boot/config-$(uname -r)  /path/to/build/dir
```
また、 ビルドを行うディレクトリにコンフィグファイルが存在しない場合、
`make oldconfig` や `make olddefconfig` 等を実行することでも現在利用中のコンフィグをコピーするとができます。

### config変更用のMakeターゲットを使う


異なるバージョンのカーネルをビルドする場合や、特定の機能を有効化/無効化したい場合は
現在利用しているカーネルコンフィグのコピーをそのまま使うことができません。
`.config`をエディタで開いて編集することも可能ではありますが、やや面倒です。
そこで、カーネルのMakefileが用意している各種ターゲットを使うと便利です。
`make help` とすればターゲットの一覧と説明を見ることができます。
個人的には以下の順で実行する場合が多いので、それぞれ説明します。

```
$ make olddefconfig
$ make localmodconfig
$ make menuconfig
```

#### make oldconfig と make olddefconfig
このMakeターゲットは、既存のコンフィグ（なければ/boot/以下にある現在のカーネルコンフィグ）を元に新しいコンフィグを作る際に使います。
既存のコンフィグから判断できないものはCLIで 以下のように選択します。

```
AMD 8111 GPIO driver (GPIO_AMD8111) [N/m/y/?] n
BT8XX GPIO abuser (GPIO_BT8XX) [N/m/y/?] (NEW) n
OKI SEMICONDUCTOR ML7213 IOH GPIO support (GPIO_ML_IOH) [N/m/y/?] n
ACCES PCI-IDIO-16 GPIO support (GPIO_PCI_IDIO_16) [N/m/y/?] n
ACCES PCIe-IDIO-24 GPIO support (GPIO_PCIE_IDIO_24) [N/m/y/?] n
```
`make oldconfig` のほうが一般的かと思いますが、 `make olddefconfig` は可能な限りデフォルト値に設定してくれて楽なので、僕は重宝しています。

#### make localmodconfig
カーネルのビルドを行う際は、同時にカーネルモジュールをビルドすることが基本ですが、
実際には利用しないモジュールのビルドに多くの時間がかかってしまうことも多いです。
そこで、`make localmodconfig` を利用することで、現在使われていないモジュールのビルドを無効化することができます。

#### make menuconfig
`make menuconfig`を使うと、以下のようにグラフィカルに各機能の設定をすることができます。
基本的には見た通り使えると思いますが、Enterキーは下部の Select, Exit, Help, Save, Loadの実行に使われるので、機能のトグルはスペースキーを使うという事は認知しておくと良いと思います。
![menuconfig.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/156680/37c3328f-a6cf-de28-d9d0-5a4b4ea55f48.png)



### ソースに付属しているスクリプトで個別にconfigを設定する

Linuxのソースツリーの `scripts` ディレクトリ配下には、様々な有用なスクリプトがあるのですが、
`scripts/config`は変更したいカーネルコンフィグ名が既知の場合に有用です。
今回は例として `DEBUG_INFO` を変更してみます。

```bash
$ grep 'DEBUG_INFO' .config              # 現状確認
CONFIG_DEBUG_INFO=y
# CONFIG_DEBUG_INFO_REDUCED is not set
# CONFIG_DEBUG_INFO_SPLIT is not set
CONFIG_DEBUG_INFO_DWARF4=y
# CONFIG_DEBUG_INFO_BTF is not set
$ ./scripts/config --disable  DEBUG_INFO # 無効化
$ grep 'DEBUG_INFO' .config              # 無効化を確認
# CONFIG_DEBUG_INFO is not set
# CONFIG_DEBUG_INFO_REDUCED is not set
# CONFIG_DEBUG_INFO_SPLIT is not set
CONFIG_DEBUG_INFO_DWARF4=y
# CONFIG_DEBUG_INFO_BTF is not set
$ ./scripts/config --enable  DEBUG_INFO  # 有効化
$ grep 'DEBUG_INFO' .config              # 有効化を確認
CONFIG_DEBUG_INFO=y
# CONFIG_DEBUG_INFO_REDUCED is not set 
# CONFIG_DEBUG_INFO_SPLIT is not set
CONFIG_DEBUG_INFO_DWARF4=y
# CONFIG_DEBUG_INFO_BTF is not set
```

なお、out-of-treeビルドの際は、`--file`オプションでビルド用ディレクトリにある `.config`を直接指定するか、
ビルド用ディレクトリにスクリプト自体をコピーする必要があるので注意してください。


# ビルド及びインストール

カーネルのビルド＆インストール方法は複数ありますが、ここでは以下３つの方法を紹介します。

- ビルド、インストール共にmakeで行う方法
- debパッケージをビルドして、aptやdpkgでインストールする方法
- targzパッケージをビルドしてインストールする方法


### ビルド、インストール共にmakeで行う方法
```bash
$ # コンフィグの生成等は省略
$ LOCALVERSION=-dev make -j8
$ sudo make modules_install 
$ sudo make install
```

基本的にはこれで十分です。
ただ、GRUB（ブートローダー）の設定に依っては再起動しても利用されるカーネルが変わらないという事もあるので[^grub]、そういう場合はGRUBについて確認してみましょう。

[^grub]: 僕が試した例だと、fedoraは再起動後上手くカーネルが切り替わりましたが、ubuntuの場合はgrub-reboot等を使っても変わらず、最終的にコンソールからGRUBメニューを表示して手動で選択することで切り替えられました。


### debパッケージをビルドして、aptやdpkgでインストールする方法
Linuxでは、標準でdebパッケージをビルドするためのターゲットがついています。


### targz形式のパッケージをビルドして、インストールする方法
targz形式のパッケージをビルドすると、中には以下のファイルが含まれています。
- /boot/System.map-*
- /boot/config-*
- /boot/vmlinuz-*
- /boot/vmlinux-*
- /lib/modules/*/ ディレクトリ(モジュール等から構成される）

これらをインストールしたい場合、/bootや/lib/modules以下に配置し、ブートローダーの設定をアップデートを行います。

ファイルは単純にコピーすればよく、GRUBについては`update-grub`(ubuntu)や`grubby`等GRUB関連のコマンド実行することになります。

アドベントカレンダーということで、締切が来てしまったので、GRUB関連のコマンドについての詳細は後日追記します...

### インストールの確認
再起動後に、実際にビルドしたカーネルが使われているかどうかは、`uname -r`を実行することで確認します。 `LOCALVERSION`等で指定したバージョンが見えれば確認完了です。


# カーネルモジュールのビルド

さて、これまでカーネル本体のビルド方法を確認してきましたが、実際にはカーネルに手を加えたい場合は、カーネルモジュールとして機能をまとめる方が一般的かと思います。

ここでは例として、モジュールのロード・アンロード時にメッセージを出力する簡単なコードをモジュール化することを考えます。

```c:hello.c
#include <linux/module.h>

MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("author <author@example.com>");
MODULE_DESCRIPTION("Hello module");

static int __init hello_module_init(void) {
	printk(KERN_INFO "Hello module is loaded\n");
	return 0;
}

static void __exit hello_module_exit(void) {
	printk(KERN_INFO "Hello module is removed\n");
}

module_init(hello_module_init);
module_exit(hello_module_exit);
```

### カーネルモジュール単体でのビルド
カーネルソース自体がない場合でも
`/lib/modules/$(shell uname -r)/build` にあるMakefileやリソースを活用することでカーネルモジュールをビルドできます。

以下のMakefileを先程のhello.cと同じディレクトリに置けば準備は完了です。

```makefile:Makefile
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)

obj-m := hello.o

.PHONY: all clean install
all:
	make -C $(KDIR) M=$(PWD) modules

clean:
	make -C $(KDIR) M=$(PWD) clean

install:
	make -C $(KDIR) M=$(PWD) modules_install
```

それでは実際にビルドしてみましょう

```bash
$ mkdir hello_module
$ cd hello_module
$ vim hello.c # 上記ソースコードを生成
$ vim Makefile # 上記Makefileを生成
$ make 
$ sudo make install
```
これでインストールできると思います。
ビルド済みのカーネルモジュールは `/lib/modules/$(uname -r)/extra/` にインストールされているはずなので、
以下のようにしてモジュールの確認ができるはずです。


```
$ sudo insmod /lib/modules/$(uname -r)/extra/hello.ko
$ sudo rmmod hello
$ sudo dmesg
略
[   33.438701] RPC: Registered tcp NFSv4.1 backchannel transport module.
[   33.464984] FS-Cache: Netfs 'nfs' registered for caching
[  815.217285] hello: loading out-of-tree module taints kernel.
[  815.217563] hello: module verification failed: signature and/or required key missing - tainting kernel
[  815.219284] Hello module is loaded
[  830.556290] Hello module is removed
```



### カーネル本体と合わせたモジュールビルド
自分でカーネル本体もビルドし、そのカーネルと共に使うカーネルモジュールをビルドしたい場合基本的には同じ流れとなります。
まずはカーネル本体をビルドし、その際に利用したディレクトリ（out-of-treeビルドなら `O=` で指定したディレクトリ、そうでなければソースのディレクトリ）のパスに前に示したモジュールビルド用のMakefileのKDIRを書き換えます。

```diff:Makefileのdiff
- KDIR := /lib/modules/$(shell uname -r)/build
+ KDIR := /path/to/linux/build
```

後は同様に `make`、 `sudo make install`すれば新しいカーネル向けのモジュールのビルド＆インストールが完了します。


# ドキュメントのビルド
Linuxのソースツリーの `Documentation` ディレクトリ配下には、多数ドキュメントが存在しており、
それらはmakeを使うことでPDFやHTML等としてビルドすることが可能です。

なお生のドキュメントは　https://www.kernel.org/doc/Documentation/ に、
HTMLとして生成されたドキュメントは https://www.kernel.org/doc/html/latest/ でそれぞれ見ることができます。
 

さて、具体的なビルドの手順についてですが、どうしても大量のwarningやerrorが消せなかったので、
Ubuntu20.04✕linux 5.4.0ソースでpdfdocsの生成を試した手順のみ
#### pdfdocsビルド手順
まず、以下のように追加で必要のパッケージをインストールします。

```bash
$ sudo apt install imagemagick graphviz
```
ただ、imagemagickの脆弱性の為、機能が制限されてビルドに失敗するため、以下の様に制約をコメントアウトして外します。

```xml:/etc/ImageMagick-6/policy.json
<!--以下の行をコメントアウトする-->
<policy domain="coder" rights="none" pattern="PDF" /> 
```

後は↓を実行すればできるはず。

```$
make pdfdocs
```

成功するとこんな感じで↓各項目についてのドキュメントが `/Documentation/outputs`以下に生成されます。
![pdfdocs.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/156680/c81064e9-9712-7906-2cfc-68b0e4c2eac1.png)
(targzパッケージを生成後だったので、LOCALVERSIONがそのままになってしまっている...)


# 付属ツールのビルド
Linuxカーネルには、カーネルやモジュール、ドキュメントの他に、perf等ユーザーが使えるツールも付属しており、`tools`ディレクトリ配下に置いてあります。

tools ディレクトリで`make all` を実行すると全ツールがビルドされ、`make perf` の様に、ツール名をしてして個別にビルドすることも可能な他、`make help`を実行すれば説明が見れます。

ただ、toolsのビルドは他のビルドと異なり、`O=` によるout-of-treeビルドはサポートしていないので注意してください。

# 落ち穂拾い
### out-of-treeビルド
linuxカーネルをビルドする際、特に何も考えずソースツリーの中でmakeを実行すると、.configや.o等がソースツリーの中に生成されて散らかってしまいます。
そこでビルドの生成物を出力するためのデレクトリを別に作成してビルドすることで、ソースツリーが汚れることを防ぎます。

具体的には、makeの実行時に `O=` で生成物出力用のディレクトリを指定してやることで実現できます。

```bash 
$ cd linux
$ mkdir ../build
$ make oldconfig O=../build
$ make menuconfig O=../build
$ make -j8 O=../build
$ sudo make modules_install O=../build
$ sudo make install O=../build
```

ただ、この方法では、毎回 `O=` を指定する必要があり少し面倒です。
別の方法として、 `O=` の代わりに環境変数`KBUILD_OUTPUT`にビルド用ディレクトリのパスを設定することも可能です。
しかしこの方法も、 `sudo` 時に環境変数が引き継がれるかどうかを気にする必要があります。

ではどうすると良いかと言うと、実は１度 `O=` を付けてビルドすると、その先のディレクトリにもMakefile等が生成されるという機能を活用します。
１度`O=`付きでmakeを実行し、以後そのディレクトリでmakeを叩けばソースディレクトリを汚さすに済みます。

```bash 
$ cd ./linux
$ mkdir ../build
$ make oldconfig O=../build
$ cd ../build
$ make menuconfig
$ make -j8
$ sudo make modules_install
$ sudo make install O=../build
```

とはいえ、ついうっかりソースツリーでmakeを実行してしまい、汚してしまう可能性もあります。
そういうミスを防ぐために、僕はソースツリーを 読み込み専用のbind mountで保護しています。

```bash
$ sudo mount --bind -r ./linux ./linux
```

dockerを使ってビルドする際は `-v` オプションの末尾に `:ro` を付けることで読み込み専用のbind mountを利用できます。

```bash
$ docker run -v $PWD/linux:/usr/src/linux:ro .....
```


### GRUBの設定
`sudo make install`やdebパッケージをインストールするだけでは、次回起動時に自動で新しいカーネルが選択されない場合もあります。
その時はGRUBの設定を確認しましょう。

#### GRUBメニューでの選択
↓こんな感じでGRUBメニューが確認できるのであれば、そこから選択すればビルドしたカーネルを使う事ができます。
![grub2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/156680/cf61efdb-f77b-bfb4-0bf0-cd62a1441725.png)

なお、このメニューを表示させるためには、
grubの設定ファイルを以下のように書き換え、　`sudo update-grub`(ubuntu)または `sudo grub2-mkconfig -o /boot/grub2/grub.cfg`(fedora) を実行すればよいはずです。

```grub:/etc/default/grub
GRUB_TIMEOUT_STYLE=menu
GRUB_TIMEOUT=5
```

なお、VMをlibvirt経由でCLIのみで使う場合、以下の様に追記or編集し

```grub:etc/default/grub
GRUB_CMDLINE_LINUX="console=ttyS0,115200n8"
GRUB_SERIAL_COMMAND="serial --speed=115200 --unit=0 --word=8 --parity=no --stop=1"
GRUB_TERMINAL="serial"
```
(GRUB_CMDLINE_LINUXは、元が空でなければ、空白区切りでつなげると良いと思います。)

以下のように実行すればGRUBメニューが見えるはずです[^tty]。

[^tty]: vagrantで generic/ubuntu20.04を使って確認した結果です。シリアルデバイスがついていなければつながらないのでその場合は個別に対応が必要です。


```bash
virsh start --console ドメイン名
```

#### grub-set-default/grub-rebootでの選択
vagrant等CLI環境のみで仮想マシンで開発している場合、そもそもGRUBメニューが見れるようにするのもやや面倒です。
そういう場合は`grub-set-default` や `grub-reboot` を使うと起動されるカーネルを変更することができます。

```sh
$ sudo grub-reboot 指定したいカーネル
または
$ sudo grub-set-default 指定したいカーネル
```
ちなみに `grub-reboot` は次回起動するカーネルを、 `grub-set-default`は以降デフォルトで起動するカーネルを指定します。

さて、ここで「指定したいカーネル」というのは、GRUBメニューに表示されるものと同じであり、
以下の様にして選択可能なカーネル一覧を確認できます。

```
$ egrep '^\s*(menuentry|submenu)\s' /boot/grub/grub.cfg
menuentry 'Ubuntu' --class ubuntu --class gnu-linux --class gnu --class os $menuentry_id_option 'gnulinux-simple-e75828cc-39b8-4553-a039-49d967b96126' {
submenu 'Advanced options for Ubuntu' $menuentry_id_option 'gnulinux-advanced-e75828cc-39b8-4553-a039-49d967b96126' {
        menuentry 'Ubuntu, with Linux 5.4.0-dev' --class ubuntu --class gnu-linux --class gnu --class os $menuentry_id_option 'gnulinux-5.4.0-dev-advanced-e75828cc-39b8-4553-a039-49d967b96126' {                              
        menuentry 'Ubuntu, with Linux 5.4.0-dev (recovery mode)' --class ubuntu --class gnu-linux --class gnu --class os $menuentry_id_option 'gnulinux-5.4.0-dev-recovery-e75828cc-39b8-4553-a039-49d967b96126' {
        menuentry 'Ubuntu, with Linux 5.4.0-33-generic' --class ubuntu --class gnu-linux --class gnu --class os $menuentry_id_option 'gnulinux-5.4.0-33-generic-advanced-e75828cc-39b8-4553-a039-49d967b96126' {
        menuentry 'Ubuntu, with Linux 5.4.0-33-generic (recovery mode)' --class ubuntu --class gnu-linux --class gnu --class os $menuentry_id_option 'gnulinux-5.4.0-33-generic-recovery-e75828cc-39b8-4553-a039-49d967b96126' {
        menuentry 'Ubuntu, with Linux 5.4.0-26-generic' --class ubuntu --class gnu-linux --class gnu --class os $menuentry_id_option 'gnulinux-5.4.0-26-generic-advanced-e75828cc-39b8-4553-a039-49d967b96126' {
        menuentry 'Ubuntu, with Linux 5.4.0-26-generic (recovery mode)' --class ubuntu --class gnu-linux --class gnu --class os $menuentry_id_option 'gnulinux-5.4.0-26-generic-recovery-e75828cc-39b8-4553-a039-49d967b96126' {

```
例えば 5.4.0-dev を起動したい場合は

```bash
$ grub-reboot "Advanced options for Ubuntu' $menuentry_id_option >Ubuntu, with Linux 5.4.0-dev" # 入れ子のキーを'>'でつなぐ
または
$ grub-reboot '1>0' # 0-indexの配列に見立てても指定できる
```
の様に指定します。


##### 補足
理由は判明していませんが、自分で試した範囲では、grub-rebootやgrub-set-defaultが効かず、最悪起動しても接続できない場合がありました。
その場合、GRUBメニューを出せるようにすれば上手くいきました。(GRUBメニューを見ずにタイムアウトでデフォルトを選択させることができるので、それを上手く活用すればよいでしょう。)


### llvmでのビルド

歴史的に、linuxカーネルはgccを用いてビルドされて来ました。
しかし近年ではllvmによるビルドもサポートされています。

コンパイラのみgccでなくclangを用いたい場合は、以下のように `CC` を書き換えます。

```bash
$ make CC=clang defconfig
$ make CC=clang
```
GNU binutilsのツールチェーン一式LLVMに置き換えたい場合は以下のようにします。

```bash
$ make LLVM=1
```
より詳しくは、カーネルのドキュメントをご覧ください。
[Building Linux with Clang/LLVM](https://www.kernel.org/doc/html/latest/kbuild/llvm.html)


### クロスコンパイル
TODO


### セキュアブートと署名
TODO


# リンク
- [The Linux Kernel Archives](https://www.kernel.org/)
- https://github.com/torvalds/linux.git
- https://www.kernel.org/doc/makehelp.txt
- [Linux Kernel Document: Kernel Build System](https://www.kernel.org/doc/html/latest/kbuild/index.html) 
  - [Linux Kernel Document: Building External Modules](https://www.kernel.org/doc/html/latest/kbuild/modules.html)
  - [Linux Kernel Document: Building Linux with Clang/LLVM](https://www.kernel.org/doc/html/latest/kbuild/llvm.html)
- [Archi Wiki カーネル/コンパイル/伝統的な方法](https://wiki.archlinux.jp/index.php/%E3%82%AB%E3%83%BC%E3%83%8D%E3%83%AB/%E3%82%B3%E3%83%B3%E3%83%91%E3%82%A4%E3%83%AB/%E4%BC%9D%E7%B5%B1%E7%9A%84%E3%81%AA%E6%96%B9%E6%B3%95)
- [Archi Wiki カーネルモジュールのコンパイル](https://wiki.archlinux.jp/index.php/%E3%82%AB%E3%83%BC%E3%83%8D%E3%83%AB%E3%83%A2%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB%E3%81%AE%E3%82%B3%E3%83%B3%E3%83%91%E3%82%A4%E3%83%AB)
- [Ubuntu wiki Kernel](https://wiki.ubuntu.com/Kernel)
    - [Ubuntu wiki BuildYourOwnKernel](https://wiki.ubuntu.com/Kernel/BuildYourOwnKernel)
    - [Ubuntu wiki KernelGitGuide](https://wiki.ubuntu.com/Kernel/Dev/KernelGitGuide#Kernel.2FAction.2FGitTheSource.Obtaining_the_kernel_sources_for_an_Ubuntu_release_using_git)
- [Ubuntu Weekly Recipe 第524回　Hades Canyon/Kaby Lake GのdGPUを有効化する -- 新しい開発中のカーネルをビルド＆インストールする](https://gihyo.jp/admin/serial/01/ubuntu-recipe/0524?page=2#sec3_h2)
- [Ubuntu Weekly Recipe 第526回　Ubuntuで最新のカーネルをお手軽にビルドする方法](https://gihyo.jp/admin/serial/01/ubuntu-recipe/0526)
- [Ubuntu Weekly Recipe 第384回　Initramfsのしくみ](https://gihyo.jp/admin/serial/01/ubuntu-recipe/0384)
- [Ubuntu Weekly Recipe 第333回　カーネルパッケージをビルドしよう](https://gihyo.jp/admin/serial/01/ubuntu-recipe/0333)
- [Ubuntu Weekly Recipe 第278回　Ubuntuカーネルとの付き合い方](https://gihyo.jp/admin/serial/01/ubuntu-recipe/0278)
- [Fedora 31でカーネルソースコードを用意する](https://bracket.hatenablog.com/entry/2019/11/17/092753)