From eedfc1ffcedd511e665a0ed246bc23777a1a2813 Mon Sep 17 00:00:00 2001 From: jackfiled Date: Tue, 27 May 2025 14:25:27 +0800 Subject: [PATCH] blog: linux distribution from zero --- .../posts/linux-distribution-from-zero.md | 306 ++++++++++++++++++ .../image-20250325160729310.webp | 3 + .../image-20250325161310820.webp | 3 + .../image-20250527134233659.webp | 3 + .../image-20250527134421403.webp | 3 + .../image-20250527134540583.webp | 3 + .../image-20250527135748547.webp | 3 + 7 files changed, 324 insertions(+) create mode 100644 YaeBlog/source/posts/linux-distribution-from-zero.md create mode 100644 YaeBlog/source/posts/linux-distribution-from-zero/image-20250325160729310.webp create mode 100644 YaeBlog/source/posts/linux-distribution-from-zero/image-20250325161310820.webp create mode 100644 YaeBlog/source/posts/linux-distribution-from-zero/image-20250527134233659.webp create mode 100644 YaeBlog/source/posts/linux-distribution-from-zero/image-20250527134421403.webp create mode 100644 YaeBlog/source/posts/linux-distribution-from-zero/image-20250527134540583.webp create mode 100644 YaeBlog/source/posts/linux-distribution-from-zero/image-20250527135748547.webp diff --git a/YaeBlog/source/posts/linux-distribution-from-zero.md b/YaeBlog/source/posts/linux-distribution-from-zero.md new file mode 100644 index 0000000..4a06941 --- /dev/null +++ b/YaeBlog/source/posts/linux-distribution-from-zero.md @@ -0,0 +1,306 @@ +--- +title: 从零开始的Linux发行版生活 +date: 2025-05-27T14:22:45.9208348+08:00 +tags: +- Linux +- 技术笔记 +--- + + + 总有些时候我们需要自己组装Linux操作系统,比如交叉编译、嵌入式开发和可信执行环境开发等等场景。本文便介绍如何使用Arch Linux作为基础在`riscv`架构上组装操作系统并使用QEMU运行。 + + + +## 初始化根文件系统 + +`rootfs`是Linux系统中除了内核之外的其他文件的总和,例如`/usr`和`/etc`中重要的系统文件均属于`rootfs`的范围。在进行Linux系统的开发时,同一架构的`rootfs`之间基本上可以互换,例如可以把Arch Linux的`rootfs`替换到`ubuntu`系统中,而内核由于硬件的敏感性,通常需要使用特定厂商提供的内核(在更改合入upstream之前)。 + +> 实际上,除了各个发行版对于内核的修改,各个发行版之间主要的不同就是rootfs的不同。 + +首先创建一个`rootfs`文件夹并修改权限为`root`。 + +```bash +mkdir rootfs +sudo chown root:root ./rootfs +``` + +然后使用`pacstrap`这个`pacman`的初始化工具在`rootfs`安装`base`软件包,最好也顺便装一个`vim`。 + +```bash +sudo pacstrap \ + -C /usr/share/devtools/pacman.conf.d/extra-riscv64.conf + -M ./rootfs \ + base vim +``` + +`extra-riscv64.conf`是在`archlinuxcn/devtools-riscv64`软件包中提供的便利工具,其中包括了`archriscv`该移植的`pacman.conf`文件,当然一般推荐修改一下该文件的镜像站点,以提高安装的速度。 + +然后清理一下`pacman`的缓存文件,缩小`rootfs`的大小,尤其是考虑到后面因为各种操作失误可能会反复解压`rootfs`文件。 + +```bash +sudo pacman \ + --sysroot ./rootfs \ + --sync --clean --clean +``` + +然后设置一下该`rootfs`的`root`账号密码: + +```bash +sudo usermod --root $(realpath ./rootfs) --password $(openssl passwd -6 "$password") root +``` + +就可以将`rootfs`打包为压缩包文件备用了。 + +```bash +sudo bsdtar --create \ + --auto-compress --options "compression-level=9"\ + --xattrs --acls\ + -f archriscv-rootfs.tar.zst -C rootfs/ . +``` + +## 初始化虚拟机镜像 + +首先,创建一个`qcow2`格式的QEMU虚拟机磁盘镜像: + +```bash +qemu-img create -f qcow2 archriscv.img 10G +``` + +其中磁盘的大小可以自行定义。 + +为了能够像正常的磁盘一样进行读写,需要将该文件映射到一个块设备,而这通过`qemu-nbd`程序实现。首先需要加载该程序需要使用的内核驱动程序: + +```bash +sudo modprobe nbd max_part=8 +``` + +命令中的`max_part`指定了最多能够挂载的块设备(文件)个数。然后将该文件虚拟化为一个块设备: + +```bash +sudo qemu-nbd -c /dev/nbd0 archriscv.img +``` + +挂载完毕之后就可以进行初始化虚拟机磁盘镜像的工作了。初始化虚拟机镜像主要涉及到如下几步: + +- 格式化磁盘 +- 安装内核 +- 设置引导程序 + +其中格式化磁盘和后续需要使用的启动引导方式有关系,当使用U-boot这一常用的嵌入式引导系统进行引导时,只需要将磁盘格式化为单个分区即可,只需要在该分区中设置`extlinux/extlinux.conf`文件,至于磁盘的分区表格式是`GPT`还是`MBR`无关紧要。而如果是使用UEFI引导,则需要使用`GPT`分区表,并创建一个ESP(EFI System Partition)分区。这里就以使用UEFI引导的格式化磁盘作为示例,硬盘分区如下表所示: + +| 分区 | 格式 | 挂载点 | 大小 | +| ----------- | ----- | ------ | ---------- | +| /dev/nbd0p1 | FAT32 | /boot | 512M | +| /dev/nbd0p2 | EXT4 | / | 余下的空间 | + +在使用`fdisk`完成磁盘的分区之后,进行格式化并挂载到当前的`mnt`目录中: + +```bash +sudo mkfs.fat -F 32 /dev/nbd0p1 +sudo mkfs.ext4 /dev/nbd0p2 +sudo mkdir mnt +sudo mount /dev/nbd0p2 mnt +sudo mkdir mnt/boot +sudo mount /dev/nbd0p1 mnt/boot +``` + +挂载完成之后解压上一步中备好的`rootfs`: + +```bash +cd mnt +sudo bsdtar -kpxf ../archriscv.tar.zst +``` + +然后使用`systemd-nspawn`工具进入`rootfs`中调用`pacman`安装内核: + +```bash +sudo systemd-nspawn -D mnt pacman \ + --nonconfirm --needed \ + -Syu linux linux-firmware +``` + +接下来分别介绍使用U-boot启动和使用UEFI启动的操作方法。 + +### 使用U-boot启动 + +为了使用U-boot启动,需要手动编译U-boot并打包到OpenSBI中作为QEMU启动的固件。 + +首先编译U-boot: + +```bash +git clone --filter=blob:none -b v2025.04 https://github.com/u-boot/u-boot.git +cd u-boot +make \ + CROSS_COMPILE=riscv64-linux-gnu- \ + qemu-riscv64_smode_defconfig +./scripts/config +make \ + CROSS_COMPILE=riscv64-linux-gnu- \ + olddefconfig +make CROSS_COMPILE=riscv64-linux-gnu- -j18 +``` + +编译好之后检查当前目录下是否存在`u-boot.bin`的固件。 + +然后去编译OpenSBI并将`u-boot.bin`打包进来: + +```bash +git clone --filter=blob:none -b v1.6 https://github.com/riscv-software-src/opensbi.git +cd opensbi +make \ + CROSS_COMPILE=riscv64-linux-gnu- \ + PLATFORM=generic \ + FW_PAYLOAD_PATH=../u-boot/u-boot.bin -j18 +``` + +编译好的三个启动固件应当在`./build/platform/generic/firmware`目录中: + +- `fw_dynamic.bin`使用启动程序设置的地址进行跳转。 +- `fw_jump.bin`跳转到一个固定的地址执行。 +- `fw_payload.bin`执行编译打包的`u-boot`文件,这也是U-boot启动所需要的。 + +编译完成之后,在`mnt`文件中创建`/boot/extlinux/extlinux.conf`文件以告知U-boot启动Linux内核的参数: + +``` +menu title Arch RISC-V Boot Menu +timeout 100 +default linux-fallback + +label linux + menu label Linux linux + kernel /vmlinuz-linux + initrd /initramfs-linux.img + append earlyprintk rw root=UUID=903944ec-a4d3-4820-ac89-c0eac37721f9 rootwait console=ttyS0,115200 + +label linux-fallback + menu label Linux linux (fallback initramfs) + kernel /vmlinuz-linux + initrd /initramfs-linux-fallback.img + append earlyprintk rw root=UUID=903944ec-a4d3-4820-ac89-c0eac37721f9 rootwait console=ttyS0,115200 +``` + +文件中的UUID可以使用如下的指令获得: + +```bash +findmnt mnt -o UUID -n +``` + +其中需要说明的是,文件中指定kernel和intird的时候使用的是`/`而不是`/boot`,这是因为虽然现在把该分区挂载到了`/boot`目录下,但是在U-boot进行启动时会将该分区挂载在`/`目录下,因此需要使用`/`。也是因为同样的原因,当只格式化为一个分区并只使用U-boot进行引导启动时,则需要将目录改为`/boot`。 + +此时即可取消挂载镜像了: + +```bash +sudo umount mnt/boot +sudo umount mnt +sudo qemu-nbd -d /dev/nbd0 +``` + +使用如下的指令即可启动虚拟机: + +```bash +#!/bin/bash + +qemu-system-riscv64 \ + -nographic \ + -machine virt \ + -smp 8 \ + -m 4G \ + -bios opensbi/build/platform/generic/firmware/fw_payload.bin \ + -device virtio-blk-device,drive=hd0 \ + -drive file=archriscv-1.img,format=qcow2,id=hd0,if=none \ + -object rng-random,filename=/dev/urandom,id=rng0 \ + -device virtio-rng-device,rng=rng0 \ + -monitor unix:/tmp/qemu-monitor,server,nowait +``` + +### 使用UEFI启动 + +使用UEFI启动,就需要编译对应的UEFI固件,即开源固件EDK2。 + +```bash +git clone -b edk2-stable202505 --recursive-submodule https://github.com/tianocore/edk2.git +export WORKSPACE=`pwd` +export GCC5_RISCV64_PREFIX=riscv64-linux-gnu- +export PACKAGES_PATH=$WORKSPACE/edk2 +export EDK_TOOLS_PATH=$WORKSPACE/edk2/BaseTools +source edk2/edksetup.sh --reconfig +make -C edk2/BaseTools -j18 +source edk2/edksetup.sh BaseTools +build -a RISCV64 --buildtarget RELEASE -p OvmfPkg/RiscVVirt/RiscVVirtQemu.dsc -t GCC5 +``` + +编译之后得到的两份固件应该在`Build/RiscVVirtQemu/RELEASE_GCC5/FV`目录下: + +- `RISCV_VIRT_CODE.fd`固件的代码部分。 +- `RISCV_VIRT_VARS.fd`固件的数据部分,可以被UEFI工具修改。 + +在启动之前首先将这两个文件填充到32M的大小以符合QEMU对于`pflash`文件的大小要求: + +```bash +truncate -s 32M Build/RiscVVirtQemu/RELEASE_GCC5/FV/RISCV_VIRT_CODE.fd +truncate -s 32M Build/RiscVVirtQemu/RELEASE_GCC5/FV/RISCV_VIRT_VARS.fd +``` + +然后就可以使用如下的指令启动QEMU虚拟机了,这里复用U-boot中编译的OpenSBI固件,如果没有执行这一步可以选择删除下面指令中的`-bios`选项,使用QEMU自带的OpenSBI实现。 + +```bash +#!/bin/bash + +qemu-system-riscv64 \ + -M virt,pflash0=pflash0,pflash1=pflash1,acpi=off \ + -m 4096 -smp 8 -nographic \ + -bios opensbi/build/platform/generic/firmware/fw_dynamic.bin \ + -blockdev node-name=pflash0,driver=file,read-only=on,filename=Build/RiscVVirtQemu/RELEASE_GCC5/FV/RISCV_VIRT_CODE.fd \ + -blockdev node-name=pflash1,driver=file,filename=Build/RiscVVirtQemu/RELEASE_GCC5/FV/RISCV_VIRT_VARS.fd \ + -device virtio-blk-device,drive=hd0 \ + -drive file=archriscv-1.img,format=qcow2,id=hd0,if=none \ + -netdev user,id=n0 -device virtio-net,netdev=n0 \ + -monitor unix:/tmp/qemu-monitor,server,nowait +``` + +但是,这一步启动并不会进入Linux内核,这是因为还没有向UEFI注册需要启动的系统,使得UEFI可以识别到可以执行启动的磁盘。在普通的系统安装上,由于是使用安装镜像直接从UEFI启动的,在`chroot`环境中可以直接使用`grub-install`直接安装,但是在目前的`systemd-nspawn`环境中是缺少`efivarfs`等必要的文件系统的。 + +因此可以首先尝试在启动之后进入`UEFI Shell`之后,手动设置参数直接启动Linux内核。 + +![image-20250527134233659](./linux-distribution-from-zero/image-20250527134233659.webp) + +进入`UEFI Shell`之后,首先选择文件系统`FS0:`,然后使用如下的指令尝试手动启动Linux内核: + +```bash +\vmlinuz-linux initrd=\initramfs-linux.img earlyprintk rw root=UUID=903944ec-a4d3-4820-ac89-c0eac37721f9 rootwait console=ttyS0,115200 +``` + +但是可能会遇到如下的问题: + +![image-20250527134421403](./linux-distribution-from-zero/image-20250527134421403.webp) + +这里也尝试了使用`mkinitcpio`生成的Unified Kernel Image,放在`EFI/Linux`文件目录下,同样遇到了如下的问题: + +![image-20250527134540583](./linux-distribution-from-zero/image-20250527134540583.webp) + +暂时不清楚这是EDK2的问题还是这里操作的问题,至少能确定这里编译内核时是启用了`CONFIG_EFI_STUB`选项的。 + +因此这里使用`grub`方式尝试绕过这个问题,首先在`systemd-nswpan`环境中使用如下的指令安装`grub`,虽然会因为环境问题报错,但是手动查看可以发现安装脚本已经将`grubriscv64.efi`文件复制到`/boot/EFI/GRUB`目录了。 + +此时再次进入`UEFI Shell`,手动指定启动`grub`,所幸这次启动成功,此时我们再从`grub shell`中尝试启动Linux,使用的指令如下: + +```bash +linux (hd0,gpt1)/vmlinuz-linux earlyprintk rw root=UUID=903944ec-a4d3-4820-ac89-c0eac37721f9 rootwait console=ttyS0,115200 +initrd (hd0,gpt1)/initramfs-linux.img +boot +``` + +![image-20250527135748547](./linux-distribution-from-zero/image-20250527135748547.webp) + +此时就可以正常的进入完成完整的安装过程了。 + +> 首次启动的时候推荐使用`fallback initramfs`,因为在`chroot`环境中生成的驱动可能不全。如果在使用主要的`initramfs`进行启动时遇到了无法挂载真`/`目录而进入`emergency shell`,同时在该Shell中也无法发现虚拟机的磁盘,就极有可能是系统缺少对应的驱动无法挂载。 +> +> 例如在`chroot`环境中生成的`initcpio`包含如下的模块: +> +> ![image-20250325160729310](./linux-distribution-from-zero/image-20250325160729310.webp) +> +> 而在进入系统之后,重新运行`mkinitcpio`之后包含的模块如下所示: +> +> ![image-20250325161310820](./linux-distribution-from-zero/image-20250325161310820.webp) + diff --git a/YaeBlog/source/posts/linux-distribution-from-zero/image-20250325160729310.webp b/YaeBlog/source/posts/linux-distribution-from-zero/image-20250325160729310.webp new file mode 100644 index 0000000..956531f --- /dev/null +++ b/YaeBlog/source/posts/linux-distribution-from-zero/image-20250325160729310.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:48ab3dd92f703da5ee676d072c69e393c6b7f2f169aacb52d626f1859b21d044 +size 19452 diff --git a/YaeBlog/source/posts/linux-distribution-from-zero/image-20250325161310820.webp b/YaeBlog/source/posts/linux-distribution-from-zero/image-20250325161310820.webp new file mode 100644 index 0000000..681ccc1 --- /dev/null +++ b/YaeBlog/source/posts/linux-distribution-from-zero/image-20250325161310820.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:903133fdccb46ef5b6c42dd30d0a0be92f0b7e93007ec3500a7327a1bda7a3de +size 16988 diff --git a/YaeBlog/source/posts/linux-distribution-from-zero/image-20250527134233659.webp b/YaeBlog/source/posts/linux-distribution-from-zero/image-20250527134233659.webp new file mode 100644 index 0000000..7187f8c --- /dev/null +++ b/YaeBlog/source/posts/linux-distribution-from-zero/image-20250527134233659.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dbddead6d887e378caa31becd335e040b3b0bddf9ad90cb18d48e0b2fbf2e045 +size 21630 diff --git a/YaeBlog/source/posts/linux-distribution-from-zero/image-20250527134421403.webp b/YaeBlog/source/posts/linux-distribution-from-zero/image-20250527134421403.webp new file mode 100644 index 0000000..f582de5 --- /dev/null +++ b/YaeBlog/source/posts/linux-distribution-from-zero/image-20250527134421403.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c76d0f31d20a60aa1945fefcf7e557cb5c44c0575557916cfca586ebe34bc665 +size 19944 diff --git a/YaeBlog/source/posts/linux-distribution-from-zero/image-20250527134540583.webp b/YaeBlog/source/posts/linux-distribution-from-zero/image-20250527134540583.webp new file mode 100644 index 0000000..6377a90 --- /dev/null +++ b/YaeBlog/source/posts/linux-distribution-from-zero/image-20250527134540583.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:10a621726031480fca3d44e9a7c24f9e645bdb99c955c009c433b76626269104 +size 21798 diff --git a/YaeBlog/source/posts/linux-distribution-from-zero/image-20250527135748547.webp b/YaeBlog/source/posts/linux-distribution-from-zero/image-20250527135748547.webp new file mode 100644 index 0000000..738dcfb --- /dev/null +++ b/YaeBlog/source/posts/linux-distribution-from-zero/image-20250527135748547.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:705f545ea119e2559aabab0b25061bd2cc62c92169824561df7212a36f847fc7 +size 10152