Linux内核交叉编译(AARCH64)

步骤

1. 获取内核源码

1
2
3
wget https://git.kernel.org/torvalds/t/linux-6.14-rc2.tar.gz

# 解压...

2. 安装交叉编译工具链

1
2
sudo apt install fakeroot build-essential ncurses-dev xz-utils libssl-dev bc flex libelf-dev bison
sudo apt install gcc-aarch64-linux-gnu

3. 配置内核

1
2
3
4
5
6
export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-
cp arch/arm64/configs/defconfig ./.config
make menuconfig
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- Image -j12
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- modules -j12

4. 制作根文件系统

获取Busybox

1
2
wget https://busybox.net/downloads/busybox-1.32.1.tar.bz2
# 解压...

配置Busybox

1
2
3
make defconfig
make menuconfig
# Enable static binary、禁用tc(Ubuntu 24环境下编译)

编译Busybox

1
2
make -j12
make install

制作根文件系统(setup_rootfs.sh)

1
2
3
4
5
6
7
$ cat setup_rootfs.sh
#!/bin/sh
qemu-img create rootfs.img 512m
mkfs.ext4 rootfs.img
sudo mount rootfs.img rootfs
sudo cp -rf _install/* rootfs/
sudo umount rootfs

自定义根文件系统(rootfs)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ cat rootfs/etc/inittab
::sysinit:/etc/init.d/rcS
::respawn:-/bin/sh
::askfirst:-/bin/sh
::ctrlaltdel:/bin/umount -a -r

$ cat rootfs/etc/fstab
proc /proc proc defaults 0 0
tmpfs /tmp tmpfs defaults 0 0
sysfs /sys sysfs defaults 0 0

$ cat rootfs/etc/init.d/rcS
#!/bin/sh
mount -a

5. 启动内核

1
qemu-system-aarch64 -machine virt,virtualization=true,gic-version=3 -nographic -m size=1024M -cpu cortex-a72 -smp 2 -kernel ../../linux-stable.git1/arch/arm64/boot/Image -drive format=raw,file=rootfs.img -append "root=/dev/vda rw"

定制

1. 选择其他编译器

虽然Ubuntu官方仓库已经提供了aarch64-linux-gnu-gcc,但是“定制”肯定也包括选择其他编译器,所以决定记录一下。

这里以使用Bootlin提供的aarch64-linux-gcc为例。

1
2
wget https://toolchains.bootlin.com/downloads/releases/toolchains/aarch64/tarballs/aarch64--musl--bleeding-edge-2024.05-1.tar.xz
# 解压...

设置环境变量:

1
2
3
4
5
6
7
8
9
10
11
export PATH="/toolchains/aarch64--musl--bleeding-edge-2024.05-1/bin/:$PATH"
export CC="aarch64-linux-gcc"
export CXX="aarch64-linux-g++"

# 以下为其他可选设置
export LD="aarch64-linux-ld"
export AR="aarch64-linux-ar"
export AS="aarch64-linux-as"

# 设置sysroot
export CC="aarch64-linux-gcc --sysroot=/toolchains/aarch64--musl--bleeding-edge-2024.05-1/aarch64-buildroot-linux-musl/sysroot/"

此外,由于某些编译器提供厂商无法覆盖所有的库,所以我们需要手动安装一些第三方库(zlib、libcurl…)。例如:

1
2
3
4
5
6
7
8
9
10
11
# 下载所需类库(源代码)
wget https://www.zlib.net/current/zlib.tar.gz
tar xzf zlib.tar.gz
cd zlib-1.3.1

# 配置编译选项,指定安装路径(放入交叉编译器环境的sysroot下)
./configure --prefix=$(pwd)/../../sysroot/usr/ # 即/toolchains/aarch64--musl--bleeding-edge-xxxx.xx/aarch64-buildroot-linux-musl/sysroot/usr/,这里我们根据自身系统环境进行计算

# 编译 & 安装
make -j$(nproc)
make install

不同第三方库的安装方法可能略微不同,不过主要步骤差别不大,这里不再赘述。

2. 编译用户程序

Linux常用的运行时库包括glibc、musl、uclibc等,这里使用了musl。在交叉编译时,我们可能需要另外设置程序的linker的地址,通过:

1
2
3
4
5
6
7
export LDFLAGS="-Wl,--dynamic-linker=/lib/musl/ld-musl-aarch64.so.1,-rpath=/lib/musl"

# 编译
make clean
./configure --host=aarch64-linux --with-randomdev=no --prefix=$(pwd)/myout-musl/
make -j$(nproc)
make install

3. 部署至操作系统

编译成功后,我们将生成的可执行的用户程序复制到rootfs某个目录即可。

在此之前,我们需要先配置好上述的musl运行时库(如果使用到了)。方法如下:

1
2
sudo mkdir -p rootfs/lib/musl/
sudo cp -rf /toolchains/aarch64--musl--bleeding-edge-2024.05-1/aarch64-buildroot-linux-musl/sysroot/lib/* rootfs/lib/musl/

将上述逻辑封装至脚本setup_rootfs.sh的umount操作前即可。

4. 测试

这里我们使用一个hello程序测试交叉编译出的系统是否正常。

1
2
3
4
5
6
7
8
9
10
11
$ cat hello.c
#include <stdio.h>
int main()
{
printf("Hello, world!\n");
return 0;
}

$ aarch64-linux-gcc --sysroot=/toolchains/aarch64--musl--bleeding-edge-2024.05-1/aarch64-buildroot-linux-musl/sysroot/ hello.c -o hello2 -Wl,--dynamic-linker=/lib/musl/ld-musl-aarch64.so.1,-rpath=/lib/musl/
$ file hello2
hello2: ELF 64-bit LSB pie executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/musl/ld-musl-aarch64.so.1, not stripped

启动Qemu,运行并查看结果: