目录
1 文档介绍
本文档的主要内容为基于Licheepi 4a开发板,描述一种从“零”开始构建自己的操作系统并烧录到开发板上使用的流程与方法。
什么是Licheepi 4a开发板?
LicheePi 4A 是基于 Lichee Module 4A 核心板的 高性能 RISC-V Linux 开发板,以 TH1520 为主控核心(4xC910@1.85G, RV64GCV,4TOPS@int8 NPU, 50GFLOP GPU)。LicheePi 4A 可以用作典型的 RISC-V 验证平台,其强大的性能可以较快速地实现本地编译,而无需使用 QEMU 进行编译。(更多详情请参考官网资料:https://wiki.sipeed.com/hardware/zh/lichee/th1520/lpi4a/1_intro.html)
2 系统镜像的构建
2.1 构建环境搭建
为了让自己构建的操作系统可以在开发板上运行,自构建操作系统的表现形式应该为一组可烧录到开发板的镜像文件。根据本文档所选择的硬件环境以及烧录工具,烧录镜像集主要包括以下三类:
- U-Boot镜像:包括了U-Boot引导加载程序,烧录到对应的存储分区;
- Boot镜像:主要包含了Linux内核镜像、设备树文件、临时根文件系统;
- rootfs镜像:主要包含了用户空间根文件系统内容;
- 构建环境搭建
我所用的构建整个OS镜像所在的环境为x86架构下的Ubuntu24.04的虚拟机环境。在进行真正的构建流程前,需要先在此构建环境下安装一些必要的软件来支持构建工作,大部分软件通过如下命令就可安装:
| apt updateapt install parted zstd make bison bc flex kpartx \ xz-utils qemu-user-static libssl-dev \ gcc-riscv64-linux-gnu dnf rpm |
|---|
2.2 内核相关镜像构建
本章节的主要内容为从内核源码编译构建出内核相关文件与镜像的操作说明,因内核负责管理硬件资源并提供对底层硬件的抽象,所以它是对CPU架构最敏感的软件之一,内核特定架构的适配是整个操作系统适配的最重要事项。
从内核源码输出的相关文件主要有:
- 内核镜像(Kernel Image)
- DTB文件(Device Tree Blob)
- 内核模块(module)

2.2.1 kernel镜像
Linux内核是操作系统的核心部分,它负责管理硬件资源并提供对底层硬件的抽象层,使得应用程序可以无需关心具体的硬件细节就能运行。
内核镜像(Kernel Image)指的是编译后的Linux内核输出文件,通常是一个可以直接加载到计算机内存并执行的二进制文件。
基于RISC-V开发板的内核镜像的构建流程如下:
- 内核配置文件
在当前的开发板系统上,内核配置文件保存在/proc/config.gz中。把它解压提取出来,即可用作编译内核时的编译配置文件。 - 内核源码
此开发板芯片的生产商(THEAD)提供了主机的基于此c910芯片的OS系统(revyos)并把相关库开源,其中就包括了内核源码库(https:// http://github.com/revyos/th1520-linux-kernel/)。选择其中的”6.6.73”分支作为内核源码。 - 内核编译
把上文中所述的配置文件放入到内核源码目录,并重命名为”.config”(更规范的做法应该是把此配置文件放入arch/riscv/configs目录下,并重命名为一个唯一性名称,然后通过make命令选择该配置文件)。然后执行make编译命令,示范命令如下:
| make ARCH=riscv CROSS_COMPILE=riscv64-linux-gnu- -j$(nproc) |
|---|
make编译时会出现配置手动设置提示,一路回车即可。最后编译出的镜像在arch/riscv/boot/Image。

2.2.2 DTB镜像
- DTB文件(Device Tree Blob):在嵌入式系统中,尤其是那些支持多种硬件配置的系统中,设备树(Device Tree)是一种描述硬件的数据结构。DTB文件是编译后的设备树,它告诉内核有关连接到系统的硬件的信息,而不需要为每种硬件配置单独编译内核。这样做的好处是可以简化内核的编译过程,并且可以在不重新编译内核的情况下更新硬件信息。

DTB构建流程:
但按照上述的步骤编译了内核时,同时也会生成所有默认启用的dtb文件,其位于arch/riscv/boot/dts/目录下:
![]()
整个dts目录可以直接拷贝到Boot镜像下,对此之后讲到Boot分区镜像时会再详述。
现阶段,可以发现dts目录下包含了多个芯片厂商的目录,因为我们使用的开发板为Licheepi 4a,它是基于C910芯片,来自THEAD公司,所以我们真正需要的dtb文件在thead目录下:

其中的” th1520-lichee-pi-4a-16g.dtb”就是真正适配我们开发板的dtb文件。
2.2.3 内核模块
内核模块:是可以在运行时动态加载到内核中的代码片段,用于扩展内核的功能。它们通常用于驱动程序或文件系统等场景,允许在不重启系统的情况下添加或移除某些功能。

依旧是在内核源码目录下,运行以下编译命令来生成内核模块:
| make ARCH=riscv CROSS_COMPILE=riscv64-linux-gnu- modules_install INSTALL_MOD_PATH=kmod |
|---|
编译完成后,内核模块位置在kmod/lib/modules/*下。
2.2.4 intramfs镜像构建
initramfs(initial RAM file system)是Linux启动过程中的一个临时根文件系统,它存在于RAM中,主要用于在实际的根文件系统可用之前加载必要的驱动程序和工具,以完成内核启动后续所需的步骤。

在当前开发板系统环境下构建initrd,在根目录下执行以下命令:
| find . | cpio -H newc -o | gzip > ./initrd-6.6.73.img |
|---|
2.3 OpenSBI固件构建
OpenSBI(开源 Supervisor Binary Interface)是一个为RISC-V架构设计的开源固件,旨在提供一个标准化的接口,以便在硬件平台和操作系统之间进行交互。具体来说,OpenSBI实现了RISC-V SBI(Supervisor Binary Interface)规范,该规范定义了运行在机器模式(Machine Mode)下的固件与运行在监督者模式(Supervisor Mode)下的操作系统内核之间的交互接口。
使用的源码库为:http://github.com/revyos/opensbi的”th1520-v1.6”branch
编译命令:
| make PLATFORM=generic FW_PIC=y CROSS_COMPILE=riscv64-buildroot-linux-gnu- -j4 |
|---|
编译完成后,镜像在build/platform/generic/firmware/fw_dynamic.bin。
2.4 U-Boot固件构建
U-Boot(Universal Boot Loader)是一个广泛使用的开源引导加载程序,适用于嵌入式系统。它支持多种架构,包括RISC-V等。U-Boot的主要任务是初始化硬件,并为操作系统提供一个合适的环境以进行加载和执行。
使用的开源仓库为:http://github.com/revyos/thead-u-boot的”th1520”分支。
修改编译配置文件configs/light_lpi4a_16g_defconfig:

使其与内核编译时生成的dtb文件名相匹配。
之后的编译命令为:
| make ARCH=riscv CROSS_COMPILE=riscv64-buildroot-linux-gnu- light_lpi4a_16g_defconfigmake ARCH=riscv CROSS_COMPILE=riscv64-buildroot-linux-gnu- -j4 |
|---|
编译生成的镜像即存放在源码根目录下的”u-boot-with-spl.bin“。
2.5 rootfs根文件系统构建
rootfs(根文件系统)是Linux系统启动时挂载的第一个文件系统,它构成了Linux系统的根目录(/)。根文件系统包含了操作系统运行所需的基本文件和目录结构,包括但不限于:
- 基本工具和库:如shell、基础命令行工具以及动态链接库。
- 配置文件:例如网络配置、服务启动选项等,通常位于/etc目录下。
- 设备文件:在/dev目录下,代表系统中的硬件设备。
- 用户文件:用户的家目录通常位于/home下。
- 临时文件:在/tmp目录下,用于存放临时数据。
根文件系统对于Linux操作系统的正常运行至关重要,因为它不仅提供了操作系统内核与用户空间之间的桥梁,还支持用户空间程序的执行。此外,根文件系统还定义了系统的行为,比如如何启动服务、如何响应用户输入等。
2.5.1 简单构建方法:从现有可运行环境复制
如果开发板上已经有了可运行的系统,可以把其根目录下的内容当作构建镜像中的根文件系统。在开发板上通过以下命令复制出内容:
| rsync -aHAXq --exclude={"/dev/*","/proc/*","/sys/*","/tmp/*","/run/*","/mnt/*","/media/*","/lost+found"} /* ${target} |
|---|
在复制出的内容中,再把本文上述生成的内核模块目录添加到${target}/lib/modules/目录下。
使用dd命令构建rootfs镜像,格式化,并挂载到构建环境下,把上述复制出来的根文件系统内容拷贝到此挂载的镜像中。至此rootfs镜像构建完成。
2.5.2 从软件包开始构建
大体思路为:现在这个时间点,一些比较流行的Linux发行版都已经有了自己的RISC-V版本,比如Fedora:
| https://fedoraproject.org/wiki/Architectures/RISC-V |
|---|
比如Ubuntu:
| https://ubuntu.com/download/risc-v |
|---|
这些发行版也提供了专门的基于RISC-V架构的软件包镜像库,可以使用此库来添加根文件系统所需的基础组件,从而构建出最基础的根文系统镜像。
在本文档中,尝试使用国内龙蜥社区推出的Anolis23 RISC-V版本的软件包库源。
在构建环境中安装所需软件包:
构建rootfs需要额外的软件支持,安装这些软件包的命令如下:
| apt install parted zstd make bison bc flex kpartx \ xz-utils qemu-user-static libssl-dev \ gcc-riscv64-linux-gnu dnf rpm -y |
|---|
创建根文件系统所在目录,并初始化RPM数据库:
先在构建环境下创建一个rootfs目录,里边用于填充根文件系统所需结构与内容:
| mkdir rootfs |
|---|
接着在其中创建rpm初始化所需目录结构:
| mkdir -p rootfs/var/lib/rpm |
|---|
在其中初始化rpm数据库:
| rpm --root ${rootfs绝对路劲} --initdb |
|---|
安装release、repos软件包;
使用rpm工具在rootfs中安装相关OS(在此即Anolis23)的release、repos这两个RPM包:
| rpm -ivh --nodeps --root ${rootfs绝对路径} ${anolis_release} --ignorearchrpm -ivh --nodeps --root ${rootfs绝对路径} ${anolis_repos} --ignorearch |
|---|
${anolis_release}与${anolis_repos}均为在软件包库中对应rpm的URL。
安装dnf、bash,来构建填充基础的rootfs中所需内容。
当安装了release\repos rpm包之后,已经可以通过dnf向rootfs中安装dnf、bash软件,通过安装它们所需的依赖,可以构建并安装上根文件系统最小需求目录结构和所需内容:
| dnf --installroot=${rootfs绝对路径} install dnf bash --nogpgcheck --forcearch riscv64 |
|---|
并重新安装(reinstall)system-release软件包:
| dnf --installroot=${rootfs绝对路径} reinstall system-release --nogpgcheck --forcearch riscv64 |
|---|

为根文件系统添加dns解析配置;
运行以下命令复制构建环境使用的dnf解析配置:
| cp -b /etc/resolv.conf ${rootfs绝对路径}/etc/resolv.conf |
|---|
为rootfs挂载必要的外部环境支持
因为之后的操作要使用chroot,使rootfs模拟一个可运行的riscv环境,故需要进行以下挂载操作:
| mount --bind /dev ${ rootfs绝对路径}/devmount -t proc /proc ${ rootfs绝对路径}/procmount -t sysfs /sys ${ rootfs绝对路径}/sys |
|---|
使用chroot安装额外软件
现节点,rootfs中已经安装了dnf工具,故通过chroot来使用rootfs的dnf完成剩下所需软件的安装,相关命令如下:
| chroot ${ rootfs绝对路径} dnf makecachechroot ${ rootfs绝对路径} dnf updatechroot ${ rootfs绝对路径} dnf install passwd hostname |
|---|
我这里安装的软件包只满足最小需求,如果想在构建阶段就加入更多功能,请在以上命令基础上自行添加要安装的软件包名称。
使用chroot进行基础配置
结合chroot和rootfs中的bash工具,可以进入rootfs的运行环境,执行一些根文件系统的配置操作:
| cat << EOF | chroot ${ rootfs绝对路径} /bin/bash echo 'riscv' | passwd --stdin root dracut --no-kernel /boot/initrd.imgEOF |
|---|
上述命令有两个作用:
- 为root用户添加密码并启用;
- 为此rootfs生成临时根文件系统。
注意:当在此生成了临时根文件系统后,之后合成Boot分区时所需的临时根文件系统镜像就使用此处生产的,而不再需要上述“内核相关镜像构建“章节中所述的方法。

挂载目录的卸载
此时rootfs中的内容已可满足作为一个基础根文件系统的要求,在把它拷贝到镜像中之前,先卸载之前挂载的目录:
| umount -l ${ rootfs绝对路径}/devumount -l ${ rootfs绝对路径}/procumount -l ${ rootfs绝对路径}/sys |
|---|
创建镜像并放入内容
首先需要知道上述构建的rootfs目录有多大,运行以下命令得到:
| size=`du -sh --block-size=1MiB ${ rootfs绝对路径} | cut -f 1 | xargs` |
|---|
扩展一些空间:
| size=$(($size+720)) |
|---|
使用dd命令创建镜像,用于承载rootfs内容和烧录,空间大小使用上述决定:
| dd if=/dev/zero of=${镜像名称与路径} bs=1MiB count=$size status=progress && sync |
|---|
为该镜像初始化文件系统
| mkfs.ext4 ${镜像名称与路径} |
|---|
镜像本身已准备完毕,接下来放入rootfs的内容,先找个临时挂载点挂载上该镜像:
| mount ${镜像名称与路径} ${临时挂载点} |
|---|
接着拷贝rootfs中的内容放入镜像:
| rsync -avHAXq ${ rootfs绝对路径}/* ${临时挂载点}syncsleep 10 |
|---|
此刻,镜像里已经有了所需内容,最后卸载挂载点即可:
| umount ${临时挂载点} |
|---|
2.6 Boot分区镜像构建
Boot分区镜像中要包含的内容有:
- 内核镜像:
把本文所述的编译出的内核镜像Image拷贝到此分区的根目录下。 - 临时根文件系统
把本文之前所述的生成的initrd.img拷贝到次分区的根目录下。 - extlinux配置文件
extlinux.conf文件需放在boot分区中的extlinux目录下。该配置文件内容格式如下所示:

其中:
- linux:指定内核镜像位置;
- initrd:指定临时根文件系统镜像位置;
- fdtfir:指定dtb设备树文件所在目录;
- append:指定传递给内核的启动参数。
设备树文件
uboot环境下的要加载的设备树文件的位置,由uboot环境变量(fdtfile)和extlinux.conf中的字段(fdtdir)共同决定(${fdtdir}/${fdtfile})。故需要根据上述两个值来调整存放设备树文件的位置。
其他硬件相关固件
当boot分区的内容准备完毕后,使用dd命令创建boot镜像,并把这些内容放入其中。至此,boot镜像的创建已完成。
2.7 待烧录镜像备齐

3 烧录与启动
3.1 开发板以及重要接口说明
执行烧录之前,让我们先观察一下实际的开发板。这款Lichee Pi 4a RISC-V开发板的正面如下图所示:

说明一下图示中标记的重要接口:
USB2.0接口,用于烧录,和供电。
20Pin GPIO接口,对我们来说主要用其中的UART串口进行系统连接。
USB转串口模块,可以让板子通过UART串口连接只有USB接口的主机。
开发板的背面则下图所示:

说明一下图示中的标记:
与正面20Pin GPIO相对应的各pin口标记。
3.2 串口连接说明
当网络还不可用时,主机通过UART串口访问开发板。在开发板一端,USB转串口模块连接特定的GPIO串口,即U0-RX和U0-TX,注意交叉连接,以及GND连接,如下图所示:

图中标记即为U0-RX、U0-TX以及GND串口具体位置以及说明位置。
相对应地,转换模块USB端的接口连接就会如下图所示:

从图中可以看出,也是对应连接着U0-RX、U0-TX以及GND端点。
3.3 启动模式设置
因为是把镜像烧录到开发板的emmc中,并在此启动,故首先确保底板的拨码开关为EMMC启动模式,如下图所示:

3.4 烧录工具的驱动安装
我们使用的烧录工具为fastboot,且在windows环境下操作,首先需要打入fastboot驱动,驱动安装的具体步骤可以直接参考开发板官网教程:
| https://wiki.sipeed.com/hardware/zh/lichee/th1520/lpi4a/4_burn_image.html |
|---|
3.5 与烧录主机连接
再次让开发板与烧录主机进行连接:
开发板上使用USB2.0 TypeC接口准备与主机USB端口连接。
连接之前按住开发板上的BOOT键

USB先另一端再与主机进行连接。

当烧录主机驱动正常安装的情况下,开发板以烧录模式连接到主机时,主机的设备管理器中会出现”Android Bootloader Interface”设备标识,如下图所示:

烧录主机准备镜像与烧录脚本
在烧录主机上,我们已经准备好了构建出来的镜像文件:
- uboot镜像文件;
- boot镜像文件,以及
- rootfs根文件系统镜像。

fastboot的用法思路主要如下所示:
烧录环境连接开发板,在终端输入下面的命令,程序应当输出一行信息代表有一个设备已经连接。
| .\fastboot.exe devices |
|---|
输入下面的命令以刷写并重启到临时uboot:
| .\fastboot.exe flash ram .\u-boot-with-spl-lpi4a-16g.bin # 替换为您的板卡规格对应的 uboot 文件的路径.\fastboot reboot |
|---|
最后,把uboot、boot、root文件通过以下命令烧录进入:
| .\fastboot.exe flash uboot u-boot-with-spl-lpi4a-16g.bin.\fastboot.exe flash boot boot.ext4.\fastboot.exe flash root root.ext4 |
|---|
具体的扫描脚本(.bat格式)如下:

3.6 执行烧录
运行此脚本来完成烧录,执行结果如下:

从图中也可以清楚了解到具体的烧录流程:
- 烧录u-boot镜像到”ram”分区;
- 重启;
- 烧录u-boot镜像到”uboot”分区;
- 烧录boot镜像到”boot”分区;
- 烧录root镜像到”root”分区。
至此,镜像已经成功烧录到开发板中,可以启动来检验成果了。
3.7 启动测试
3.7.1 主机与开发板的连接
就像前面提到的,我们使用开发板的UART串口与主机通信。当串口以及转换模块准备好之后,转换模块的USB端连接到主机上,此时USB2.0 TYPEC端口用来上电。

主机使用串口工具进行通信,Windows下推荐Xshell,mobaterm等。设置串口的波特率为”115200”,即可在串口终端下访问开发板。
在WindTerm终端工具中,具体的会话设置如下图所示:

3.7.2 上电启动:U-Boot-SPL
当会话设置完毕,开发板开始上电时,会话窗口开始能访问到启动信息。
刚上电时的启动日志如下:

可以发现上电之后首先启动的就是U-Boot SPL,SPL启动完成后会紧接着引导启动U-Boot。
3.7.3 U-Boot启动引导
U-Boot的启动如下图所示:

U-Boot开始识别开发板的硬件信息以及读取U-Boot的环境变量和其他初始化事务。最后,它会提示用户是否进入U-Boot环境,如果不进入就直接继续启动Kernel。
3.7.4 U-Boot 环境
如果进入U-Boot环境,它会提供一个类shell界面,并可使用提供的工具进行一些U-Boot阶段的操作,这里使用printenv命令来打印U-Boot的环境变量:

3.7.5 SBI与Kernel启动
之后,U-Boot引导启动Kernel,启动信息如下图所示:

从启动信息中可以发现:U-Boot会启动OpenSBI,并由它为Kernel提供SBI接口。
3.7.6 挂载initramfs临时根文件系统
Kernel启动后,根据我们的启动配置(extlinux),U-Boot会先挂载Initramfs作为Kernel的临时根文件系统,这在启动信息中也有体现:

3.7.7 挂载真正的根文件系统
当Kernel挂载临时根文件系统完成了一些必要的驱动初始化等初步操作之后,就会挂载真正的根文件系统rootfs,就如启动信息中所示:

3.7.8 登录
最后,登录相关服务完成初始化,可以登录,如下图所示:

3.7.9 整体启动流程总结
通过上文的追踪描述,可以发现实际的OS启动流程可以用下图总结:

1176



被折叠的 条评论
为什么被折叠?



