嵌入式 FreeBSD:打造自己的镜像

在上一卷中,我粗略提及了我用的开发板,Digilent 的 ARTYZ7。在本卷,我将讨论如何制作自己的镜像。到某个时候,你可能会需要一个与现有镜像略有差异的镜像,因此你可能想从源代码开始构建、创建镜像来写入 SD 卡。

首先,让我们了解一下 ARTYZ7 如何启动。 Zynq-7000 SoC 技术参考手册 是一本宝贵的技术信息来源,第 6 章讲述了芯片及所有 Zynq 主板的启动过程。这里有很多技术细节,但根据默认配置的跳线设置,ARTYZ7 是从 SD 卡启动的。你可能已经下载看了我在上一卷中提供的镜像。如果查看过,你会发现它是以 MBR 模式格式化的,首先是 FAT 分区,第二个 MBR 分区上是一个带有 UFS 的 FreeBSD 切片。处理器将查找 MBR,并寻找 FAT16/FAT32 分区。它会在 FAT 分区中查找文件 boot.bin。若找到,它会将其加载到内存中,开始执行。如果你想要直接编程到裸机,可以将你的应用程序写入,命名为 boot.bin。对我来说这有些复杂。因此,当启动 FreeBSD 时,我们使用了一个第一阶段引导加载程序,像许多嵌入式开发板一样,我们使用 Das U-Boot。U-Boot 是一款由社区维护的开源引导加载程序。在我们的用例中,使用了两回 U-Boot。首先,U-Boot 被编译为最小的第一阶段引导加载程序(FSBL),它设置硬件查找第二阶段引导加载程序,第二阶段引导加载程序也是 U-Boot,但功能更加丰富。在我们的使用中,第二阶段的 U-Boot 位于 FAT 分区中的名为 U-boot.img 的文件中。这个 U-Boot 第二阶段加载程序会从 FAT 分区中的 EFI/BOOT/bootarm.efi 文件加载 lua 加载器。然后,lua 加载器会将内核等文件加载到内存中再执行。

所以,如果我们要构建镜像,我们需要创建分区,再把 U-Boot 和 FreeBSD 安装到 SD 卡上。

构建 U-Boot 相对直接。虽然有许多 U-Boot 的移植版本用于各种板子,但 ARTYZ7 并没有现成的移植版本。我已经创建了一个移植版本,你能在这里找到。虽然我还没有把它纳入 FreeBSD 的 ports 中,但你可以直接将其放入最新的 ports 目录 /usr/ports/sysutils 下。 FreeBSD 手册第 4.5 章提供了非常详细的安装和构建 ports 的说明。你将该 port 添加到你的 ports 中后,在 sysinstall/u-boot-artyz7 目录下简单地运行 make,应该就能自动下载构建 U-Boot。运行 make install 后,文件 boot.binU-boot.img 应该会出现在目录 /usr/local/share/U-boot/U-boot-artyz7 下。注意:我以前可以使用较大的 “-j” 值来让 make 使用多个核心进行构建,但在最新的 ports(2024Q3)中,似乎无法正常工作。

从源代码构建也在 FreeBSD 手册中有很好的文档。在第 26.6 章:从源代码更新 FreeBSD 中,你将找到有关下载 FreeBSD 源代码和从中构建的详细信息。如果你已经在 /usr/src 安装了 FreeBSD 源代码,你可以直接进入该目录并运行以下命令:

# make buildworld
# make buildkernel KERNCONF=ARTYZ7

虽然这些命令应该能在 ARTYZ7 板上正常工作,毕竟它有完整的 FreeBSD 安装,但你应该做好准备,它会花费很长时间。由于 PC 硬件变得非常强大且价格低廉,我使用一台 AMD64 系统来托管所有文件、开发环境,并进行所有构建。FreeBSD 对交叉编译和构建提供了内置支持。在我的 PC 上,我使用以下命令让我的 PC 为基于 ARM 的 ARTYZ7 卡板进行源代码构建:

# make buildworld TARGET=arm TARGET_ARCH=armv7 -j32
# make buildkernel KERNCONF=ARTYZ7 TARGET=arm \ TARGET_ARCH=armv7 -j32

这将从 AMD64 构建一个交叉编译器到 ARMv7,并使用这个编译器来构建所有内容。

对于内核配置文件,我已将 src/sys/arm/conf/ZEDBOARD 中的 ZEDBOARD 配置文件复制到 src/sys/arm/conf/ARTYZ7 并在其中修改了名称以匹配。参数 -j32 使得编译过程可以使用最多 32 个进程来进行构建。在一台搭载高速固态硬盘的 AMD 5950x PC 上,构建世界大约需要 10 分钟,构建内核大约需要 60 秒。我无法想象在 ARTYZ7 上需要多少天来完成这个过程。

若你从源代码构建了所有内容,过程可能会有所不同,具体有以下几种方式:

  1. 你可以将 SD 卡挂载到主机开发系统,并直接从主机安装到 SD 卡上。

  2. 你可以使用 mdconfig 命令创建一个基于文件的内存设备。这使你可以将一个文件当作块设备来处理。你可以使用所有标准的 FreeBSD 工具来分区设备,并将分区挂载到文件系统中。从那里,你可以像操作物理设备一样进行安装,最终得到适合使用 dd 复制到 SD 卡或提供给其他人的文件。

  3. 最后一种方法,也是我正在使用并将在这里讨论的方法,是先在主机 PC 上的某个目录中进行安装,然后使用 mkimgmakefs 从主机目录构建一个镜像,该镜像同样适合使用 dd 复制到 SD 卡上。

接下来,我们更详细地考察方法 3。我通常会创建 msdos 目录和 ufs 目录,来表示我需要在 SD 卡上的两个分区:

# mkdir msdos ufs

接下来,我在 ufs 目录上进行安装:

# make installworld installkernel TARGET=arm \
TARGET_ARCH=armv7 -j32 DESTDIR=ufs

你还需要运行分发目标,该目标会在 /etc 中创建所有默认的配置文件。当你对一个工作系统进行源代码升级时,通常不会运行这个命令,因为它会覆盖你的配置文件,但在从零开始构建系统时,你确实需要默认的配置文件:

# make distribution TARGET=arm \
TARGET_ARCH=armv7 DESTDIR=ufs -j32

此时,我已经在 ufs 目录下完成了完整的安装,并可以对镜像进行一些自定义。例如,我可以定制 ufs/etc/rc.conf,以便自动启动以太网接口 cgem0 上的 DHCP 获取 IP 地址。我可以将 ssh 密钥安装到 ufs/etc/ssh 中,这样系统每次启动时都会使用相同的 ssh 密钥,而非在首次启动时生成新的密钥。由于我使用主机系统来进行所有构建并托管我的文件,我还喜欢配置 /etc/fstab 以通过 NFS 挂载我的主目录。

我发现创建一个用户账户非常方便,这样我可以立即登录:

# echo 'xxxx' | pw -R ${ufs} useradd -n crb -m -u 1001 \
-d /homes/crb -g crb -G 1001,wheel,operator\
-c “Christopher R. Bowman” -s /bin/tcsh -H 0

pw 命令的参数 -R 使得 pw 会编辑 ufs/etc 中的密码文件,而不是我主机系统中的文件。参数 -H0 能让我使用 echo 将密码通过管道传输给 pw,而无需交互式输入(你需要使用你主机系统中的编码密码替代 xxxx)。你也可以觉得修改 root 账户的密码更加方便,避免没有密码的情况。

现在,既然我已经按照希望的方式定制了 ufs 目录,我们就来关注一下 FAT 分区。

我需要安装 boot.binU-boot.img

# cp /usr/local/share/U-boot/U-boot-artyz7/boot.bin msdos
# cp /usr/local/share/U-boot/U-boot-artyz7/U-boot.img msdos

boot.bin 会加载 U-boot.img,而 U-boot.img 模拟了 FreeBSD 加载器的 EFI 固件。EFI 系统会在 FAT 分区上查找一个名为 EFI/BOOT/bootarm.efi 的文件,因此我们需要将 FreeBSD 的 lua 加载器复制到该位置:

# mkdir -p msdos/ EFI/BOOT
# cp ufs/boot/loader_lua.efi msdos/EFI/BOOT/bootarm.efi

注意,我复制的是我们构建的 ARM 版本,而不是主机版本(后者是 AMD64 代码)。

现在,我们已经有了两个目录 msdosufs,它们包含了我们想要写入 SD 卡的文件。接下来我们只需要创建镜像文件。这是一个 4 步的过程:

makefs -t msdos \
  -o fat_type=16 \
  -o sectors_per_cluster=1 \
  -o volume_label=EFISYS \
  -s 32m \
  efi.part msdos

makefs -B little \
  -o label=rootfs \
  -o version=2 \
  -o softupdates=1 \
  -s 3g \
  rootfs.ufs ufs

mkimg -s bsd \
-p freebsd-ufs:=rootfs.ufs \
  -p freebsd-swap::1G \
  -o freebsd.part

mkimg -s mbr -f raw -a 1\
-p fat16b:=efi.part \
  -p freebsd:=freebsd.part \
  -o selfbuilt.img

第一个命令从 msdos 目录构建文件系统镜像文件 (efi.part)。第二个命令从 ufs 目录构建文件系统镜像文件 (rootfs.ufs)。第三个命令将 rootfs.ufs 文件组合成一个包含 1GB 交换分区的 FreeBSD 切片。最后一个命令将我们的 efi.partfreebsd.part 文件打包成一个单一的镜像文件 (selfbuilt.img)。

如果我将 SD 卡插入主机,我会看到一个 /dev/da0 设备,并使用简单的 dd 命令将镜像复制到 SD 卡:

# dd if=selfbuilt.img of=/dev/da0 bs=1m status=progress

到此为止,剩下的工作就是将 SD 卡插入 ARTYZ7 板卡,按下重置(reset)按钮,观察精彩的启动过程。由于我在 ufs 分区中的配置,我能够在启动完成后立即通过 ssh 登录到我的板卡,并且已经通过 NFS 挂载我的主目录。现在,要么征服世界,要么喝杯啤酒——反正啤酒什么时候都合适。


Christopher R. Bowman 自 1989 年开始使用 BSD,当时他在约翰斯·霍普金斯大学应用物理实验室的地下二层工作。后来,在 90 年代中期,他在马里兰大学使用 FreeBSD 设计了自己的第一款 2 微米 CMOS 芯片。从那时起,他一直是 FreeBSD 用户,并对硬件设计及其驱动软件非常感兴趣。他在过去 20 年里一直从事半导体设计自动化行业工作。

最后更新于

FreeBSD 中文社区