跳转至

构建 RISC-V 64 Linux 内核和 Busybox

环境准备

建议使用 Vlab 的 Ubuntu 24.04 LXC 虚拟环境。

更新并安装所需的软件:

sudo apt update
sudo apt upgrade -y && sudo apt install -y build-essential git bc bison flex libssl-dev libncurses-dev qemu-system-misc gcc-riscv64-linux-gnu gdb gdb-multiarch ncat telnet

下载 Linux 源代码:

git clone --filter=tree:0 https://mirrors.ustc.edu.cn/linux.git
cd linux
du -h | tail

可以看到 Linux 内核的源代码大小在 clone 后达到了 3GB,这其中 1.3GB 是 .git 目录下的 Git 仓库数据。

然后,设置交叉编译选项:

export ARCH=riscv
export CROSS_COMPILE=riscv64-linux-gnu-

提示:如果你在中间做错了某一步骤,可以用下面的命令来清理环境并重新开始,无需再次下载:

git reset --hard
git clean -fdx

第一次交叉编译

初始化配置

我们只需要一个极小的内核,因此:

make defconfig

然后,由于 QEMU 里面没有什么设备,因此不需要编译很多驱动程序,只保留我们需要的即可。这里可以使用 make menuconfig 来进行交互式的配置,但是由于需要修改的地方比较多,交互式操作可能不太方便。所以,使用配置切片(config fragment)来完成配置。在 Linux 源代码根目录下编写 qemu.config 并填入下面的内容:

CONFIG_SERIAL_8250=y
CONFIG_SERIAL_8250_CONSOLE=y
CONFIG_OF=y
CONFIG_OF_FLATTREE=y
CONFIG_VIRTIO=y
CONFIG_VIRTIO_MMIO=y

CONFIG_MODULES=n
CONFIG_NET=n
CONFIG_USB=n
CONFIG_SOUND=n
CONFIG_DRM=n

CONFIG_CC_OPTIMIZE_FOR_SIZE=y

构建

接下来将它合并到当前的 .config 中并且开始编译:

scripts/kconfig/merge_config.sh .config qemu.config
make olddefconfig
make -j$(nproc)

在这个过程中,make 可能会询问一些问题,类似

Vector extension support (RISCV_ISA_V) [Y/n/?] (NEW)

一般来说,直接使用默认值(按 Enter)即可。

如果直接按照 defconfig 构建的话,make 在 drivers/ 阶段花费了大量的时间——但其实这里很多驱动不是我们所需要的,它们也同时占据了编译出来的内核的相当大小。因此,建议从精简驱动开始裁剪嵌入式内核。此外,还可以通过 tinyconfig 得到一个精简的配置。

最后的输出是:

  OBJCOPY vmlinux
  GEN     modules.builtin.modinfo
  GEN     modules.builtin
  OBJCOPY arch/riscv/boot/Image
  Kernel: arch/riscv/boot/Image is ready
  GZIP    arch/riscv/boot/Image.gz
  Kernel: arch/riscv/boot/Image.gz is ready

这就说明我们的内核准备好了!

在构建过程中,都可以使用 file 命令来看我们的构建产物的信息。例如:

$ file vmlinux
vmlinux: ELF 64-bit LSB shared object, UCB RISC-V, RVC, soft-float ABI, version 1 (SYSV), dynamically linked, BuildID[sha1]=618e9ee6b09fff369073f9dc28538a44cfcb5b3e, not stripped

这说明我们的交叉编译是正确的,产物 vmlinux 确实是 RISC-V 64 架构的二进制文件。

构建 initramfs 和 Busybox

为了让我们的内核有事情可做,可以使用 Busybox,提供一个极小化的环境。首先需要建立起最基本的文件系统结构:

mkdir -p rootfs/{bin,sbin,proc,sys,usr/bin,usr/sbin}

然后,获取 Busybox 的源代码并进入源码目录:

git clone --depth 1 https://git.busybox.net/busybox.git
cd busybox

先生成默认配置,再使用 make menuconfig 来进行交互式的配置:

make defconfig
make menuconfig

需要配置的有:

  • 选中 Settings -> Build static binary,确保产物是静态链接的;
  • 取消选中 Networking Utilities 下的所有选项;

保存退出后,使用 make -j$(nproc) 构建(之前导出的 CROSS_COMPILE 环境变量会自动生效)。构建完成后,产物应该具有如下的信息:

$ file busybox_unstripped
busybox_unstripped: ELF 64-bit LSB executable, UCB RISC-V, RVC, double-float ABI, version 1 (SYSV), statically linked, BuildID[sha1]=35d654cb925b199e53af27ddff8cd4b6c0b7995b, for GNU/Linux 4.15.0, not stripped

接下来,使用

make CONFIG_PREFIX=../rootfs install

将 Busybox 安装到你的 Initramfs 中。安装完成后,回到 Linux 源代码根目录:

cd ..

然后,创建 init 程序,这样我们才能让内核创建第一个进程:

cat > rootfs/init << 'EOF'
#!/bin/sh
mount -t proc none /proc
mount -t sysfs none /sys
echo "Hello from RISC-V init!"
exec /bin/sh
EOF

chmod +x rootfs/init

最后的打包

需要构建的东西已经完成了,接下来进行一下打包:

cd rootfs/
find . | cpio -o --format=newc | gzip > ../initramfs.cpio.gz
cd ..

运行内核

我们已经成功地准备好了第一次构建的产物。现在来运行一下。

qemu-system-riscv64 \
  -machine virt \
  -nographic \
  -kernel arch/riscv/boot/Image \
  -initrd initramfs.cpio.gz \
  -append "console=ttyS0"

你应该可以看到:

[    1.431314] Run /init as init process
Hello from RISC-V init!
/bin/sh: can't access tty; job control turned off
~ # ls
bin      init     proc     sbin     usr
dev      linuxrc  root     sys
~ #

成功了!