Linux Kernel & 文件系统编译

警告
本文最后更新于 2022-10-21,文中内容可能已过时。

Linux Kernel

内核(kernel)

内核版本

https://www.kernel.org/ 上可以看到最新的稳定版,在我写文时最新版本是 5.16.15

image-20220317180537311

内核编译

安装编译所需的库

1
sudo apt-get install libncurses5-dev build-essential kernel-package ibdw-dev dwarves zstd libssl-dev libelf-dev

内核文件下载与解压

想要编译内核,首要的肯定是先把源码下载下来,这里给出几个下载地址(镜像站)。在我的网络环境下,北京交通大学的镜像站速度最快。

1
2
3
4
5
https://www.kernel.org/pub/
https://mirrors.edge.kernel.org/pub/linux/kernel
https://mirrors.tuna.tsinghua.edu.cn/kernel/
https://mirror.bjtu.edu.cn/kernel/linux/kernel/
https://mirrors.ustc.edu.cn/kernel.org/linux/kernel/

我这里选择的是我编写文章时的最新版

1
2
linux-5.16.15.tar.xz	121.7 MiB	2022-03-16 21:36
https://mirrors.tuna.tsinghua.edu.cn/kernel/v5.x/linux-5.16.15.tar.xz

使用 wget 对文件进行下载,使用 tar 对压缩包进行解压

1
2
wget https://mirror.bjtu.edu.cn/kernel/linux/kernel/v5.x/linux-5.16.15.tar.gz
tar -xzvf linux-5.16.15.tar.gz

编译选项配置

1
2
cd linux-5.16.15
make menuconfig

打开后会出现这样的界面,可以在里面来调整一些编译选项,具体操作可以看顶部的介绍。

image-20220317184259889

我们这里开启调试相关的选项

勾选 Kernel hacking -> Compile-time checks and compiler options -> Compile the kernel with debug info,现在似乎是默认开启的。

image-20220317184530128

如果需要使用 kgdb 调试内核,则需要选中 Kernel hacking -> Generic Kernel Debugging Instruments -> KGDB: kernel debugger 下的所有选项,我这里暂时不需要,故不开启。

全部设置完毕后,选择 Save 保存设置。

编译内核

可以根据机器的核数来选择具体配置,编译过程大概十分钟左右。

1
2
3
4
编译主体
sudo make bzImage -j8
编译模块
sudo make modules -j8

编译成功后会提示,本机是 x86 的,默认编译了 x86 的内核

1
Kernel: arch/x86/boot/bzImage is ready  (#3)

坑点

make[1]: *** 没有规则可制作目标“debian/canonical-certs.pem”,由“certs/x509_certificate_list” 需求。 停止。

搜索到一些相关内容,似乎是因为编译机器不是 Debian 系统,这个位置没有证书,解决方案如下

1
gedit .config

搜索到以下配置项并修改为

1
2
CONFIG_SYSTEM_TRUSTED_KEYS=""
CONFIG_SYSTEM_REVOCATION_KEYS=""

交叉编译 ARM64

编译过程有少许的不同

从 ARM64 路径下复制默认配置

1
cp ./arch/arm64/configs/defconfig  .config

配置选项

在原来的编译命令上,添加 ARCH 来指定目标指令集

1
make menuconfig ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu-

编译内核

从 bzImage 变成了 Image,编译后在根目录下会产生 vmlinux*,arch/arm64/boot/ 下会产生 Image 和 Image.gz

1
make Image -j8 ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu-

内存文件系统(initrd)

BusyBox

可以使用 busybox 来构建一个简单的文件系统

编译 BusyBox

https://busybox.net/ 可以找到 busybox 的最新版本,我这里编译 BusyBox 1.35.0

image-20220317194257208

下载源码

1
2
wget https://busybox.net/downloads/busybox-1.35.0.tar.bz2
tar -xvf busybox-1.35.0.tar.bz2

配置

1
2
cd busybox-1.35.0
make menuconfig
  • 在 Setttings 选中 Build static binary (no shared libs),将 busybox 编译为静态链接的文件
  • 在 Linux System Utilities 中取消选中 Support mounting NFS file systems on Linux < 2.6.23 (NEW),关闭网络文件系统
  • 在 Networking Utilities 中取消选中 inetd,关闭 Internet 超级服务器

编译

1
2
make -j8
make install

在源码根目录下就会有一个 _install 目录,下面存放着编译好的文件

BusyBox 交叉编译

对于 busybox 来说,在 menuconfig 中就可以直接配置交叉编译,具体位置在 Settings -> Cross compiler prefix,可以将其设置为 aarch64-linux-gnu- (对于编译 aarch64 来说)

image-20220318012057094

配置后和原来一样编译就可以得到一份 aarch64 架构的 busybox

image-20220318012320083

配置文件系统

1
2
cd _install
mkdir -p  proc sys dev etc/init.d

添加启动文件,并且加上可执行权限

1
2
gedit etc/init.d/rcS
chmod +x etc/init.d/rcS

init 文件内容

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#!/bin/sh
echo "INIT SCRIPT"
mkdir /tmp
mount -t proc none /proc
mount -t sysfs none /sys
mount -t devtmpfs none /dev
mount -t debugfs none /sys/kernel/debug
mount -t tmpfs none /tmp
echo -e "Boot took $(cut -d' ' -f1 /proc/uptime) seconds"
setsid /bin/cttyhack setuidgid 1000 /bin/sh

打包文件系统

1
find . | cpio -o --format=newc > ../rootfs.img

Buildroot

https://buildroot.org/download.html

image-20220318024211295

Buildroot 下载与解压

1
2
wget https://buildroot.org/downloads/buildroot-2022.02.tar.xz
tar -xvf buildroot-2022.02.tar.x

编译选项配置

1
2
cd buildroot-2022.02
make menuconfig

在弹出的配置界面中:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Target options  ---> 选择目标板架构特性。
Build options  ---> 配置编译选项。
Toolchain  ---> 配置交叉工具链,使用buildroot工具链还是外部提供
System configuration  ---> 配置生成的根文件系统中所需功能
Kernel  ---> 配置kernel是否编译以及编译选项
Target packages  ---> 配置生成的根文件系统中的工具以及库
Filesystem images  ---> 配置生成的根文件系统的格式,是ext2还是其他
Bootloaders  ---> 配置使用哪种bootloader以及编译选项uboot只是其中一种
Host utilities  ---> 主机使用功能
Legacy config options  ---> 以前遗留的配置选项

交叉编译

  • Target option ---> Target ArchitectureAArch64 (little endian)
  • Toolchain ---> Toolchain typeExternal toolchain,这时我们可以看到 Toolchain ---> Toolchain 的值为 Arm AArch64 xxxx.xx

设置密码

  • System configuration ---> Enable root login with password 开启
  • System configuration ---> Root passwordxxxx(任意的你喜欢的密码)

交叉编译

  • System configuration ---> Run a getty (login prompt) after boot ---> TTY port 的值为 ttyAMA0(这一条非常重要,不然虚拟机可能启动不了,如果出现 can’t open /dev/ttyAMA0: No such file or directory,就改回 console)
  • Target packages ---> Show packages that are also provided by busybox 开启
  • Target packages ---> Debugging, profiling and benchmark ---> strace 开启
  • Filesystem images ---> cpio the root filesystem 开启。

启动欢迎语

  • System configuration ---> System hostname
  • System configuration ---> System banner

配置网卡 DHCP

  • System configuration ---> Network interface to configure through DHCP

编译源码

编译过程中使用到 wget,可能需要代理

1
sudo make -j8

坑点

gdb 编译失败

报错内容为 错误: ‘LONG_MIN’未声明(在此函数内第一次使用)

尝试添加以下 PATCH

buildroot/package/gdb/版本/0007-fix-LONG_MIN-undefined-in-libiberty.patch

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
From 5385e269c6ce1e0525628f378942315442a5841e Mon Sep 17 00:00:00 2001
From: zhongw <service@t-firefly.com>
Date: Sat, 21 Dec 2019 10:08:28 +0800
Subject: [PATCH] fix LONG_MIN undefined in libiberty

Signed-off-by: zhongw <service@t-firefly.com>
---
 libiberty/fibheap.c | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

diff --git a/libiberty/fibheap.c b/libiberty/fibheap.c
index a37ee4e..b344b17 100644
--- a/libiberty/fibheap.c
+++ b/libiberty/fibheap.c
@@ -34,6 +34,22 @@ Boston, MA 02110-1301, USA.  */
 #include "libiberty.h"
 #include "fibheap.h"

+/* FIXME: It'd be nice to configure around these, but the include files are too
+   painful.  These macros should at least be more portable than hardwired hex
+   constants. */
+
+#ifndef ULONG_MAX
+#define ULONG_MAX       ((unsigned long)(~0L))          /* 0xFFFFFFFF */
+#endif
+
+#ifndef LONG_MAX
+#define LONG_MAX        ((long)(ULONG_MAX >> 1))        /* 0x7FFFFFFF */
+#endif
+
+#ifndef LONG_MIN
+#define LONG_MIN        ((long)(~LONG_MAX))             /* 0x80000000 */
+#endif
+

 #define FIBHEAPKEY_MIN	LONG_MIN

buildroot/package/gdb/版本/0008-fix-to-use-fcntl-include-in-libiberty.patch

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
From 9dbe3dd463ac209cf9dc99d83e9446ca743ad0ec Mon Sep 17 00:00:00 2001
From: zhongw <service@t-firefly.com>
Date: Sat, 21 Dec 2019 10:17:55 +0800
Subject: [PATCH] fix to use fcntl include in libiberty

Signed-off-by: zhongw <service@t-firefly.com>
---
 libiberty/pex-unix.c | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/libiberty/pex-unix.c b/libiberty/pex-unix.c
index b48f315..b27d699 100644
--- a/libiberty/pex-unix.c
+++ b/libiberty/pex-unix.c
@@ -28,6 +28,10 @@ Boston, MA 02110-1301, USA.  */
 #include <stdio.h>
 #include <signal.h>
 #include <errno.h>
+
+/* Define to 1 if you have the <fcntl.h> header file. */
+#define HAVE_FCNTL_H 1
+
 #ifdef NEED_DECLARATION_ERRNO
 extern int errno;
 #endif

添加后,需要先 make clean,再重新 make,此时会把新加入的 Patch 文件应用

QEMU 中添加网卡后网络无法使用

方法一

需要在编译选项中配置网卡 DHCP,然后重新 sudo make clean、sudo make

image-20220318140216152

方法二

如果你不想重新编译的话,也可以直接修改配置文件 buildroot/output/target/etc/network/interfaces

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# interface file auto-generated by buildroot

auto lo
iface lo inet loopback

auto eth0
iface eth0 inet dhcp
  pre-up /etc/network/nfs_check
  wait-delay 15
  hostname $(hostname)

重新启动后可以看到使用 udhcpc 去获取 DHCP 分配的 IP 地址

image-20220318135156440

ipconfig 也能够正常显示出 IP 地址

image-20220318135002005

Qemu 模拟环境

接下来就要用 qemu-system-xxx 来启动我们编译好的内核,不过首先先对相关的配置参数做一个介绍

常用的选项

  • -m, 指定 RAM 大小,默认 384M
  • -kernel,指定内核镜像文件路径
  • -initrd,设置内核启动的内存文件系统
  • -smp [cpus=]n[,cores=cores][,threads=threads][,dies=dies][,sockets=sockets][,maxcpus=maxcpus],指定使用到的核数。
  • -cpu,指定指定要模拟的处理器架构,可以同时开启一些保护,如
    • +smap,开启 smap 保护
    • +smep,开启 smep 保护
  • -nographic,表示不需要图形界面
  • -monitor,对 qemu 提供的控制台进行重定向,如果没有设置的话,可以直接进入控制台,对于出题来说,最好设置为 null
  • -append,内核参数(以空格分开的一个字符串列表)
    • nokaslr 关闭随机偏移
    • console=ttyS0,和 nographic 一起使用,启动的界面就变成了当前终端。
    • root=xxx,告诉内核启动时使用哪个设备作为根文件系统,如果是某个分区,可以用 /dev/hda8;如果是在内存上,可以用 /dev/mem
    • init=… 告诉内核在启动后执行哪个程序。如果该选项未被设置,将会启动/sbin/init
    • rootwait 告诉内核无限期等待,直到根文件系统出现。对于 U 盘等设备,设备的发现是异步的,如果在发现 USB 设备之前挂载根文件系统,则内核会找不到根文件系统,造成启动失败。
0%