尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

从零手动构建RISC-V嵌入式Linux:QEMU实战与工具链深度解析

从零手动构建RISC-V嵌入式Linux:QEMU实战与工具链深度解析 1. 项目缘起与目标定位几年前当我第一次尝试将Linux系统移植到一块全新的RISC-V开发板上时那段经历至今记忆犹新。从交叉编译工具链的版本冲突到内核启动参数的反复调试再到根文件系统里一个个缺失的动态库整个过程就像在黑暗中摸索拼图每一步都可能踩坑。市面上关于嵌入式Linux的教程不少但要么是基于成熟的ARM平台步骤简化到近乎“一键完成”掩盖了底层细节要么是过于理论化和实际动手操作之间隔着一道鸿沟。对于RISC-V这个正在蓬勃发展的开源指令集架构一份从零开始、手把手、不回避任何“脏活累活”的实战指南显得尤为稀缺。这就是我启动“基于qemu-riscv从0开始构建嵌入式linux系统”这个系列专栏的初衷。我的目标非常明确不使用任何现成的、封装好的构建系统或发行版工具如Buildroot、Yocto而是像二十年前的先驱们那样亲手组合每一个部件。我们将从最底层的模拟器环境搭建开始一步步编译出专属的交叉工具链配置、编译Linux内核制作最小的根文件系统最终在QEMU模拟的RISC-V虚拟硬件上引导出一个完全由自己掌控的、极简的Linux系统。这个过程你会清晰地看到bootloader如何把控制权交给内核内核又如何挂载根文件系统并启动第一个用户态进程。这不仅仅是完成一个“Hello World”式的演示而是深入理解嵌入式Linux系统骨架的绝佳机会。这个系列适合谁如果你是一名对操作系统原理感兴趣的学生希望将书本上的概念落到实处如果你是一名嵌入式软件工程师习惯了在SDK基础上开发想揭开底层的神秘面纱或者你是一位RISC-V技术的爱好者想找一个有挑战性的动手项目——那么这个系列就是为你准备的。我们不需要昂贵的物理开发板一台普通的电脑和足够的耐心就是全部所需。最终你获得的将不仅仅是一个能跑起来的系统镜像更是一套能够举一反三、应对各种定制化需求的底层构建能力。2. 核心工具链与平台选型解析工欲善其事必先利其器。在开始漫长的构建之旅前我们必须对核心工具做出明确的选择并理解其背后的原因。这直接决定了后续所有步骤的顺畅程度。2.1 为什么是QEMU与RISC-VQEMU是我们的核心实验平台。它是一个功能强大且开源的全系统模拟器。对于本项目而言选择QEMU而非物理开发板有三大不可替代的优势零成本与高可复现性无需购买特定的RISC-V硬件任何x86或ARM架构的宿主机都能运行。实验环境可以轻易保存、复制和分享确保教程中的每一步结果对所有读者都是一致的。极佳的调试支持QEMU提供了GDB调试接口我们可以单步跟踪内核的启动代码观察寄存器和内存的变化。这种能力在物理板上往往需要昂贵的硬件调试器如JTAG才能实现。灵活的硬件模拟我们可以为虚拟机配置特定的CPU核心数、内存大小、外设类型如UART、virtio磁盘、网络设备这有助于我们理解硬件描述Device Tree如何与驱动交互。RISC-V是我们构建的目标架构。选择它不仅仅因为其开源和开放的特性更因为它在嵌入式与学术领域的独特地位。与ARM架构相比RISC-V的指令集手册完全公开没有授权壁垒。这意味着我们在学习过程中可以毫无阻碍地查阅指令集细节甚至去阅读相关工具链如GCC的源码来理解ABI应用程序二进制接口。从最简单的rv32ima到支持多核与虚拟化的rv64gc我们可以从基础子集开始逐步深入。本次构建我们将选择主流的rv64gc作为目标即64位支持整数(I)、乘法(M)、原子操作(A)、单精度浮点(F)、双精度浮点(D)以及压缩指令(C)扩展这是一个功能相对完整的通用配置。2.2 交叉编译工具链自制还是使用预编译这是第一个关键决策点。交叉编译工具链Cross Compilation Toolchain是在宿主机比如你的x86_64电脑上运行但生成目标机RISC-V可执行代码的一套程序主要包括编译器gcc、链接器ld、库glibc等和二进制工具objdump, strip等。方案一使用预编译工具链。例如SiFive或Bootlin提供的预构建版本。优点是快下载解压即可用。缺点是版本固定可能不包含你需要的特定功能或库且无法定制C库比如你想用musl libc替代glibc以追求更小的体积。方案二使用Crosstool-NG等工具自动化构建。这是一个折中方案它通过配置菜单引导你选择架构、C库、GCC版本等然后自动下载源码并编译。它比方案一更灵活但抽象掉了很多构建细节。方案三完全手动构建。这正是本系列要走的“硬核”路线。我们将从GNU官网下载binutils、gcc、glibc等组件的源码按照严格的依赖顺序和配置参数手动执行configure,make,make install。这个过程极其繁琐极易出错但价值巨大。你会彻底明白工具链各组件间的依赖关系例如编译gcc需要目标系统的头文件而头文件来自glibc但编译glibc又需要目标系统的编译器这就产生了“鸡生蛋蛋生鸡”的循环依赖需要分阶段构建理解--target、--prefix、--with-sysroot这些关键参数的意义并拥有从源码构建任何架构工具链的能力。实操心得对于第一次尝试的读者我强烈建议在跟随本系列手动构建一次之后为日常开发保存一份预编译或Crosstool-NG生成的工具链。手动构建是“修炼内功”而高效的日常工具是“提高生产力”两者并不矛盾。2.3 宿主操作系统与基础环境准备宿主机操作系统推荐使用Linux发行版如Ubuntu 22.04 LTS或Fedora。在Windows或macOS上可以通过WSL2或虚拟机安装一个Linux环境。选择Linux宿主的根本原因在于我们构建的整个工具链和流程其脚本、命令和环境都与Linux生态无缝契合可以避免在Windows上因路径、符号链接、脚本解释器如bash差异带来的无数隐性问题。在开始之前我们需要安装一些基础的开发包。以下以Ubuntu/Debian为例sudo apt update sudo apt install build-essential git wget flex bison libncurses-dev libssl-dev \ libelf-dev python3-dev texinfo automake autoconf pkg-config \ gperf libtool ninja-build meson这些包涵盖了从源码编译所需的编译器、库、配置工具和文档生成器。build-essential是基础编译套件flex和bison用于语法分析libncurses-dev是make menuconfig界面所必需的libssl-dev和libelf-dev是编译新版内核和某些工具所必需的。3. 项目整体构建路线图构建一个完整的、可启动的嵌入式Linux系统其核心组件和流程是一个经典的依赖链条。下图清晰地展示了从零到一的完整路径以及各关键组件之间的依赖关系flowchart TD A[宿主机环境准备br安装基础编译工具] -- B[手动构建RISC-V交叉编译工具链] subgraph B [工具链构建解决循环依赖] B1[1. 编译Binutils] -- B2[2. 编译GCC第一阶段br仅编译器无C库] B2 -- B3[3. 编译目标系统Linux内核头文件] B3 -- B4[4. 编译Glibc C库] B4 -- B5[5. 编译完整的GCCbr含编译器与标准库] end B -- C[编译BusyBoxbr提供用户态核心命令] B -- D[编译Linux内核br生成内核镜像vmlinux与设备树] C -- E[制作初始内存盘initramfs] D -- F[制作根文件系统rootfsbr整合BusyBox与运行时库] E -- G[整合最终系统镜像br内核initramfs或内核磁盘rootfs] F -- G G -- H[使用QEMU加载系统镜像br完成从零启动]这个流程图是本系列的行动总纲。它揭示了一个关键挑战工具链的构建本身就是一个“先有鸡还是先有蛋”的循环依赖问题。我们需要一个编译器来编译C库但C库的头文件又是编译器运行时所必需的。因此我们必须采用分阶段构建的策略如图中步骤B1至B5所示。这是整个项目第一个也是最重要的技术难点。后续所有步骤包括BusyBox、内核的编译都依赖于这套我们亲手打造的、可靠的交叉工具链。整个构建过程可以概括为四个主要阶段基础工具链打造解决循环依赖生成riscv64-unknown-linux-gnu-为前缀的系列工具。内核与核心用户态编译用自制的工具链编译Linux内核和BusyBox。系统镜像组装创建根文件系统将内核、BusyBox、必要的配置文件和库文件整合到一起形成可启动的镜像。启动与调试配置QEMU加载镜像观察启动日志使用GDB进行内核调试。每一步我们都将深入细节解释每个配置选项的作用并记录可能遇到的错误及其解决方法。4. 第一阶段实操手动构建RISC-V交叉工具链现在让我们开始最富挑战性也最有价值的一步手动构建交叉工具链。我们将按照binutils-gcc (bootstrap)-linux headers-glibc-gcc (full)的顺序进行。4.1 准备工作目录与源码下载首先创建一个清晰的工作目录结构这有助于管理源码、构建文件和安装路径。export TOPDIR$(pwd) export RISCV$TOPDIR/toolchain-install # 工具链安装路径 export PATH$RISCV/bin:$PATH mkdir -p $TOPDIR/src $TOPDIR/build $RISCV cd $TOPDIR/src我们将所有源码下载到src目录在build目录中进行编译保持源码目录纯净最终安装到toolchain-install目录。接下来下载所需版本的源码包。版本的选择需要兼顾兼容性这里给出一个经过验证的组合wget https://ftp.gnu.org/gnu/binutils/binutils-2.42.tar.xz wget https://ftp.gnu.org/gnu/gcc/gcc-13.2.0/gcc-13.2.0.tar.xz wget https://ftp.gnu.org/gnu/glibc/glibc-2.38.tar.xz wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.6.tar.xz wget https://ftp.gnu.org/gnu/gmp/gmp-6.3.0.tar.xz wget https://ftp.gnu.org/gnu/mpfr/mpfr-4.2.1.tar.xz https://ftp.gnu.org/gnu/mpc/mpc-1.3.1.tar.gzGCC编译依赖GMP、MPFR、MPC这三个数学库。我们可以将它们解压到GCC源码目录内GCC构建脚本会自动识别并使用它们。tar -xf gmp-6.3.0.tar.xz mv gmp-6.3.0 gcc-13.2.0/gmp tar -xf mpfr-4.2.1.tar.xz mv mpfr-4.2.1 gcc-13.2.0/mpfr tar -xf mpc-1.3.1.tar.gz mv mpc-1.3.1 gcc-13.2.0/mpc4.2 第一步编译BinutilsBinutils是二进制工具的集合包括汇编器as、链接器ld、静态库生成器ar等是编译其他一切的基础。cd $TOPDIR/build mkdir binutils cd binutils ../../src/binutils-2.42/configure \ --prefix$RISCV \ --targetriscv64-unknown-linux-gnu \ --with-sysroot$RISCV/sysroot \ --disable-multilib \ --disable-nls make -j$(nproc) make install关键参数解析--targetriscv64-unknown-linux-gnu定义目标系统三元组。格式为架构-厂商-系统-ABI。riscv64指64位RISC-Vunknown表示厂商未知或通用linux指目标系统gnu指使用GNU的C库glibc及ABI。--with-sysroot$RISCV/sysroot指定目标系统的根目录。工具链将在该目录下寻找目标系统的头文件和库。目前这个目录是空的我们后续会填充。--disable-multilib禁用多库支持。因为我们只构建rv64gc一种配置不需要同时生成32位库可以简化构建。--disable-nls禁用本地化支持可以加快编译速度并减少依赖。安装成功后$RISCV/bin目录下会出现riscv64-unknown-linux-gnu-as、riscv64-unknown-linux-gnu-ld等工具。可以执行riscv64-unknown-linux-gnu-as --version验证。4.3 第二步编译GCC第一阶段Bootstrap Compiler此时我们还没有目标系统的C库glibc因此只能先编译一个“裸”的GCC它支持C语言编译但无法链接标准库因为库还不存在。这个编译器仅用于编译后续的glibc。cd $TOPDIR/build mkdir gcc-bootstrap cd gcc-bootstrap ../../src/gcc-13.2.0/configure \ --prefix$RISCV \ --targetriscv64-unknown-linux-gnu \ --with-sysroot$RISCV/sysroot \ --enable-languagesc \ --without-headers \ --disable-shared \ --disable-threads \ --disable-libatomic \ --disable-libgomp \ --disable-libquadmath \ --disable-libssp \ --disable-libvtv \ --disable-libstdcxx \ --disable-multilib \ --disable-nls make -j$(nproc) all-gcc make install-gcc关键参数解析--enable-languagesc只编译C语言前端足矣。--without-headers明确告诉GCC目标系统还没有C库头文件。这正是bootstrap阶段的核心标志。一系列--disable-*禁用所有依赖C库或线程等复杂运行时的组件确保这个初始编译器能独立构建出来。4.4 第三步安装Linux内核头文件C库glibc需要知道目标系统内核提供了哪些系统调用和数据结构定义这些信息通过内核头文件暴露。我们需要从Linux内核源码中提取出纯净的、与架构无关的头文件。cd $TOPDIR/src/linux-6.6 make ARCHriscv INSTALL_HDR_PATH$RISCV/sysroot/usr headers_install这个命令会将内核的通用头文件如asm-generic/和RISC-V架构特定的头文件asm/安装到$RISCV/sysroot/usr/include目录下。sysroot目录开始有内容了。4.5 第四步编译与安装Glibc现在我们有了bootstrap编译器、binutils和内核头文件终于可以编译完整的C库了。Glibc的构建需要在一个独立的目录中进行。cd $TOPDIR/build mkdir glibc cd glibc ../../src/glibc-2.38/configure \ --prefix/usr \ --hostriscv64-unknown-linux-gnu \ --with-headers$RISCV/sysroot/usr/include \ --disable-werror \ libc_cv_forced_unwindyes \ libc_cv_c_cleanupyes make -j$(nproc) make install DESTDIR$RISCV/sysroot关键参数解析--hostriscv64-unknown-linux-gnu指定运行编译结果的主机即目标机。配置脚本会根据这个值寻找我们之前安装的bootstrap交叉编译器。--prefix/usr指定glibc在目标根文件系统中的安装路径。注意这里不是$RISCV因为glibc是安装在目标系统的/usr目录下。--with-headers指定内核头文件的位置。DESTDIR$RISCV/sysroot这是make install时的参数而非configure的。它指定一个“临时根目录”所有文件将被安装到$RISCV/sysroot/usr下而不是真正的系统/usr。这是交叉编译中的标准做法。注意事项Glibc的编译是对工具链稳定性的第一次重大考验。你可能会遇到关于libc_cv_forced_unwind和libc_cv_c_cleanup的配置错误。这是因为bootstrap编译器功能不全无法运行某些测试程序。通过环境变量直接设置yes可以绕过这些测试。如果遇到其他测试失败需要根据具体错误信息在搜索引擎或社区寻找对应的解决方案这通常是手动构建过程中最耗时的部分。4.6 第五步编译完整的GCCGlibc就位后我们可以重新编译一个功能完整的GCC它支持C、C等语言并能链接到我们刚编译好的glibc。cd $TOPDIR/build mkdir gcc-full cd gcc-full ../../src/gcc-13.2.0/configure \ --prefix$RISCV \ --targetriscv64-unknown-linux-gnu \ --with-sysroot$RISCV/sysroot \ --enable-languagesc,c \ --disable-multilib \ --disable-nls make -j$(nproc) make install这次配置去掉了--without-headers等限制性选项。编译安装完成后我们的交叉工具链就彻底准备好了。使用riscv64-unknown-linux-gnu-gcc -v查看版本并使用一个简单的Hello World程序测试// hello.c #include stdio.h int main() { printf(Hello, RISC-V!\n); return 0; }编译并检查文件类型riscv64-unknown-linux-gnu-gcc hello.c -o hello.rv64 file hello.rv64 # 应输出hello.rv64: ELF 64-bit LSB executable, UCB RISC-V, ... for GNU/Linux ...如果看到RISC-V和dynamically linked的字样恭喜你最艰难的一关已经通过。5. 第二阶段实操编译Linux内核与BusyBox拥有了得心应手的工具链接下来的工作会顺畅许多。我们将编译系统的“大脑”内核和“手脚”用户态基础命令集。5.1 配置与编译Linux内核Linux内核的构建系统Kbuild非常成熟。我们主要使用make menuconfig进行图形化配置。cd $TOPDIR/src/linux-6.6 make ARCHriscv CROSS_COMPILEriscv64-unknown-linux-gnu- menuconfig在配置界面中我们需要关注几个核心设置General setup-Cross-compiler prefix确认已自动设置为$(CROSS_COMPILE)。Platform type-RISCV platform type选择(X) Virtio MMIO。QEMU的virt机器默认使用Virtio设备这是一个在虚拟化环境中高效、标准化的I/O框架。Kernel Features确保(X) 64-bit kernel被选中。Device Drivers-Character devices-Serial drivers确保(X) 8250/16550 and compatible serial support和(X) Support for console on 8250/16550 and compatible serial port被启用。这是QEMUvirt机器默认的串口是内核启动早期和用户交互的唯一通道。File systems- 为了支持后续的根文件系统需要启用(X) ext4等文件系统支持。对于初期的initramfs需要启用(X) Initial RAM filesystem and RAM disk (initramfs/initrd) support。配置完成后保存退出开始编译make ARCHriscv CROSS_COMPILEriscv64-unknown-linux-gnu- -j$(nproc)编译成功后主要生成两个文件arch/riscv/boot/Image压缩的内核镜像。arch/riscv/boot/dts/riscv/virt.dtb设备树二进制文件Device Tree Blob。它以一种硬件描述语言DTB的形式告诉内核QEMUvirt机器上有什么硬件CPU、内存、中断控制器、串口、磁盘等以及它们的地址。5.2 编译BusyBox用户态的瑞士军刀BusyBox将上百个常用的Unix命令如ls,cp,sh,mount集成进一个单一的可执行文件通过创建符号链接来调用不同的功能是嵌入式系统根文件系统的绝对核心。cd $TOPDIR/src wget https://busybox.net/downloads/busybox-1.36.1.tar.bz2 tar -xf busybox-1.36.1.tar.bz2 cd busybox-1.36.1配置BusyBox同样使用菜单界面make CROSS_COMPILEriscv64-unknown-linux-gnu- menuconfig关键配置Settings-Build Options-Build static binary (no shared libs)强烈建议勾选。这将BusyBox编译成静态链接不依赖动态库。这能确保它在任何环境下都能独立运行极大简化了根文件系统的制作。缺点是二进制文件体积会稍大。Settings-Installation Options-Destination path for ‘make install’可以设置为$TOPDIR/rootfs方便后续操作。配置完成后编译并安装make CROSS_COMPILEriscv64-unknown-linux-gnu- -j$(nproc) make CONFIG_PREFIX$TOPDIR/rootfs install安装后$TOPDIR/rootfs目录下会出现bin,sbin,usr/bin等目录里面是BusyBox二进制文件和指向它的各种符号链接如ls - busybox。6. 第三阶段实操制作根文件系统与整合镜像系统启动的最后一个环节是为内核提供一个包含初始程序和必要文件的“家园”——根文件系统。6.1 构建最小根文件系统我们首先创建一个基本的根文件系统目录结构并将BusyBox安装进去。cd $TOPDIR mkdir -p rootfs/{bin,sbin,etc,proc,sys,dev,lib,usr/{lib,bin,sbin},tmp} cp -a src/busybox-1.36.1/_install/* rootfs/由于我们编译的是静态版BusyBoxlib目录暂时是空的。但为了系统的完整性我们需要创建几个必要的设备节点在真实的Linux系统中/dev下的文件是特殊的设备文件。sudo mknod rootfs/dev/console c 5 1 sudo mknod rootfs/dev/null c 1 3console节点是系统控制台null是空设备。c表示字符设备后面的数字是主设备号和次设备号。接下来创建最简化的初始化脚本。内核启动后会尝试执行根文件系统中的/init或/sbin/init程序。我们用BusyBox提供的init并配置一个简单的inittab。# 创建初始化脚本 cat rootfs/init EOF #!/bin/sh mount -t proc none /proc mount -t sysfs none /sys mount -t devtmpfs none /dev echo -e \nWelcome to My RISC-V Linux!\n exec /bin/sh EOF chmod x rootfs/init # 创建inittab (BusyBox init会读取) mkdir -p rootfs/etc/init.d cat rootfs/etc/inittab EOF ::sysinit:/etc/init.d/rcS ::askfirst:-/bin/sh ::ctrlaltdel:/sbin/reboot ::shutdown:/sbin/swapoff -a ::shutdown:/bin/umount -a -r ::restart:/sbin/init EOF cat rootfs/etc/init.d/rcS EOF #!/bin/sh mount -a EOF chmod x rootfs/etc/init.d/rcS cat rootfs/etc/fstab EOF proc /proc proc defaults 0 0 sysfs /sys sysfs defaults 0 0 devtmpfs /dev devtmpfs defaults 0 0 EOF6.2 制作initramfs镜像Initramfs是一个被压缩的cpio归档文件内核在启动早期会将其解压到内存中作为一个临时的根文件系统。这是最简单快速的启动方式。cd $TOPDIR/rootfs find . | cpio -H newc -o | gzip -9 ../initramfs.cpio.gz现在我们拥有了启动所需的所有部件内核镜像Image、设备树virt.dtb和初始内存盘initramfs.cpio.gz。7. 第四阶段实操QEMU启动与问题排查激动人心的时刻到了让我们启动这个亲手打造的系统。7.1 基础启动命令与参数解析使用QEMU的virt机器进行模拟cd $TOPDIR qemu-system-riscv64 \ -machine virt \ -cpu rv64 \ -m 256M \ -kernel src/linux-6.6/arch/riscv/boot/Image \ -dtb src/linux-6.6/arch/riscv/boot/dts/riscv/virt.dtb \ -initrd initramfs.cpio.gz \ -append consolettyS0 earlyconsbi root/dev/ram rdinit/init \ -nographic \ -serial mon:stdio关键参数解析-machine virt指定模拟的机器类型为QEMU的通用RISC-V虚拟平台。-cpu rv64指定CPU为通用的64位RISC-V。-m 256M分配256MB内存。-kernel,-dtb,-initrd分别指定内核、设备树和initramfs镜像。-append传递给内核的命令行参数。consolettyS0指定控制台设备为第一个串口。earlyconsbi启用早期控制台输出通过SBI即RISC-V Supervisor Binary Interface。root/dev/ram告诉内核根文件系统在RAM磁盘上即我们的initramfs。rdinit/init指定initramfs中的初始化程序为/init。-nographic禁用图形界面完全使用命令行。-serial mon:stdio将虚拟串口重定向到标准输入输出这样我们就可以在终端里与虚拟机交互。如果一切顺利你将看到内核解压、启动、挂载文件系统最后打印出Welcome to My RISC-V Linux!并进入BusyBox的shell提示符。你可以运行ls,mount,ps等命令进行探索。7.2 常见启动问题与排查实录第一次启动很少能一帆风顺。以下是几个我踩过的坑及排查思路问题一内核在Starting kernel ...后卡住或无输出。排查这通常意味着内核崩溃在了非常早的阶段。首先检查串口配置。确保内核配置中CONFIG_SERIAL_8250和CONFIG_SERIAL_8250_CONSOLE已启用。其次检查QEMU命令行中的-append参数是否正确指定了consolettyS0。可以尝试添加更详细的早期打印参数如earlyprintk但新内核可能已移除。进阶调试使用QEMU的-S -s参数启动它会暂停CPU并开启一个GDB调试服务默认端口1234。在另一个终端用GDB连接riscv64-unknown-linux-gnu-gdb src/linux-6.6/vmlinux (gdb) target remote localhost:1234 (gdb) c然后观察卡在哪里。或者在内核命令行添加panic5让内核在panic时等待更久方便查看最后的信息。问题二内核提示VFS: Cannot open root device ...或Failed to execute /init。排查这是根文件系统相关的问题。首先确认-initrd指定的路径和文件名正确。其次检查initramfs.cpio.gz文件是否制作正确可以用gunzip -c initramfs.cpio.gz | cpio -t列出其中文件看/init是否存在且可执行。最后检查内核配置是否启用了CONFIG_BLK_DEV_INITRD和CONFIG_DEVTMPFS。问题三进入shell后命令无法执行或提示-sh: xxx: not found。排查如果你编译的是动态链接的BusyBox而根文件系统里缺少相应的动态库/lib/ld-linux-riscv64-*.so.1和/lib/libc.so.6等就会出现此问题。解决方案是1重新编译BusyBox为静态版本推荐。2或者将工具链sysroot里的相关库文件拷贝到根文件系统的/lib目录下。可以使用riscv64-unknown-linux-gnu-readelf -d busybox查看动态依赖。问题四系统启动后键盘输入无反应。排查QEMU在-nographic模式下为了退出虚拟机默认使用了CtrlA C等组合键。这有时会干扰输入。可以尝试在QEMU命令中添加-echr 2来改变转义字符。更简单的方法是要退出QEMU先按CtrlA松开后再按X。8. 从Initramfs到磁盘镜像系统的持久化基于initramfs的系统运行在内存中所有改动在关机后都会丢失。为了构建一个更接近真实场景的、可持久化的系统我们需要创建磁盘镜像并将根文件系统安装到其中。8.1 创建并格式化磁盘镜像使用dd和mkfs.ext4创建一个空的磁盘镜像文件并格式化为ext4文件系统。cd $TOPDIR dd if/dev/zero ofrootfs.img bs1M count64 mkfs.ext4 rootfs.img这个命令创建了一个64MB的镜像文件。你可以将其挂载到宿主机的一个目录以便向其中拷贝文件。mkdir -p mnt sudo mount -o loop rootfs.img mnt8.2 向磁盘镜像中部署根文件系统将我们之前制作的rootfs目录全部拷贝到挂载点并创建必要的设备节点。sudo cp -a rootfs/* mnt/ sudo mknod mnt/dev/console c 5 1 sudo mknod mnt/dev/null c 1 3 sudo umount mnt现在rootfs.img就是一个包含了完整根文件系统的ext4格式磁盘镜像。8.3 修改启动参数以从磁盘启动我们需要修改内核命令行参数告诉内核从磁盘的特定分区寻找根文件系统并指定根文件系统的类型。qemu-system-riscv64 \ -machine virt \ -cpu rv64 \ -m 256M \ -kernel src/linux-6.6/arch/riscv/boot/Image \ -dtb src/linux-6.6/arch/riscv/boot/dts/riscv/virt.dtb \ -drive filerootfs.img,formatraw,idhd0 \ -device virtio-blk-device,drivehd0 \ -append consolettyS0 earlyconsbi root/dev/vda rootfstypeext4 rw \ -nographic \ -serial mon:stdio关键变更-drive指定磁盘镜像文件。-device virtio-blk-device为虚拟机添加一个Virtio块设备并将其关联到上面的驱动器。在QEMUvirt机器中第一个Virtio块设备在系统内通常显示为/dev/vda。-appendroot/dev/vda指定根设备为第一个Virtio磁盘。rootfstypeext4指定文件系统类型。rw以读写模式挂载。启动后系统将从磁盘镜像加载根文件系统。你可以在其中创建文件关机后再启动文件依然存在。这为我们后续安装更多软件包、配置网络等操作奠定了基础。走到这一步你已经完成了一个从源码开始涵盖工具链、内核、根文件系统的完整嵌入式Linux构建循环。这个过程虽然曲折但每一个错误和解决过程都让你对系统各组件间的联系有了更深刻的理解。这不仅仅是构建了一个系统更是获得了一把理解复杂系统的钥匙。在后续的章节中我们可以在此基础上继续探索内核模块的编译、设备树的深入解析、网络功能的添加甚至移植一个轻量级的图形界面。
返回列表