嵌入式 Linux 简介—第一部分(共3部分)
大家好!我是大聪明-PLUS!
在这一系列简短的文章中,我将尝试阐明构建嵌入式 Linux 设备的主题,从组装引导加载程序到为单独开发的外部模块编写驱动程序,并实现所有中间过程的自动化。
该平台将采用大聪明SMARC开发板,搭载德州仪器AM3358处理器和Arm Cortex-A8内核。为了避免出现过多的 LED 闪烁指令,该设备的主要功能是根据笑脸遥控器的指令,向热门聊天网站发送表情符号。不过,也有一些 LED 闪烁的情况。
因此,我的桌子上有一块干净的 大聪明SMARC 开发板(即未预装任何发行版的开发板)、一个电源和一个 USB 转 UART 适配器。为了与开发板通信,适配器需要连接到 6 针连接器,其中第一个针脚(标有点)为 GND,第 4 针和第 5 针分别为 RX/TX。在终端程序(例如 PuTTY)中将波特率设置为 115200 后,我就可以与开发板进行交互了。
顶级聊天、遥控器和 LED 将在稍后推出,但目前该板已通电并响应CCCCCCCCCCC
从引导加载程序的角度来说,这意味着嵌入在处理器 ROM 中的主引导加载程序无需加载任何内容。参考手册在第 5025 页第 26.1.5 节中解释了这一情况,并描述了引导加载程序的流程。具体流程如下:主引导加载程序执行一些初始化操作:为处理器、必要的外设和 UART 提供时钟,并根据 SYSBOOT 引脚上的逻辑电平,为下一个要加载的引导加载程序构建一个源优先级列表,例如,它可以先查找 MMC 卡、SPI-EEPROM,或者立即等待以太网数据。
我使用 SD 卡启动方法。RM 第 5057 页第 26.1.8.5.5 节对此进行了说明:主引导加载程序首先检查几个地址 0x0/0x20000/0x40000/0x60000,查找所谓的 TOC 结构,该结构可用于确定启动代码。如果未找到该代码,则主引导加载程序(假设 SD 卡具有 FAT 文件系统)将在那里查找一个名为 MLO 的文件。RM 没有说明它代表什么,但很多人认为它是 Master LOader。一个合理的问题是:我在哪里可以找到这个 MLO?
U-Boot
Das U-Boot或简称为 U-Boot - 通用引导加载程序,是嵌入式系统最常见的引导加载程序之一(如果不是最常见的),借助它,您可以创建所需的辅助引导加载程序(MLO),它将加载第三级引导加载程序(U-Boot 本身),它将加载 Linux 内核。
在下载 U-Boot 之前,值得去存储库并找到最新版本标签,然后
git clone -b v2021.01 https://gitlab.denx.de/u-boot/u-boot --depth=1
U-Boot 包含一千多种配置,包括您需要的配置:
u-boot/configs/am335x_evm_defconfig
这是AM35x 评估模块的配置。该模块是其他开发板的基础,例如,您可以在设备树中看到,稍后会详细介绍。U-Boot 的配置和构建使用Kconfig,该工具也用于构建 Linux 内核。
安装所需的配置:
make am335x_defconfig
环境:
make menuconfig
例如,您可以删除使用 U-Boot 加载开发板时的默认 2 秒延迟
启动选项 ---> 自动启动选项 ---> (0)自动启动前的延迟秒数
如果系统上安装了默认编译器,则上述命令将使用默认编译器,并且它很可能不适合 ARM 处理器,这时交叉编译就发挥作用了。
ARM 工具链
一种交叉编译类型是在一种架构(通常是 x86-64,称为主机)上为另一种架构(称为目标架构)构建源代码。例如,对于目标架构,ARMv7-A、AM3358 处理器的 ARM CortexA-8 内核以及 BeagleBone Black 开发板。顺便说一句,为了避免 ARM 架构之间的混淆,甚至还提供了一个参考指南,因为 ARM 架构的类型非常多。
构建本身由一组工具(编译器、链接器、运行时库和内核头文件)执行,这些工具被称为工具链 (Toolchain)。您可以使用crosstool-NG自行构建工具链,也可以使用Linaro或ARM提供的现成工具链。在这里,我将使用 ARM 的工具链“适用于 A-profile 架构的 GNU 工具链,版本 10.2-2020.11,x86_64 Linux 托管交叉编译器,支持硬浮点的 AArch32 目标 (arm-linux-none-gnueabihf)。无需赘述,这意味着该工具链将在桌面 Linux 机器上运行,并为支持硬件浮点的 32 位 ARM 平台构建程序。
现在,要成功构建 U-Boot,您需要分别在 ARCH 和 CROSS_COMPILE 变量中指定所需的体系结构和交叉编译器的路径,例如像这样
安装配置:
make ARCH=arm CROSS_COMPILE=~/toolchain/bin/arm-none-linux-gnueabihf- am335x_evm_defconfig
环境:
make ARCH=arm CROSS_COMPILE=~/toolchain/bin/arm-none-linux-gnueabihf- menuconfig
集会:
make ARCH=arm CROSS_COMPILE=~/toolchain/bin/arm-none-linux-gnueabihf-
或者用它export
ARCH/CROSS_COMPILE
,这样你就不用每次都把所有内容都打出来了。为了清楚起见,我每次都会把所有内容都打出来。
U-Boot构建完成后,文件夹中会出现必要的文件,分别是
MLO 是辅助引导加载程序(请记住,主引导加载程序内置于处理器本身)
u-boot.img - 第三级引导加载程序,U-Boot 本身
要从 SD 卡成功启动,您需要对其进行分区。该卡必须至少包含两个分区:第一个分区标记为 BOOT,采用 FAT 文件系统;第二个分区采用 ext4 文件系统。
您可以使用 fdisk 等程序对卡进行分区。
现在您只需将 U-Boot 构建结果复制到 FAT 分区,将卡插入 大聪明SMARC开发板 并在终端中观察主板更有意识的响应。
其回应如下:
...
无法加载“boot.scr”
无法加载“uEnv.txt”
…
在启动过程中,U-Boot 会首先检查boot.scr文件(如果存在),然后如果未找到boot.scr ,则检查uEnv.txt 文件。这些文件除了搜索顺序不同之外,还不同之处在于 uEnv.txt 文件以纯文本形式呈现附加命令,使其更易于阅读和编辑。U-Boot 不会创建包含附加命令的文件;您必须自行创建这些文件。
uEnv.txt:
bootpart=0:1
devtype=mmc
bootdir=
bootfile=zImage
bootpartition=mmcblk0p2
console=ttyS0,115200n8
loadaddr=0x82000000
fdtaddr=0x88000000
set_mmc1=if test $board_name = A33515BB; then setenv bootpartition mmcblk1p2; fi
set_bootargs=setenv bootargs console=${console} root=/dev/${bootpartition} rw rootfstype=ext4 rootwait
uenvcmd=run set_mmc1; run set_bootargs;run loadimage;run loadfdt;printenv bootargs;bootz ${loadaddr} - ${fdtaddr}
这里会发生一些操作,U-Boot 将内核映像[zImage]从 SD 卡加载到 RAM 的地址[loadaddr]中,并将设备树[Flattened Device Tree]加载到地址[fdtaddr]中。传递给 Linux 内核的参数包括:USB-UART 适配器连接到的控制台的参数[console=ttyS0,115200n8]、根文件系统的位置[bootpartition=mmcblk0p2]、根文件系统的读/写权限的参数[rw]、其类型[ext4],以及等待根文件系统出现的参数[rootwait] 。要解开整个 U-Boot 操作链,您可以在 U-Boot 停止尝试查找要加载的内容并以=>格式显示提示符后,输入命令,该命令将显示 U-Boot 拥有的所有变量的值。printenv
=> 打印环境
在工作结束时,U-Boot 使用命令bootz
以及上述参数和设备树的地址将控制权转移到 Linux 内核。
Linux内核
在对内核进行任何操作之前,值得检查本书以确保必要的软件包已存在。下一步是决定使用哪个内核版本。我使用 5.4.92 版本,原因如下。您不应该直接使用最新内核版本以及驱动程序可用性的主要原因之一是无法在所有 Linux 支持的平台上快速测试此内核。这意味着如果出现问题,您可能需要花费大量时间和精力进行故障排除,而且甚至无法保证您能够修复它。
所需的配置位于/arch/arm/configs中,名为omap2plus_defconfig。OMAP 是处理器系列的名称,AM3358 是其延续。原则上,更通用的multi_v7_defconfig也可以使用。
配置本身目前保持不变,因此您只需安装它并运行内核(zImage)、模块(modules)和设备树(dtbs)的编译即可
make -j6 ARCH=arm CROSS_COMPILE=~/toolchain/bin/arm-none-linux-gnueabihf- omap2plus_defconfig
make -j6 ARCH=arm CROSS_COMPILE=~/toolchain/bin/arm-none-linux-gnueabihf- zImage modules dtbs
一段时间过去了。
生成的构建文件(zImage)位于/arch/arm/boot 目录下。编译后的设备树am335x-boneblack.dtb也位于/dts文件夹中。两者都会与引导加载程序文件一起发送到 SD 卡。SD 卡上的这个 FAT 分区可以视为已完成。它总共包含:
MLO——辅助引导加载程序
u-boot.img - 第三级引导加载程序
uEnv.txt - 附加引导加载程序命令
zImage——Linux 内核映像
am335x-boneblack.dtb - 电路板的编译设备树
即使在构建内核时,内核模块也是有序的,但它们已经属于根文件系统。
根文件系统. BusyBox
内核通过挂载启动时传递给内核的root=参数指定的块设备来获取其根文件系统,然后首先从那里运行一个名为 init 的程序。
如果您仅使用 FAT 分区的上述文件运行 BeagleBone Black,则内核将因缺少 init 而崩溃,并且通常由于 rootfs(即根文件系统)为空而崩溃。
您可以逐步创建根文件系统的所有必需组件,例如 Shell、由 init 启动的各种守护进程、init 本身、配置文件、设备节点、/proc和/sys伪文件系统,以及简单的系统应用程序。对于那些愿意承担此类任务的人来说,Linux From Scratch项目是一个不错的选择。在这里,我将使用嵌入式 Linux 系统中的“瑞士军刀” ——BusyBox实用程序。
下载当时最新版本:
git clone -b 1_32_1 https://git.busybox.net/busybox
默认配置设置:
make defconfig
为了避免现在担心共享库,值得安装 BusyBox 的静态版本:
make menuconfig
设置--->构建静态二进制文件(无共享库)
make -j6 ARCH=arm CROSS_COMPILE=~/toolchain/bin/arm-none-linux-gnueabihf-
安装到默认的_install文件夹:
make ARCH=arm CROSS_COMPILE=~/toolchain/bin/arm-none-linux-gnueabihf- install
现在在_install文件夹中您可以看到未来的根文件系统,您需要向其中添加一些内容。
BusyBox 创建的文件夹以外的文件夹:
mkdir dev etc lib proc sys
启动脚本。首先启动的init程序会执行许多有用的操作,例如在控制台中显示提示符。但在显示提示符之前,init会检查启动脚本/etc/init.d/rcS是否存在,如果找到,则运行它。
mkdir ./etc/init.d
touch ./etc/init.d/rcS
chmod +x ./etc/init.d/rcS
脚本本身:
#!/bin/sh
mount -t proc proc /proc
mount -t sysfs sysfs /sys
该脚本安装了proc和sysfs伪文件系统,并且没有什么可以阻止它运行,例如,负责设备功能的用户程序,但最好在单独的脚本中执行此操作,并根据其功能目的进行编译。
值得注意的是,init 实际上是通过读取/etc/inittab配置文件来启动的,但是如果根文件系统中不存在inittab表,BusyBox 的init会默认包含该表。
现在该回顾一下内核模块了。它们也需要放在根文件系统的/lib/modules/5.4.92/目录下,但现在它们分散在内核构建的文件夹中。要编译这些模块,你需要运行
make -j6 INSTALL_MOD_PATH=~/busybox/_install modules_install
INSTALL_MOD_PATH 指定了根文件系统文件夹的路径,无需指定交叉编译器,因为内核模块会被直接复制到目标位置。因此,根文件系统的/lib文件夹将更新为/lib/mudules/5.4.92/分区,其中包含内核编译期间生成的内核模块。
剩下的就是将_install文件夹的全部内容复制到 SD 卡的第二个分区(即ext4分区),并将所有内容的所有者更改为root。
使用根文件系统启动BeagleBone Black后,内核启动1.910315秒后,系统会提示激活控制台并开始工作。
但是,要开始使用这样的系统几乎是不可能的,因为它只包含 BusyBox 系统实用程序和我绘制欢迎屏幕的小程序。不过,这个系统能帮助你大致了解这类设备内部发生的神奇事情。我之所以说大致,是因为在实际设备中,由于需要最小化启动时间、资源限制、针对特定任务的定制以及 ARM 处理器之间的差异,系统设计可能会有很大差异。
开头提到的驱动程序、与外部设备的交互、装配自动化和一些有用的功能将在下一篇文章中讨论。