Linux 内核动手编译实用指南
Linux 内核 编译
![](/data/attachment/album/202310/04/115142ggqqhuclvxdxsb14.jpg)



> 
> 一份让你深入体验最新 Linux 内核编译过程的实操指南。
> 
> 
> 


出于各种原因，自行编译 Linux 内核可能引起你的兴趣。这些原因可能包括但不限于：


* 测试一个比你目前的 Linux 发行版更新的内核版本
* 采用一组不同的配置选项、驱动来构建内核
* 学习者的好奇心 ?


此指南将一步步指导你如何亲自编译 Linux 内核，包括你该运行哪些命令，为什么运行这些命令以及这些命令的执行效果。本文篇幅较长，所以请做好准备！



> 
> ? 诸如 [Ubuntu 这样的发行版提供了更简单地安装主线 Linux 内核的方式](https://itsfoss.com/upgrade-linux-kernel-ubuntu/)。但本教程目标是从源码手动完成所有工作。**此教程需要你付出时间、耐心以及丰富的 Linux 命令行使用经验**。本文更注重亲身实践的体验。不管怎么说，我仍建议你在虚拟机或备用系统中尝试此冒险，而非在你的主系统上进行。
> 
> 
> 


### 前置准备


在软件领域，构建任何事物都有两个基本要求：


1. 源代码
2. 构建依赖


因此，作为预备环节，我们需要下载 Linux 内核的源码压缩包，并安装一些能让我们成功构建 Linux 内核的依赖项。


#### Linux 版本导览


在任何时刻，[~~Freax~~](https://en.wikipedia.org/wiki/History_of_Linux#Naming) Linux 内核都有四种“版本”。


Linux 的这些 “版本”，按照开发流程的顺序是：


1. **linux-next 树：** 所有准备合并到 Linux 代码库的代码首先被合并到 linux-next 树。它代表的是 Linux 内核最新也是“最不稳定”的状态。大多数 Linux 内核开发者和测试人员使用这个来提高代码质量，为 Linus Torvalds 的后续提取做准备。**请谨慎使用！**
2. **发布候选版（RC） / 主线版：** Linus 从 linux-next 树抽取代码并创建一个初始发布版本。这个初始发布版本的测试版称为 RC（<ruby> 发布候选 <rt>  Release Candidate </rt></ruby>）版本。一旦 RC 版本发布，Linus 只会接受对它的错误修复和性能退化相关的补丁。基础这些反馈，Linus 会每周发布一个 RC 内核，直到他对代码感到满意。RC 发行版本的标识是 `-rc` 后缀，后面跟一个数字。
3. **稳定版：** 当 Linus 觉得最新的 RC 版本已稳定时，他会发布最终的“公开”版本。稳定发布版将会维护几周时间。像 Arch Linux 和 Fedora Linux 这样的前沿 Linux 发行版会使用此类版本。**我建议你在试用 linux-next 或任何 RC 版本之前，先试一试此版本。**
4. **LTS 版本：** 每年最后一个稳定版将会再维护 [几年](https://news.itsfoss.com/linux-kernel-support/)。这通常是一个较旧的版本，但它会 **会积极地维护并提供安全修复**。Debian 的稳定版本会使用 Linux 内核的 LTS 版版本。


若想了解更多此方面的知识，可参阅 [官方文档](https://www.kernel.org/category/releases.html)。


本文将以当前可用的最新稳定版为例，编写此文时的 Linux 内核版本是 [6.5.5](https://lwn.net/Articles/945378/)。


### 系统准备


由于 Linux 内核使用 C 语言编写，编译 Linux 内核至少需要一个 C 编译器。你的计算机上可能还需要其他一些依赖项，现在是安装它们的时候了。



> 
> ? 这个指南主要聚焦于使用 GNU C 编译器（GCC）来编译 Linux 内核。但在未来的文章中（可能会深入介绍 Rust 的支持），我**可能**会介绍使用 LLVM 的 Clang 编译器作为 GCC 的替代品。
> 
> 
> 不过，请注意，MSVC 并不适用。尽管如此，我仍期待有微软的员工为此发送修补程序集。我在瞎想啥？
> 
> 
> 


对于 Arch Linux 以及其衍生版本的用户，安装命令如下：



```
sudo pacman -S base-devel bc coreutils cpio gettext initramfs kmod libelf ncurses pahole perl python rsync tar xz

```

对于 Debian 以及其衍生版本的用户，安装命令如下：



```
sudo apt install bc binutils bison dwarves flex gcc git gnupg2 gzip libelf-dev libncurses5-dev libssl-dev make openssl pahole perl-base rsync tar xz-utils

```

对于 Fedora 以及其衍生版本的用户，安装命令如下：



```
sudo dnf install binutils ncurses-devel \
    /usr/include/{libelf.h,openssl/pkcs7.h} \
    /usr/bin/{bc,bison,flex,gcc,git,gpg2,gzip,make,openssl,pahole,perl,rsync,tar,xz,zstd}

```

#### 下载 Linux 内核源码


请访问 [kernel.org](https://kernel.org/)，在页面中寻找第一个 <ruby> 稳定 <rt>  Stable </rt></ruby> 版本。你不会找不到它，因为它是最显眼的黄色方框哦 ?



> 
> **[点击访问 kernel.org](https://kernel.org/)**
> 
> 
> 


![](/data/attachment/album/202310/04/115421iaaw8wvn85r6no8d.jpg)


通过点击黄色的方框，你就可以下载 Tar 文件。同时，也别忘了下载相匹配的 PGP 签名文件，稍后我们需要用到它来验证 Tar 文件。它的扩展名为 `.tar.sign`。


##### 校验 Tar 文件的完整性


你如何知道刚下载的 Tar 文件是否被损坏？对于个人来说，一个损坏的 Tar 文件只会浪费你的宝贵时间，如果你是在为一个组织工作，那么可能会危及到组织的安全（这时你可能还有更大的问题需要担忧，但我们并不想让所有人都产生创伤后应激障碍！）。


为了验证我们的 Tar 文件的完整性，我们需要先解压它。目前，它是使用 XZ 压缩算法压缩的。因此，我将使用 `unxz` 工具（其实就是 `xz --decompress` 的别名）来解压 `.tar.xz` 格式的压缩文件。



```
unxz --keep linux-*.tar.xz

```

解压完成后，我们需要获取 Linus Torvalds 和 Greg KH 使用的 GPG 公开密钥。这些密钥用于对 Tar 文件进行签名。



```
gpg2 --locate-keys torvalds@kernel.org gregkh@kernel.org

```

你应该可以得到一个与我在我的电脑上看到的类似的结果：



```
$ gpg2 --locate-keys torvalds@kernel.org gregkh@kernel.org
gpg: /home/pratham/.gnupg/trustdb.gpg: trustdb created
gpg: key 38DBBDC86092693E: public key "Greg Kroah-Hartman <gregkh@kernel.org>" imported
gpg: Total number processed: 1
gpg:               imported: 1
gpg: key 79BE3E4300411886: public key "Linus Torvalds <torvalds@kernel.org>" imported
gpg: Total number processed: 1
gpg:               imported: 1
pub   rsa4096 2011-09-23 [SC]
      647F28654894E3BD457199BE38DBBDC86092693E
uid           [ unknown] Greg Kroah-Hartman <gregkh@kernel.org>
sub   rsa4096 2011-09-23 [E]

pub   rsa2048 2011-09-20 [SC]
      ABAF11C65A2970B130ABE3C479BE3E4300411886
uid           [ unknown] Linus Torvalds <torvalds@kernel.org>
sub   rsa2048 2011-09-20 [E]

```

在导入 Greg 和 Linus 的密钥后，我们可以使用 `--verify` 标志来验证 Tar 的完整性，操作如下：



```
gpg2 --verify linux-*.tar.sign

```

如果验证成功，你应该会看到如下的输出信息：



```
$ gpg2 --verify linux-*.tar.sign
gpg: assuming signed data in 'linux-6.5.5.tar'
gpg: Signature made Saturday 23 September 2023 02:46:13 PM IST
gpg:                using RSA key 647F28654894E3BD457199BE38DBBDC86092693E
gpg: Good signature from "Greg Kroah-Hartman <gregkh@kernel.org>" [unknown]
gpg: WARNING: This key is not certified with a trusted signature!
gpg:          There is no indication that the signature belongs to the owner.
Primary key fingerprint: 647F 2865 4894 E3BD 4571  99BE 38DB BDC8 6092 693E

```

**务必查看是否存在 `gpg: Good signature` 的提示，然后再继续！**



> 
> ? 你可以忽略以下警告：`WARNING: This key is not certified with a trusted signature! There is no indication that the signature belongs to the owner.`。
> 
> 
> 我们已根据 Linus 和 Greg 的邮件地址获取了公开密钥，并无需对此警告感到担忧。
> 
> 
> 


##### 解压 Tar 文件


如果你顺利的进行到这里，意味着你的 Tar 文件完整性检查已经成功完成。接下来，我们将从 Tar 文件中解压出 Linux 内核的源码。


![The "TAR" xkcd comic: https://xkcd.com/1168/](/data/attachment/album/202310/04/115346wqqq7guguzg7cdgv.png)


这个步骤十分简单，只需对 Tar 文件执行 `tar -xf` 命令，如下：



```
tar -xf linux-*.tar

```

在这里，`-x` 选项表示解压，`-f` 选项则用来告诉 Tar 文件的文件名。


这个解压过程可能需要几分钟时间，你可以先放松，耐心等待一下。


### 配置 Linux 内核


Linux 内核的构建过程会查找 `.config` 文件。顾名思义，这是一个配置文件，用于指定 Linux 内核的所有可能的配置选项。这是必需的文件。


获取 Linux 内核的 `.config` 文件有两种方式：


1. 使用你的 Linux 发行版的配置作为基础（**推荐做法**）
2. 使用默认的，通用的配置



> 
> ? 也有第三种方法，也就是从零开始，手动配置每一个选项，但注意，这需要配置超过 12,000 个选项。并不推荐这种方式，因为手动配置所有选项将花费大量的时间，并且你还需要理解每个启用和禁用选项的含义。
> 
> 
> 


#### 使用发行版提供的配置


**使用你的 Linux 发行版提供的配置是一个安全的选择。** 如果你只是跟随这个指南测试一个不是你的发行版提供的新内核，那么这就是推荐的方式。


你的 Linux 发行版的 Linux 内核配置文件会在以下两个位置之一：


* 大多数 Linux 发行版，如 Debian 和 Fedora 及其衍生版，将会把它存在 `/boot/config-$(uname -r)`。
* 一些 Linux 发行版，比如 Arch Linux 将它整合在了 Linux 内核中。所以，可以在 `/proc/config.gz` 找到。



> 
> ? 如果两者都有，建议使用 `/proc/config.gz`。这是因为它在只读文件系统中，所以是未被篡改的。
> 
> 
> 


进入含有已经解压出的 Tar 文件的目录。



```
cd linux-*/

```

接着，复制你的 Linux 发行版的配置文件：



```
### Debian 和 Fedora 及其衍生版：
$ cp /boot/config-"$(uname -r)" .config

### Arch Linux 及其衍生版：
$ zcat /proc/config.gz > .config

```

##### 更新配置文件


一旦完成这些步骤，接下来就需要“更新”配置文件了。因为你的发行版提供的配置很可能比你正在构建的 Linux 内核版本要旧。



> 
> ? **这同样适用于像 Arch Linux 和 Fedora 这样前沿的 Linux 发行版。** 它们并不会因为有新版本可用就立刻发布更新。他们会进行一些质量控制工作，这必然会花费些时间。因此，即便是你的发行版提供的最新内核，相较于你在 [kernel.org](http://kernel.org) 上获取的版本也会滞后几个小版本。
> 
> 
> 


要更新一个已有的 `.config` 文件，我们使用 `make` 命令搭配 `olddefconfig` 参数。简单解释一下，这个命令的意思是使用 *旧的、默认的、配置*。


这将使用“旧的配置文件”（当前保存为 `.config`，这是你发行版配置的一份直接副本），并检查从上一版本以来 Linux 代码库中新加的任何配置选项。如果找到任何新的、*未配置* 的选项，该选项的默认配置值会被使用，并会对 `.config` 文件进行更新。


原来的 `.config` 文件将被重命名为 `.config.old` 进行备份，并将新的更改写入至 `.config` 文件。



```
make olddefconfig

```

以下是我机器上的输出：



```
$ file .config
.config: Linux make config build file, ASCII text

$ make olddefconfig
    HOSTCC  scripts/basic/fixdep
    HOSTCC  scripts/kconfig/conf.o
    HOSTCC  scripts/kconfig/confdata.o
    HOSTCC  scripts/kconfig/expr.o
    LEX     scripts/kconfig/lexer.lex.c
    YACC    scripts/kconfig/parser.tab.[ch]
    HOSTCC  scripts/kconfig/lexer.lex.o
    HOSTCC  scripts/kconfig/menu.o
    HOSTCC  scripts/kconfig/parser.tab.o
    HOSTCC  scripts/kconfig/preprocess.o
    HOSTCC  scripts/kconfig/symbol.o
    HOSTCC  scripts/kconfig/util.o
    HOSTLD  scripts/kconfig/conf
.config:8593:warning: symbol value 'm' invalid for USB_FOTG210_HCD
.config:8859:warning: symbol value 'm' invalid for USB_FOTG210_UDC
#
# configuration written to .config
#

```

##### 针对 Debian 及其衍生版用户


Debian 及其衍生版为内核模块使用一个签名证书。默认情况下，你的计算机并不包含这个证书。


我推荐关闭启用模块签名的选项。具体如下所示：



```
./scripts/config --file .config --set-str SYSTEM_TRUSTED_KEYS ''
./scripts/config --file .config --set-str SYSTEM_REVOCATION_KEYS ''

```

**如果你不这么做，在后面你进行 Linux 内核构建时，可能会导致构建失败。要注意这点。**


#### 使用自定义配置


如果你出于学习内核开发的目的学习如何构建 Linux 内核，那你应该这样做。



> 
> ? **请注意，偏离你的 Linux 发行版的配置可能无法在实体硬件上“正常”工作。**问题可能是特定硬件无法工作、Linux 内核无法启动等。
> 
> 
> 因此，我们只建议在虚拟机中使用。
> 
> 
> 


你可以通过查看 [make help 的输出](https://www.kernel.org/doc/makehelp.txt) 来查看 *所有* 可用的选项，但我们主要关注三个 `make` 目标：


* `defconfig`: 默认配置。
* `allmodconfig`: 根据当前系统状态，尽可能地把项目构建为可加载模块（而非内建）。
* `tinyconfig`: 极简的 Linux 内核。


由于 `tinyconfig` 目标只会构建少数项目，构建时间将会缩短。我个人选择它的原因主要有：


1. 检查我在代码/工具链中做的修改是否正确，以及代码是否可以编译。
2. 在虚拟机中只进行少数选项的测试。



> 
> ? 在为 ARM 或 RISC-V 机器构建 Linux 内核时，你可能需要 DTB（设备树的二进制文件）。**使用 `tinyconfig` 目标将不会启用构建 DTB 的选项，你的内核很可能无法启动。**
> 
> 
> 当然，你可以用 QEMU 在没有任何 DTB 的情况下启动 Linux 内核。但这篇文章并不会聚焦在此。或许你可以通过评论，让我在之后的时间里覆盖这个话题 ?
> 
> 
> 


**除非你确切地知道自己在做什么，否则你应当使用 `defconfig` 目标。** 以下是我在我的电脑上运行的效果：



```
$ make defconfig
    HOSTCC  scripts/basic/fixdep
    HOSTCC  scripts/kconfig/conf.o
    HOSTCC  scripts/kconfig/confdata.o
    HOSTCC  scripts/kconfig/expr.o
    LEX     scripts/kconfig/lexer.lex.c
    YACC    scripts/kconfig/parser.tab.[ch]
    HOSTCC  scripts/kconfig/lexer.lex.o
    HOSTCC  scripts/kconfig/menu.o
    HOSTCC  scripts/kconfig/parser.tab.o
    HOSTCC  scripts/kconfig/preprocess.o
    HOSTCC  scripts/kconfig/symbol.o
    HOSTCC  scripts/kconfig/util.o
    HOSTLD  scripts/kconfig/conf
*** Default configuration is based on 'defconfig'
#
# configuration written to .config
#

```

#### 修改配置


无论你是使用 Linux 发行版的配置并更新它，还是使用 `defconfig` 目标创建新的 `.config` 文件，你都可能希望熟悉如何修改这个配置文件。**最可靠的修改方式是使用 `menuconfig` 或 `nconfig` 目标。**


这两个目标的功能是相同的，只不过提供给你的界面有所不同。这是这两者间唯一的区别。我个人更偏向于使用 `menuconfig` 目标，但近来我发现 `nconfig` 在搜索选项时似乎更具直观性，所以我逐渐转向使用它。


首先，带着 `menuconfig` 目标运行 `make` 命令：



```
$ make menuconfig
    HOSTCC  scripts/kconfig/mconf.o
    HOSTCC  scripts/kconfig/lxdialog/checklist.o
    HOSTCC  scripts/kconfig/lxdialog/inputbox.o
    HOSTCC  scripts/kconfig/lxdialog/menubox.o
    HOSTCC  scripts/kconfig/lxdialog/textbox.o
    HOSTCC  scripts/kconfig/lxdialog/util.o
    HOSTCC  scripts/kconfig/lxdialog/yesno.o
    HOSTLD  scripts/kconfig/mconf

```

在此界面，你可以根据各选项的类型来进行切换操作。


有两类可切换选项：


* 布尔状态选项：这类选项只能关闭（`[ ]`）或作为内建组件开启（`[*]`）。
* 三态选项：这类选项可以关闭（`< >`）、内建（`<*>`），或作为可加载模块（`<M>`）进行构建。


想要了解更多关于某个选项的信息，使用上/下箭头键导航至该选项，然后按 `<TAB>` 键，直至底部的 `< Help >` 选项被选中，然后按回车键进行选择。此时就会显示关于该配置选项的帮助信息。


**在修改选项时请务必谨慎。**


当你满意配置后，按 `<TAB>` 键直到底部的 `< Save >` 选项被选中。然后按回车键进行选择。然后再次按回车键（**记住，此时不要更改文件名**），就能将更新后的配置保存到 `.config` 文件中。


### 构建 Linux 内核


构建 Linux 内核实际上十分简单。然而，在开始构建之前，让我们为自定义内核构建添加一个标签。我将使用字符串 `-pratham` 作为标签，并利用 `LOCALVERSION` 变量来实施。你可以使用以下命令实现配置：



```
./scripts/config --file .config --set-str LOCALVERSION "-pratham"

```

这一命令将 `.config` 文件中的 `CONFIG_LOCALVERSION` 配置选项设为我在结尾指定的字符串，即 `-pratham`。当然，你也不必非得使用我所用的名字哦 ?


`LOCALVERSION` 选项可用于设置一个“本地”版本，它会被附加到通常的 `x.y.z` 版本方案之后，并在你运行 `uname -r` 命令时一并显示。


由于我正在构建的是 6.5.5 版本内核，而 `LOCALVERSION` 字符串被设为 `-pratham`，因此，对我来说，最后的版本名将会是 `6.5.5-pratham`。这么做的目的是确保我所构建的自定义内核不会与发行版所提供的内核产生冲突。


接下来，我们来真正地构建内核。可以用以下的命令完成此步骤：



```
make -j$(nproc) 2>&1 | tee log

```

**这对大部分（99%）用户来说已经足够了。**


其中的 `-j` 选项用于指定并行编译任务的数量。而 `nproc` 命令用于返回可用处理单位（包括线程）的数量。因此，`-j$(nproc)` 其实意味着“使用我拥有的 CPU 线程数相同数量的并行编译任务”。


`2>&1` 会将 STDOUT 和 STDIN 重定向到相同的文件描述符，并通过管道传输给 `tee` 命令，这会将输出存储在一个名为 `log` 的文件，并且在控制台打印出完全相同的文本。如果你在构建时遇到错误，并希望回顾日志来检查出了什么问题，这将会十分有用。遇到那种情况，你只需要简单执行 `grep Error log` 命令就能找到线索。


#### 自定义 make 目标


在 Linux 内核的源文件夹中，`make` 命令有一些自定义的目标可供执行各种操作。这些主要作为开发者的参考。如果你的唯一目标是安装一个比你当前发行版更新的 Linux 内核，那么你完全可以跳过这部分内容 ?


##### 构建目标


作为一名开发者，你可能只想构建 Linux 内核，或者只想构建模块，或者只想构建设备树二进制（DTB）。在这种情况下，你可以指定一个构建目标，然后 `make` 命令只会构建指定的项目，而不会构建其他的。


以下是一些构建目标：


* `vmlinux`：纯粹的 Linux 内核。
* `modules`：可加载模块。
* `dtbs`：设备树二进制文件（主要用于 ARM 和 RISC-V 架构）。
* `all`：构建所有被标记了星号 `*` 的项目（从 `make help` 的输出中可以查看）。


通常情况下，你并不需要指定构建目标，因为它们都已经在构建列表中。所列出的目标是在你只想要测试某一个构建目标，而不是其他目标时的情况。


依据你的 [计算机架构](https://itsfoss.com/arm-aarch64-x86_64/)，构建完成的 Linux 内核镜像（存放在 `/boot` 目录）的名称会有所不同。


对于 `x86_64`，Linux 内核的默认镜像名称是 `bzImage`。因此，如果你只需要构建引导所需的 Linux 内核，你可以像下面这样设定 `bzImage` 为目标：



```
### 对于 x86_64
$ make bzImage

```

“那么如何在我的架构上找到用来调用 `make` 的目标名称呢？”


有两种方法。要么你可以执行 `make help` 之后查找在 `Architecture specific targets` 下，第一个前面带有星号 `*` 的选项。


或者，如果你希望自动完成，你可以利用 `image_name` 目标得到镜像的完全路径（相对路径），选择性地添加 `-s` 标志来获得有用的输出。


以下是我拥有的三台电脑的输出，一台是 `x86_64`，另一台是 `AArch64`，还有一台是 `riscv` ：



```
### x86_64
$ make -s image_name
arch/x86/boot/bzImage

### AArch64
$ make -s image_name
arch/arm64/boot/Image.gz

### RISC-V
$ make -s image_name
arch/riscv/boot/Image.gz

```

现在，要只构建 Linux 内核镜像，你可以这样进行：



```
make $(make -s image_name | awk -F '/' '{print $4}')

```

##### 清理目标


如果你需要清理构建产生的文件，你可以用以下的目标来实现你的需求：


* `clean`：除了 `.config` 文件外，删除几乎所有其他内容。
* `mrproper`：执行了 `make clean` 的所有操作外，还会删除 `.config` 文件。
* `distclean`：除了执行 `make mrproper` 的所有操作外，还会清理任何补丁文件。


### 安装


一旦成功编译了 Linux 内核，接下来就是启动安装一些东西的时候了。“*一些* 东西？” 没错，我们至少构建了两种不同的东西，如果你使用的是 ARM 或 RISC-V 架构，那就有三种。我会在以下内容中详细解释。



> 
> ? 虽然我将告诉你不同的安装方式，尤其是关于如何改变默认安装路径的方法，但**如果你不确定自己在做什么，那么我不建议你这么做！** 请慎重考虑，如果你决定走自定义的路线，那你需要自己负责后果。默认设置之所以存在，是因为它们有其特殊的原因 ?
> 
> 
> 


#### 安装内核模块


Linux 内核有部分在系统启动时并非必需的。这些部分被构建为可加载模块，即在需要时才进行加载和卸载。


所以，首先需要安装这些模块。这可以通过 `modules_install` 目标完成。**必须使用 `sudo`**，因为模块会被安装在 `/lib/modules/<kernel_release>-<localversion>` 这个需要 `root` 权限的路径下。


这个过程不仅会安装内核模块，还会对其进行签名，所以可能需要一些时间。好消息是你可以通过之前提到的 `-j$(nproc)` 选项来并行执行安装任务，这样会快一些。?



```
sudo make modules_install -j$(nproc)

```


> 
> **给开发者的提示：** 你可以通过设定 `INSTALL_MOD_PATH` 变量来指定一个不同的路径存放 Linux 模块，而不用默认的 `/lib/modules/<kernel_release>-<localversion>`，具体如下：
> 
> 
> 
> ```
>    sudo make modules_install INSTALL_MOD_PATH=<path>
> 
> ```
> 
> 



> 
> **另一个给开发者的提示：** 你可以使用 `INSTALL_MOD_STRIP` 变量来决定是否需要剥离模块的调试符号。如果未设定该变量，调试符号**不会被剥离**。当设为 `1` 时，符号信息将会被使用 `--strip-debug` 选项剥离，随后该选项会传递给 `strip`（或者在使用 Clang 的时候传递给 `llvm-strip`）工具。
> 
> 
> 


#### （可选）安装 Linux 内核头文件


如果你打算使用这个内核来支持树外模块，比如 ZFS 或英伟达 DKMS，或者打算尝试自行编写模块，你可能会需要 Linux 内核提供的头文件。


可以通过以下方式使用 `headers_install` 目标来安装 Linux 内核头文件：



```
sudo make headers_install

```

**应使用 `sudo` 命令**，因为这些头文件会被安装到 `/usr` 目录。同时还会在 `/usr` 目录内创建子目录 `include/linux`，然后将头文件安装到 `/usr/include/linux` 内。



> 
> **给开发者的提示：** 通过设定 `INSTALL_HDR_PATH` 变量，你可以修改 Linux 内核头文件的安装路径。
> 
> 
> 


#### 安装 DTB（只针对 ARM 和 RISC-V）


**如果你使用的是 x86\_64 架构，那么你可以跳过此步骤！**


如果你针对 ARM 或者 RISC-V 构建了内核，那么在运行 `make` 的过程中，设备树的二进制文件可能已经被编译出来了。你可以通过在 `arch/<machine_architecture>/boot/dts` 目录查找 `.dtb` 文件来确认这一点。


这里提供了一个快速检查的技巧：



```
### 对于 AArch32
$ find arch/arm/boot/dts -name "*.dtb" -type f | head -n 1 > /dev/null && echo "DTBs for ARM32 were built"

### 对于 AArch64
$ find arch/arm64/boot/dts -name "*.dtb" -type f | head -n 1 > /dev/null && echo "DTBs for ARM64 were built"

### 对于 RISC-V
$ find arch/riscv/boot/dts -name "*.dtb" -type f | head -n 1 > /dev/null && echo "DTBs for RISC-V were built"

```

如果你看到出现 `DTBs for <arch> were built` 的消息，那么你可以开始安装 DTB。这可以通过 `dtbs_install` 目标来实现。


**需要使用 `sudo`**，因为它们会被安装在 `/boot/dtb-<kernel_release>-<localversion>` 中，而这个目录是由 `root` 所拥有的。



```
sudo make dtbs_install

```


> 
> **给开发者的提示：** 就像安装模块一样，你可以使用 `INSTALL_DTBS_PATH` 变量指定一个自定义的路径来安装设备树二进制文件。
> 
> 
> 


#### 安装 Linux 内核


最后，我们来安装 Linux 内核本身！这可以通过 `install` 目标来完成，就像这样：



```
sudo make install

```

**在这里必须使用 `sudo`**，因为 Linux 内核将被安装在 `/boot` 目录，而这个目录不允许普通用户写入。



> 
> ? 一般来讲，`install` 目标也会更新引导加载程序，但是如果它没有成功，那可能是不支持你使用的引导加载程序。如果你没有使用 GRUB 作为你的引导加载程序，请一定要阅读你引导加载程序的使用手册 ?
> 
> 
> 



> 
> **给开发者的提示：** 并不奇怪，`INSTALL_PATH` 变量被用来设定 Linux 内核的安装位置，而非默认的 `/boot` 目录。
> 
> 
> 


#### 针对 Arch Linux 用户的说明


如果你尝试执行了 `make install` 命令，可能已经注意到产生了错误。错误如下：



```
$ sudo make install
    INSTALL /boot
Cannot find LILO.

```

要在 Arch Linux 上实际完成 Linux 内核的安装，我们需要手动复制 Linux 内核镜像文件。别担心，如果你使用的是 Arch Linux，手动操作应该是家常便饭了。( ͡° ͜ʖ ͡°)


可以使用以下命令完成这个步骤：



```
sudo install -Dm644 "$(make -s image_name)" /boot/vmlinuz-<kernel_release>-<localversion>

```

因为我编译的是 6.5.5 版本的内核，所以我将会执行下面这条命令，你可以根据你的实际情况进行适当调整：



```
sudo install -Dm644 "$(make -s image_name)" /boot/vmlinuz-6.5.5-pratham

```

虽然不是必须的，但最好复制一份名为 `System.map` 的文件。既然你已经在操作了，一并也复制了 `.config` 文件吧 ?



```
sudo cp -vf System.map /boot/System.map-<kernel_release>-<localversion>
sudo cp -vf .config /boot/config-<kernel_release>-<localversion>

```

##### 生成初始 RAM 磁盘


当你安装 Arch Linux 时，可能已经了解过 `mkinitcpio` 这个工具。现在，我们将使用它来创建初始的 RAM 磁盘。


首先，我们需要创建一个预设文件。向 `/etc/mkinitcpio.d/linux-<localversion>.preset` 文件中添加以下内容，根据实际需要来替换 `<kernel_release>` 和 `<localversion>`。



```
ALL_config="/etc/mkinitcpio.conf"
ALL_kver="/boot/vmlinuz-<kernel_release>-<localversion>"

PRESETS=('default' 'fallback')

default_image="/boot/initramfs-<kernel_release>-<localversion>.img"
fallback_options="-S autodetect"

```

配置完成后，执行下面的命令来生成初始 RAM 磁盘：



```
sudo mkinitcpio -p linux-<localversion>

```

我自己的电脑上得到的输出如下，你的结果应该会类似！



```
$ sudo mkinitcpio -p linux-pratham
==> Building image from preset: /etc/mkinitcpio.d/linux-pratham.preset: 'default'
==> Using configuration file: '/etc/mkinitcpio.conf'
    -> -k /boot/vmlinuz-6.5.5-pratham -c /etc/mkinitcpio.conf -g /boot/initramfs-6.5.5-pratham.img
==> Starting build: '6.5.5-pratham'
    -> Running build hook: [base]
    -> Running build hook: [udev]
    -> Running build hook: [autodetect]
    -> Running build hook: [modconf]
    -> Running build hook: [kms]
    -> Running build hook: [keyboard]
==> WARNING: Possibly missing firmware for module: 'xhci_pci'
    -> Running build hook: [keymap]
    -> Running build hook: [consolefont]
==> WARNING: consolefont: no font found in configuration
    -> Running build hook: [block]
    -> Running build hook: [filesystems]
    -> Running build hook: [fsck]
==> Generating module dependencies
==> Creating zstd-compressed initcpio image: '/boot/initramfs-6.5.5-pratham.img'
==> Image generation successful
==> Building image from preset: /etc/mkinitcpio.d/linux-pratham.preset: 'fallback'
==> Using configuration file: '/etc/mkinitcpio.conf'
==> WARNING: No image or UKI specified. Skipping image 'fallback'

```

初始 RAM 磁盘已成功生成，现在我们可以进入下一步，更新引导加载器！


##### 更新 GRUB


一旦所有必要的文件已成功复制到其对应的位置，接下来，我们将进行 GRUB 的更新。


使用以下命令对 GRUB 引导加载器进行更新：



```
sudo grub-mkconfig -o /boot/grub/grub.cfg

```


> 
> ? 如果你使用的引导加载器不是 GRUB，请参看 Arch Wiki 中相关的引导加载器文档。
> 
> 
> 


**注意，更新 GRUB 并不会直接使新的内核版本设为默认启动选项。在引导时，请在启动菜单中手动选择新的内核版本。**


你可以通过选择 `Advanced options for Arch Linux` 菜单，并在随后的菜单中选择 `Arch Linux, with Linux <kernel_release>-<localversion>` 来启用新版的 Linux 内核。


### 重启电脑


恭喜你！你已经完成了获取 Linux 内核源代码、进行配置、构建以及安装等所有步骤。现在只需要通过重启电脑并进入新构建和安装的 Linux 内核，就可以开始享受你的努力成果了。


启动时，请确保从引导加载器中选择正确的 Linux 内核版本。系统启动后，运行 `uname -r` 命令来确认你正在使用预期的 Linux 内核。


以下是我自己的电脑输出的内容：



```
$ uname -r
6.5.5-pratham

```

**是时候开始庆祝了！** ?


### 卸载操作



> 
> ? 提示：在删除当前正在使用的内核版本之前，你应该首先切换至较旧的内核版本。
> 
> 
> 


可能你的 Linux 发行版所使用的 Linux 内核版本就是你手动编译的版本，或者你自行编译了新的内核并注意到应卸载旧的内核以节省空间，于是你开始想如何才能卸载。当然，虽然我们无法简单地运行 `make uninstall` 命令，但这并不代表没有其他的方法！


我们清楚各个文件的安装位置，因此删除它们相对简单。



```
### 删除内核模块
$ rm -rf /lib/modules/<kernel_release>-<localversion>

### 删除设备树二进制文件
$ rm -rf /boot/dtb-<kernel_release>-<localversion>

### 删除 Linux 内核本身
$ rm -vf /boot/{config,System,vmlinuz}-<kernel_release>-<localversion>

```

### 总结


这个过程不是一次简单的旅程，是吧？但是现在，我们终于抵达了终点。我们一起学习了手动编译 Linux 内核的全过程，包括安装依赖、获取和验证源码、解压源码、配置 Linux 内核、构建内核以及安装内核。


如果你喜欢这个详细的步骤指南，请给我留言反馈。如果在操作过程中遇到问题，也欢迎提出，让我知道！


*（题图：MJ/853481c5-87e3-42aa-8ace-e9ddfa232f75）*




---


via: <https://itsfoss.com/compile-linux-kernel/>


作者：[Pratham Patel](https://itsfoss.com/author/pratham/) 选题：[lujun9972](https://github.com/lujun9972) 译者：[ChatGPT](https://linux.cn/lctt/ChatGPT) 校对：[wxy](https://github.com/wxy)


本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


OK
![Warp Terminal](/assets/images/warp-terminal.webp)


![Warp Terminal](/assets/images/warp-terminal.webp)

You may be interested in compiling the Linux kernel yourself, for many reasons. It might be, but not limited to, one of the following:

- Trying out a newer kernel than what your Linux distribution provides
- Building the kernel with a different set of configuration options and/or drivers
- A learner's curiosity :)

This guide will show you how you can compile the Linux kernel yourself, with the commands that you should run, why run these commands and explain what it does. This is a long one, so brace yourself!

[Ubuntu have easier ways of installing mainline Linux kernel](https://itsfoss.com/upgrade-linux-kernel-ubuntu/). But this tutorial is about doing things manually from the source code.

**. This is more about experiencing things first hand. However, I advise trying this adventure in a VM or on your spare system instead of doing it on your main system.**

**You'll need time, patience and good experience with the Linux command line for this tutorial**## Pre-requisites

There are two prerequisites to building anything (in context to software).

- Source code
- Build dependencies

So, as the prerequisites, we will be downloading the Linux kernel's source as a tarball and install a few dependencies that will allow us to build the Linux kernel.

### Primer on Linux versions

At a given moment, there are 4 "versions" of the [ Freax](https://en.wikipedia.org/wiki/History_of_Linux#Naming) Linux kernel.

These "versions" of Linux, in the order of the development flow are:

**The**Any code to be merged in the Linux codebase is first merged in the`linux-next`

tree:`linux-next`

tree. This is the newest but also the "least stable" state of the Linux kernel. Most Linux kernel developers and testers use this to refine the code quality for Linus to pull from, later on.**Tread carefully!****RC/Mainline releases:**Linus pulls from the`linux-next`

tree and creates an initial release. The beta version of this release is called an RC release (Release Candidate). Once an RC is released, Linus accepts only bug-fixes and performance regression related patches. Linus keeps releasing an RC kernel every week until he is satisfied with the code (with feedback from users). The`-rc`

suffix, followed by a number, is added to indicate the RC release version.**Stable releases:**Once Linus feels that the last RC was stable, he releases the final, "public" release. A stable release is maintained for a few more weeks. This is what bleeding edge Linux distributions like Arch Linux and Fedora Linux use.**I recommend you try this first before**`linux-next`

or any RC releases.**LTS releases:**The last stable release of a given year is maintained for[a few more years](https://news.itsfoss.com/linux-kernel-support/). This is usually an older release but it is**actively maintained with security fixes**. A stable release of Debian uses the LTS release of the Linux kernel.

You can read more about this in the [official documentation](https://www.kernel.org/category/releases.html).

For the purposes of this article, I will be using the latest stable release that is available. Which, at the time of writing this is at [ v6.5.5](https://lwn.net/Articles/945378/).

## Getting the system ready

Since the Linux kernel is written in the C programming language, you need at least a C compiler to compile the Linux kernel. There are other such dependencies that might or might not be present on your computer. Time to install those.

*in a future article (diving into Rust support), I will cover using LLVM's Clang compiler as an alternative to GCC.*

**maybe**And no, MSVC does not count. That said, I do expect a Microsoft employee sending in a patchset for this. What have I done?

Install command for users of Arch Linux and its derivatives:

`sudo pacman -S base-devel bc coreutils cpio gettext initramfs kmod libelf ncurses pahole perl python rsync tar xz`


Install command for users of Debian and its derivatives:

`sudo apt install bc binutils bison dwarves flex gcc git gnupg2 gzip libelf-dev libncurses5-dev libssl-dev make openssl pahole perl-base rsync tar xz-utils`


Install command for Fedora and its derivatives:

```
sudo dnf install binutils ncurses-devel \
/usr/include/{libelf.h,openssl/pkcs7.h} \
/usr/bin/{bc,bison,flex,gcc,git,gpg2,gzip,make,openssl,pahole,perl,rsync,tar,xz,zstd}
```


### Fetching the Linux kernel's source

Head over to [kernel.org](https://kernel.org/) and on the page, find the first Stable release. You can't miss it since it is the biggest yellow box ;)

![Screenshot of kernel.org showing the list of available kernels](https://itsfoss.com/content/images/2023/09/Screenshot-2023-09-24-at-2.21.00-PM.png)

You can download the tarball by clicking on the big yellow box. While you are at it, download the matching PGP signature file too. It will be handy when we verify the tarball at a later point in time. It has the extension `.tar.sign`

.

#### Verifying the tarball's authenticity

How do you know if the tarball you just downloaded is corrupted or not? On an individual level, a corrupted tarball will just waste your precious tinkering hours, but if this is done for an organization, you might be making things easier for an attacker (at which point you have bigger issues to worry about, but let's not give PTSD to everyone!).

To verify the integrity of our tarball, we need the tarball. At the moment, it is compressed using the XZ compression algorithm. Hence, I will use the `unxz`

utility (merely an alias to `xz --decompress`

) to decompress the `.tar.xz`

archive file.

`unxz --keep linux-*.tar.xz`


Once extracted, we will fetch the public GPG keys that Linus Torvalds and Greg KH use. These keys are used to sign the tarball.

`gpg2 --locate-keys `[[email protected]](/cdn-cgi/l/email-protection) [[email protected]](/cdn-cgi/l/email-protection)


You should get output that is similar to what I got on my machine:

`$ gpg2 --locate-keys `[[email protected]](/cdn-cgi/l/email-protection) [[email protected]](/cdn-cgi/l/email-protection)
gpg: /home/pratham/.gnupg/trustdb.gpg: trustdb created
gpg: key 38DBBDC86092693E: public key "Greg Kroah-Hartman <[[email protected]](/cdn-cgi/l/email-protection)>" imported
gpg: Total number processed: 1
gpg: imported: 1
gpg: key 79BE3E4300411886: public key "Linus Torvalds <[[email protected]](/cdn-cgi/l/email-protection)>" imported
gpg: Total number processed: 1
gpg: imported: 1
pub rsa4096 2011-09-23 [SC]
647F28654894E3BD457199BE38DBBDC86092693E
uid [ unknown] Greg Kroah-Hartman <[[email protected]](/cdn-cgi/l/email-protection)>
sub rsa4096 2011-09-23 [E]
pub rsa2048 2011-09-20 [SC]
ABAF11C65A2970B130ABE3C479BE3E4300411886
uid [ unknown] Linus Torvalds <[[email protected]](/cdn-cgi/l/email-protection)>
sub rsa2048 2011-09-20 [E]


Once Greg's and Linus' keys are imported, the integrity of the tarball can be verified using the `--verify`

flag; like so:

`gpg2 --verify linux-*.tar.sign`


If the verification was successful, you should get output similar to following:

```
$ gpg2 --verify linux-*.tar.sign
gpg: assuming signed data in 'linux-6.5.5.tar'
gpg: Signature made Saturday 23 September 2023 02:46:13 PM IST
gpg: using RSA key 647F28654894E3BD457199BE38DBBDC86092693E
gpg: Good signature from "Greg Kroah-Hartman <
```[[email protected]](/cdn-cgi/l/email-protection)>" [unknown]
gpg: WARNING: This key is not certified with a trusted signature!
gpg: There is no indication that the signature belongs to the owner.
Primary key fingerprint: 647F 2865 4894 E3BD 4571 99BE 38DB BDC8 6092 693E


**Please do not proceed unless you see a message that says gpg: Good signature!**


*WARNING: This key is not certified with a trusted signature! There is no indication that the signature belongs to the owner.*We fetched the keys from Linus' and Greg's emails and have no need to worry about this warning.

#### Extracting the tarball

If you are here, it means that your tarball's integrity check completed successfully. Now then, it is time to extract the Linux kernel's source out of it.

![The "TAR" xkcd comic: https://xkcd.com/1168/](https://itsfoss.com/content/images/2023/09/tar_2x.png)

[https://xkcd.com/1168/](https://xkcd.com/1168/)

This one is quite easy, just do a `tar -xf`

on the tarball, like so:

`tar -xf linux-*.tar`


The `-x`

option is used to specify extraction, and `tar`

is informed about the tarball filename using the `-f`

option.

The extraction will take a few minutes, adjust and sit straight :)

## Configuring the Linux kernel

The Linux kernel's build process looks for a `.config`

file. As the name suggests, it is a configuration file that specifies every possible configuration option for the Linux kernel. It is necessary to have one.

There are two methods of getting this `.config`

file for the Linux kernel:

- Using your Linux distribution's configuration as a base (
**recommended**) - Using a default, generic configuration

### Using the distribution-provided configuration

**Using the configuration provided by your Linux distribution is a safe bet.** If you are following this guide just to try out a new kernel than what your distribution offers, this is the recommended method.

Your Linux distribution's configuration file for the Linux kernel will be in either of the two places:

- Most Linux distributions like Debian and Fedora, and their derivatives will store it as
`/boot/config-$(uname -r)`

. - Some Linux distributions like Arch Linux have it integrated in the Linux kernel itself. Therefore, it will be available at
`/proc/config.gz`

.

**as it is on a read-only filesystem and hence untampered.**

**/proc/config.gz**Enter the directory which contains the extracted tarball.

`cd linux-*/`


Then, copy your Linux distribution's configuration file:

```
## Debian and Fedora's derivatives:
$ cp /boot/config-"$(uname -r)" .config
## Arch Linux and its derivatives:
$ zcat /proc/config.gz > .config
```


#### Updating the configuration

Once that is done, it is time to "update" the configuration file. You see, there is a high probability that the configuration that your distribution provides is older than the Linux kernel that you are building.

**Neither of them release an update just because there is a new version available. They do some QA, which is bound to take time. And hence, even the latest kernel offered by your distribution will be a few minor releases behind, compared to what you will get from kernel.org.**

**This applies to bleeding edge Linux distributions like Arch Linux and Fedora too.**To update an existing `.config`

file, the `make`

command is used with the target `olddefconfig`

. Broken down, this is * old default configuration*.



This will take the "old configuration file" (which is currently saved as `.config`

as a literal copy of your distribution's configuration) and check for any new configuration options that were added to the Linux codebase since. If any new, *unconfigured* options are found, the default configuration value for that option is used and the `.config`

file is updated.

The original `.config`

file is renamed to `.config.old`

as the backup and new changes are written to `.config`

.

`make olddefconfig`


Following is the output from my machine:

```
$ file .config
.config: Linux make config build file, ASCII text
$ make olddefconfig
HOSTCC scripts/basic/fixdep
HOSTCC scripts/kconfig/conf.o
HOSTCC scripts/kconfig/confdata.o
HOSTCC scripts/kconfig/expr.o
LEX scripts/kconfig/lexer.lex.c
YACC scripts/kconfig/parser.tab.[ch]
HOSTCC scripts/kconfig/lexer.lex.o
HOSTCC scripts/kconfig/menu.o
HOSTCC scripts/kconfig/parser.tab.o
HOSTCC scripts/kconfig/preprocess.o
HOSTCC scripts/kconfig/symbol.o
HOSTCC scripts/kconfig/util.o
HOSTLD scripts/kconfig/conf
.config:8593:warning: symbol value 'm' invalid for USB_FOTG210_HCD
.config:8859:warning: symbol value 'm' invalid for USB_FOTG210_UDC
#
# configuration written to .config
#
```


#### For users of Debian and its derivatives

Debian and its derivatives use a certificate to sign the kernel modules. This certificate, by default, is absent on your computer.

I recommend disabling the option that enables module signing. It can be achieved with the following commands:

`./scripts/config --file .config --disable MODULE_SIG`


**Failing to do this will result in a build failure later on, when you build the Linux kernel. You have been warned.**

### Using a custom configuration

If you are learning about building the Linux kernel for the purposes of learning kernel development, this is the way to follow.


**There are no guarantees that deviating away from your Linux distribution's configuration will work "normally" on your**

**physical hardware****The issue may range from a particular piece of hardware not working, to the Linux kernel not booting at all.**

**.**Therefore, it is recommended only for use inside a VM.

You can take a look at the [output of make help](https://www.kernel.org/doc/makehelp.txt) to see

*all*the available options, but we will focus on three

`make`

targets:`defconfig`

: The default configuration.`allmodconfig`

: Based on the current system state, build items as loadable modules (instead of built-in) when possible.`tinyconfig`

: A tiny Linux kernel.

Since the `tinyconfig`

target will only build a few items, the build times are naturally faster. I personally use it for the following reasons:

- Checking if any changes I made in the code/toolchain is correct and that the code compiles.
- Testing only a few select features inside a VM.


**The**

**tinyconfig**

**target will not enable the option to build DTBs and your kernel will most likely fail from starting.**Though, you can use QEMU to boot the Linux kernel without any DTB. But this article will not focus on that. Maybe you should comment and let me know to cover it sometime later ;)

**You should use the defconfig target unless you know exactly what you're doing.** Following is how it looks on my computer:

```
$ make defconfig
HOSTCC scripts/basic/fixdep
HOSTCC scripts/kconfig/conf.o
HOSTCC scripts/kconfig/confdata.o
HOSTCC scripts/kconfig/expr.o
LEX scripts/kconfig/lexer.lex.c
YACC scripts/kconfig/parser.tab.[ch]
HOSTCC scripts/kconfig/lexer.lex.o
HOSTCC scripts/kconfig/menu.o
HOSTCC scripts/kconfig/parser.tab.o
HOSTCC scripts/kconfig/preprocess.o
HOSTCC scripts/kconfig/symbol.o
HOSTCC scripts/kconfig/util.o
HOSTLD scripts/kconfig/conf
*** Default configuration is based on 'defconfig'
#
# configuration written to .config
#
```


### Modifying the configuration

You created a `.config`

file using some method. Either you used the one that your Linux distribution used and updated it, or you created one using the `defconfig`

target.

Either way, you are looking for how to modify it. **The most reliable way to do this is via the menuconfig or nconfig target.**


Both targets do the same thing but have a different interface for you. That's the only difference between them. I prefer to use the `menuconfig`

target but lately I've been leaning towards `nconfig`

since it is a bit more intuitive in searching for options.

Start with running the `make`

command with the `menuconfig`

target:

```
$ make menuconfig
HOSTCC scripts/kconfig/mconf.o
HOSTCC scripts/kconfig/lxdialog/checklist.o
HOSTCC scripts/kconfig/lxdialog/inputbox.o
HOSTCC scripts/kconfig/lxdialog/menubox.o
HOSTCC scripts/kconfig/lxdialog/textbox.o
HOSTCC scripts/kconfig/lxdialog/util.o
HOSTCC scripts/kconfig/lxdialog/yesno.o
HOSTLD scripts/kconfig/mconf
```


Now, in there, modify the configuration options to toogle them based on their type.

There are two types of toggleable options:

- Boolean-state options: Options that can only be turned off (
`[ ]`

) or on, as built-in (`[*]`

). - Tri-state options: Options that can be off (
`< >`

), or built-in (`<*>`

), or built as loadable-module (`<M>`

).

To know more information about an option, navigate to it using the up/down arrow keys and then press the `<TAB>`

key until the `< Help >`

option at the bottom is selected. And then, press the `<Enter>`

key to select it. A help menu about that configuration option item will be displayed.

**Please be careful when you modify an option.**

Once you have configured it to your heart's content, press the `<TAB>`

key until the `< Save >`

option at the bottom is selected. Then, press the `<Enter>`

key to select it. Press the `<Enter>`

key again (**without changing the filename**) to save the updated configuration to the `.config`

file.

## Building the Linux kernel

Building the Linux kernel is simple. But before we do that, let's tag our custom kernel build. I will use the string `-pratham`

as the tag and make use of the `LOCALVERSION`

variable to do that. This can be configured using the following command:

`./scripts/config --file .config --set-str LOCALVERSION "-pratham"`


What this does is, set the `CONFIG_LOCALVERSION`

configuration option in the `.config`

file to the string I specify at the end, which, in my case is `-pratham`

. Don't feel pressured to use my name ;)

The `LOCALVERSION`

option is used to set a "local" version which gets appended to the usual, *x.y.z* versioning scheme and reported when you run the `uname -r`

command.

Since I am building the kernel 6.5.5 with the `LOCALVERSION`

string set to `-pratham`

, for me, it will be `6.5.5-pratham`

. This is done to make sure that the custom kernel that I have built does not conflict with the distribution provided kernel.

Now, let's build the kernel itself. Following is the command to do so:

`make -j$(nproc) 2>&1 | tee log`


**This is sufficient for 99% of the users.**

The `-j`

option is used to specify how many parallel compilation jobs should be created. And the `nproc`

command returns a number for the amount of processing units that are available (this includes threads). So `-j$(nproc)`

means "use as many parallel compilation jobs as many CPU threads I have".

The `2>&1`

will redirect STDOUT and STDIN to the same file descriptor and that gets piped to the `tee`

command, which will store the output a file called `log`

and also print the same text to the console. This is in case you face a build error and want to take a look back at the log to check what went wrong. In that case you can simply do a `grep Error log`

.

### Custom 'make' targets

There are a few custom targets that you can use with the `make`

command to perform various operations in the Linux kernel's source directory. These are as a reference to developers. If your sole intention is to install a newer Linux kernel than what your distribution offers, you can skip this part ;)

#### Build targets

As a developer, there will be times when you want to build only the Linux kernel, or, only the modules, or only the DTBs. In that case, you can specify a build target and `make`

will build only the one(s) specified, and nothing else.

The build targets are as following:

`vmlinux`

: The bare Linux kernel.`modules`

: The loadable modules.`dtbs`

: Device-tree binaries (mostly for for ARM and RISC-V architectures).`all`

: Build everything [that is marked with an asterisk`*`

(from the output of`make help`

)].

Generally speaking, you do not need to specify either build target since they should automatically be build. These are for times when you want to test something only in one build target, and not in others.

Depending on your [computer's architecture](https://itsfoss.com/arm-aarch64-x86_64/), the name of the Linux kernel image that gets built (which is stored in `/boot`

) will vary.

For `x86_64`

, the Linux kernel's [default] image name is `bzImage`

. So, if you only want to build the Linux kernel for the purposes of booting it, you can specify `bzImage`

as a target, like so:

```
## For x86_64
$ make bzImage
```


"And how do I find the target's name to call `make`

with, on my architecture?"

There are two methods. Either, you can do a `make help`

and look for the first option under "Architecture specific targets" that has an asterisk `*`

before it.

Or, if you want to automate it, you can get the full (relative) path of the image using the `image_name`

target. Optionally, add the `-s`

flag to keep the output useful.

Following is the output from three computers I own, one `x86_64`

, another `AArch64`

and third one being `riscv`

:

```
## x86_64
$ make -s image_name
arch/x86/boot/bzImage
## AArch64
$ make -s image_name
arch/arm64/boot/Image.gz
## RISC-V
$ make -s image_name
arch/riscv/boot/Image.gz
```


And now, to build just the Linux kernel image, you can do this:

`make $(make -s image_name | awk -F '/' '{print $4}')`


#### Targets for clean-up

In case you want to clean build artifacts up, you can use either of the following targets to achieve what you want:

`clean`

: Remove almost everything except for the`.config`

file.`mrproper`

: Everything that`make clean`

does, but also delete the`.config`

file.`distclean`

: Everything that`make mrproper`

does but also remove any patch files.

## Installation

Once the Linux kernel has been compiled, it is time to install a few things. "A few *things*?" Yes. We build at least 2 different things, 3 if you are on ARM or RISC-V. I will explain as we proceed.

**Please understand that if you go a custom route, you are on your own. These defaults exist for a reason ;)**

**it is not recommended to do it unless you know what you are doing!**### Install the kernel modules

There are parts of the Linux kernel that are not necessary during booting. These parts are built as loadable modules (i.e. loaded and unloaded when necessary).

So, let's install these modules. This can be achieved with the `modules_install`

target. **The use of sudo is necessary** since the modules will be installed in

`/lib/modules/<kernel_release>-<localversion>`

and that directory is owned by `root`

, not your user.This will not only install the kernel modules, but also sign them. So it will take some time. The good news is that you can parallelize this using the previously discussed `-j$(nproc)`

option ;)

`sudo make modules_install -j$(nproc)`


**Note for developers:** You can specify a different path where the Linux modules are stored (instead of `/lib/modules/<kernel_release>-<localversion>`

) using the `INSTALL_MOD_PATH`

variable like so:

`sudo make modules_install INSTALL_MOD_PATH=<path>`


**Another note for developers:** You can use the `INSTALL_MOD_STRIP`

variable to specify if the modules should be stripped of debug symbols or not. The debug symbols are **not stripped if it is undefined**. When set to `1`

, they are stripped using the `--strip-debug`

option, which is then passed to the `strip`

(or `llvm-strip`

if Clang is used) utility.

### [Optional] Installing the Linux kernel Header files

If you intend to use this kernel with out-of-tree modules, like ZFS or Nvidia DKMS, or try writing your own modules, you will most likely need the header files provided by the Linux kernel.

The Linux kernel headers can be installed using the `headers_install`

target, like so:

`sudo make headers_install`


**The use of sudo is necessary** because the headers are installed in the

`/usr`

directory. The child directories `include/linux`

are also created inside `/usr`

and the headers are installed inside `/usr/include/linux`

.**Note for developers:** The path for installing Linux kernel headers can be overridden by using the `INSTALL_HDR_PATH`

variable.

### Installing DTBs (only for ARM and RISC-V)

**If you are on x86_64, you can skip this step!**

If you built for ARM or RISC-V, it is very likely that running `make`

also built the device-tree binaries. You can check that by checking for `.dtb`

files in `arch/<machine_architecture>/boot/dts`

.

I have a hack to check this:

```
## For AArch32
$ find arch/arm/boot/dts -name "*.dtb" -type f | head -n 1 > /dev/null && echo "DTBs for ARM32 were built"
## For AArch64
$ find arch/arm64/boot/dts -name "*.dtb" -type f | head -n 1 > /dev/null && echo "DTBs for ARM64 were built"
## For RISC-V
$ find arch/riscv/boot/dts -name "*.dtb" -type f | head -n 1 > /dev/null && echo "DTBs for RISC-V were built"
```


If you get a message saying "DTBs for <arch> were built", proceed with installing DTBs. That is done with the `dtbs_install`

target.

**The use of sudo is necessary** since this will be installed in

`/boot/dtb-<kernel_release>-<localversion>`

which is owned by `root`

.`sudo make dtbs_install`


**Note for developers:** Just like installing modules, you can specify a custom path for where the device-tree binaries are installed using the `INSTALL_DTBS_PATH`

variable.

### Install the Linux kernel

Finally, we are installing the Linux kernel itself! This is done with the `install`

target, like so:

`sudo make install`


**The use of sudo is necessary** here because the Linux kernel gets installed in

`/boot`

which your normal user does not have permission to write in.**target will also update the bootloader, but if it fails, it means you probably have an unsupported bootloader. If you are not using GRUB as your bootloader, please read the manual of your bootloader ;)**

**install****Note for developers:** Not surprising this time; The `INSTALL_PATH`

variable is used to specify where the Linux kernel is installed, instead of the default path which is in `/boot`

.

### For Arch Linux users

If you tried running the `make install`

command, you might have noticed that you got an error. Like following:

```
$ sudo make install
INSTALL /boot
Cannot find LILO.
```


To actually install the Linux kernel on Arch Linux, we need to copy the Linux kernel image manually. Don't worry, if you are using Arch Linux, you're probably used to doing things manually anyways. ( ͡° ͜ʖ ͡°)

This can be done with the following command:

`sudo install -Dm644 "$(make -s image_name)" /boot/vmlinuz-<kernel_release>-<localversion>`


Since I compiled the 6.5.5 kernel, I will run the following command, adjust it as per your needs:

`sudo install -Dm644 "$(make -s image_name)" /boot/vmlinuz-6.5.5-pratham`


It is not necessary, but you should also copy a file called `System.map`

, and while you are at it, copy the `.config`

file too ;)

```
sudo cp -vf System.map /boot/System.map-<kernel_release>-<localversion>
sudo cp -vf .config /boot/config-<kernel_release>-<localversion>
```


#### Generate the initial ramdisk

You might have come across a utility called `mkinitcpio`

when you installed Arch Linux. We are going to use it to create the initial ramdisk.

To do that, we need a preset first. Do so by adding the following contents to the `/etc/mkinitcpio.d/linux-<localversion>.preset`

file. Substitute `<kernel_release>`

and `<localversion>`

as necessary.

```
ALL_config="/etc/mkinitcpio.conf"
ALL_kver="/boot/vmlinuz-<kernel_release>-<localversion>"
PRESETS=('default' 'fallback')
default_image="/boot/initramfs-<kernel_release>-<localversion>.img"
fallback_options="-S autodetect"
```


Once you do that, run the following command to generate the initial ramdisk:

`sudo mkinitcpio -p linux-<localversion>`


Following is the output from my computer, yours should be similar too!

```
$ sudo mkinitcpio -p linux-pratham
==> Building image from preset: /etc/mkinitcpio.d/linux-pratham.preset: 'default'
==> Using configuration file: '/etc/mkinitcpio.conf'
-> -k /boot/vmlinuz-6.5.5-pratham -c /etc/mkinitcpio.conf -g /boot/initramfs-6.5.5-pratham.img
==> Starting build: '6.5.5-pratham'
-> Running build hook: [base]
-> Running build hook: [udev]
-> Running build hook: [autodetect]
-> Running build hook: [modconf]
-> Running build hook: [kms]
-> Running build hook: [keyboard]
==> WARNING: Possibly missing firmware for module: 'xhci_pci'
-> Running build hook: [keymap]
-> Running build hook: [consolefont]
==> WARNING: consolefont: no font found in configuration
-> Running build hook: [block]
-> Running build hook: [filesystems]
-> Running build hook: [fsck]
==> Generating module dependencies
==> Creating zstd-compressed initcpio image: '/boot/initramfs-6.5.5-pratham.img'
==> Image generation successful
==> Building image from preset: /etc/mkinitcpio.d/linux-pratham.preset: 'fallback'
==> Using configuration file: '/etc/mkinitcpio.conf'
==> WARNING: No image or UKI specified. Skipping image 'fallback'
```


The initial ramdisk has been generated. It is now time to move onto updating the bootloader!

#### Update GRUB

Once all the necessary files are in their usual destination, it is now time to update GRUB.

Update the GRUB bootloader using the following command:

`sudo grub-mkconfig -o /boot/grub/grub.cfg`


**Updating GRUB won't make the newer kernel the default. Please select it from the boot menu during boot.**

You can select the newer version of the Linux kernel by going into the 'Advanced options for Arch Linux' menu item, and then select the menu item that says 'Arch Linux, with Linux <kernel_release>-<localversion>'.

## Reboot

Congratulations! You have completed all the steps to getting the Linux kernel's source, configuring it, building it and installing it. It is time to reap the benefits of your hard work by rebooting and booting into the newly built+installed Linux kernel.

Please be sure to select the correct Linux kernel version from the bootloader. Once booted, run the `uname -r`

command to verify that you booted using the intended Linux kernel.

Below is the output from my computer:

```
$ uname -r
6.5.5-pratham
```


**Party time! **🎉

## Uninstallation

Either your Linux distribution shipped the Linux kernel with the version that you compiled manually, or you compiled another, newer kernel yourself and noticed that you should uninstall the older kernel to make space for the newer one(s).

And now, you are wondering how you can undo that. Well, there is no `make uninstall`

that you can run, but that doesn't mean that all hope is lost!

We know where all the files are installed, so that makes it easier to remove it.

```
## Remove kernel modules
$ rm -rf /lib/modules/<kernel_release>-<localversion>
## Remove device-tree binaries
$ rm -rf /boot/dtb-<kernel_release>-<localversion>
## Remove the Linux kernel itself
$ rm -vf /boot/{config,System,vmlinuz}-<kernel_release>-<localversion>
```


## Conclusion

Quite an adventure, ain't it? But finally, it is concluded. We have looked at the entire process of what it takes to manually compile the Linux kernel. It involved installing the dependencies, fetching the source, verifying it, extracting it, configuring the Linux kernel, building the Linux kernel and then installing it.

If you liked this detailed step-by-step guide, please comment and let me know. If you faced any issues, comment and let me know!