简介:典型的嵌入式系统
启动嵌入式 Linux 系统的第一步实际上与 Linux 无关。相反,处理器被重置并开始从给定的位置执行代码。这个位置包含一个引导加载程序,它初始化设备并设置基本必需品。当一切准备就绪后,Linux 内核被加载并启动。然后内核初始化所有设备,然后挂载文件系统并启动用户空间应用程序。
Linux 内核和用户空间不仅仅是一个简单的加载和运行的 blob。内核由特定于系统的配置和通常一些经过调整的初始化代码组成。用户空间包含软件库、数据和多个应用程序,所有这些都相互作用以形成一个系统。为了获得紧凑且性能良好的系统,这些组件中的每一个都为手头的任务和设备精心挑选。图 1 显示了事件的基本顺序。
引导加载程序是系统上最早运行的软件之一。它基本上有两个任务:初始化系统和加载内核。初始化可以设置为设置一个 UART 以用作串行调试控制台,并配置系统的内存控制器。例如,如果您的系统正在使用 SDRAM,您可能必须根据内存的物理特性来设置控制器。这包括页面大小、列数、支持的读写宽度、延迟等等。在当今的便携式设备时代,在内存方面通常有大量的节能设置。
除了引导加载程序所需的基本任务之外,通常还会提供某种命令提示符,可以在其中执行常见的低级任务。这些任务通常包括查看和探测随机内存地址、在闪存中下载和存储 Linux 内核镜像以及设置供内核解释的 bootargs。
嵌入式系统的常见引导加载程序示例有 Das U-Boot 和 RedBoot。两者都支持基本任务——意味着它们可以管理闪存、网络和串行通信。它们也适用于多个处理器平台,例如 x86、ARM、PowerPC 等。您也可以向其中添加自己的命令。这使得在不涉及 Linux 的情况下调试自定义硬件成为可能,从而降低了测试阶段系统的复杂性。
内核本身与普通的桌面内核没有太大区别。但是,有两个主要区别。首先是初始化,它通常是特定于系统的。其次,您可能确切地知道将使用什么硬件,因此您可以将所有驱动程序作为内核的一部分包含在内,并避免对模块的需求(除非您有专有驱动程序,当然)。
当启动桌面或服务器系统时,常见的场景是内核探测硬件并加载相应的驱动程序作为模块。这使得添加硬件并仍然拥有可工作的系统成为可能。您还可以添加新硬件的驱动程序,而无需重新编译整个内核。在嵌入式系统上,您可以通过将所有驱动程序包含在内核中来优化启动时间,也可以通过硬编码可用硬件的部分来优化启动时间,从而避免探测所有设备和设置的需要。
回到标准 PC,每台机器在初始化期间启动和看起来都大致相同。在嵌入式情况下,每件硬件都是独一无二的,您通常必须初始化自定义硬件。这意味着您实际上必须编写代码来为您的板设置内核,这通常比您想象的要容易。首先,Linux 内核已经支持许多板,您通常可以选择其中一个作为起点。其次,大多数常见外围设备都有驱动程序,而且,即使您必须创建自己的东西,您通常也可以找到一个好的起点。因此,这个过程或多或少是研究您板的数据表,并将您学到的东西表达给内核(这可能既令人生畏又让人望而却步)。
在系统资源方面,嵌入式系统通常比普通计算机更受限制,因此保持内核的 footprint 小很重要。反过来,这使得内核配置阶段变得重要。通过将配置限制到最低限度,您可以节省那些将所有内容都装进去所需的额外字节。
标准 C 库是任何 Linux 系统的关键组件之一。它为用户空间应用程序提供了预定义的接口,使其可以在不同版本的 Linux 内核以及不同的 UNIX 方言之间移植。它基本上充当用户空间应用程序和内核之间的桥梁。
您通常在桌面机器上找到的 C 库版本是 GNU C 库 glibc。它是一个功能齐全的 C 库,因此,是一个非常庞大的软件。对于嵌入式系统,可以使用一些较小的替代方案:uClibc、newlib、dietlibc 等。这些库尝试以最小的方式实现最常用的接口。这意味着它们在很大程度上与 glibc 兼容,但并非完全兼容。
那么,C 库中可以删除哪些内容呢?例如,uClibc 跳过了数据库库,限制了支持的身份验证方法的数量,没有完全实现区域设置支持,将数学库主要限制为双精度浮点数,并省略了一些加密函数。此外,内核的结构在可能的情况下直接使用。这些和其他因素显着减小了库的大小。
这对您作为嵌入式开发人员意味着什么?最重要的是,这意味着您可以节省相当多的内存,尽管您这样做是以兼容性为代价的。例如,在适用时使用内核结构的决定意味着 stat 结构与 glibc 使用的结构不同。您还必须将自己限制为平面密码文件和共享密码文件,除非您想添加第三方库来处理身份验证。还存在更多限制,但总的来说,大多数软件都可以在不打补丁的情况下愉快地编译。
当您拥有引导加载程序、内核和标准库时,愿望清单上的下一项通常是命令提示符。BusyBox 是嵌入式 Linux 世界中的一颗巨星。该项目背后的想法是,大多数标准应用程序(例如 ls、cd、mkdir、ping 等)共享大量代码。单独编译每个程序意味着处理命令行参数等代码在每个应用程序中重复。BusyBox 通过提供一个可以处理所有标准应用程序提供的所有任务的单个程序 busybox 来解决这个问题。通过为所有单独的命令创建符号链接并将它们指向 BusyBox,用户仍然可以输入预期的命令并获得预期的结果。
与嵌入式世界中的其他一切一样,调整和优化非常重要。在 BusyBox 方面,您可以手动选择要包含的命令,对于某些命令,您甚至可以手动选择支持哪些命令行参数。如果您不需要特定的命令,只需不要将其包含在 BusyBox 中即可。例如,如果您没有网络,为什么要保留 ifconfig 呢?
在桌面 PC 上构建动态链接的默认配置 BusyBox 时,它会生成一个刚好小于 700KB 的二进制文件。这个二进制文件代表 200 多个命令,并在我的基于 Kubuntu 的系统上占用超过 6MB 的磁盘空间。
一旦您将所有关键组件都就位,您就可以开始构建和填充根文件系统。这涉及添加 BusyBox、设备文件和预期目录。您可能还想添加 /etc/password 和 /etc/shadow、init 脚本等等。所有这些都是必要的,但要让您的设备做一些事情,您需要添加您自己的应用程序。
当为嵌入式设备开发时,您可能会发现自己身处一个完全没有图形界面的系统中。这通常意味着将您的功能实现为某种服务器。随着越来越多的设备联网,Web 服务器通常取代了用户界面。由于 Apache 是一个庞大的软件,因此常见的解决方案是使用轻量级服务器(例如 Boa)进行配置和信息。
如果您碰巧有一个显示器,您很可能希望在其上放置图形。X 服务器听起来可能是一个解决方案,但用于构建图形界面的两个最常见的工具包 Qt 和 GTK+ 也支持直接使用帧缓冲——再次节省了内存和计算资源。
而这就是嵌入式设备工程的全部内容:以尽可能少的资源实现最多的功能。能够将最酷的功能融入到一个小型系统中意味着以优惠的价格为消费者带来有吸引力的设备。使用嵌入式 Linux 来做到这一点意味着您可以比使用闭源系统更快、更便宜地完成工作,并且更易于 hack。
文件系统
为您的嵌入式系统选择文件系统取决于许多因素。您是否需要能够写入它?您重视大小还是速度?您是否希望能够在不更换内核的情况下更换文件系统?
您还需要了解存储介质的限制。例如,闪存对每个单元可以写入的次数有限制。为了延长基于闪存的设备的寿命,最好使用针对此目的进行调整的文件系统。
有许多文件系统可供选择,但以下三个很有趣,因为它们显示了您应该考虑的一些重要因素
initramfs:嵌入到内核镜像中的文件系统。如果您的内核被压缩,则 initramfs 文件系统会与内核一起解压缩。这为系统带来了性能优势。文件系统在设备运行时保存在 RAM 中,并且可以修改。但是,所有修改都会在重新启动时丢失。
cramfs/squashfs:两个压缩的只读文件系统。这两个系统都允许您创建一个压缩镜像,您可以在运行时挂载它。可以在不接触内核的情况下更换文件系统。
jffs2/ubifs:为闪存设备调整的压缩文件系统。这些文件系统可以永久写入,并且它们试图通过在设备上分散写入操作来最大限度地减少闪存块的“磨损”。
幸运的是,您不必选择这些文件系统之一;相反,您可以将它们混合使用——例如,从包含最基本工具的 initramfs 镜像开始,然后挂载 jffs2 闪存分区以存储用户数据。由于 Linux 允许您将文件系统挂载到目录树中的任何位置,因此您可以使应用程序使用文件系统的过程变得透明。
交叉编译
嵌入式开发的一个有趣的方面是您可能会遇到新的处理器系列。你们大多数人在家中使用 x86 硬件;有些人可能拥有 SPARC、68k 或 MIPS 系统。对于嵌入式系统,您可能会遇到 ARM、SH、PowerPC 或 MIPS 等。
这意味着您必须从桌面构建机器(您的主机)为目标设备交叉编译所有内容。生成的二进制文件无法直接在您的桌面机器上运行。您可以使用 QEMU 等模拟器来模拟常见的 CPU 来做到这一点,但是您将不得不在目标设备上进行一些测试,可能还有一些调试。
有时您可以从供应商或发行版获得交叉编译器。您也可以自己构建。构建自己的交叉编译器曾经是一件非常痛苦的事情,但如今,您可以使用 Dan Kegel 的 crosstool。Crosstool 是一组脚本和补丁,允许您为您选择的平台构建 gcc 和标准库。
Crosstool 最强大的功能是您可以(尝试)构建编译器和标准库的任何组合。这使得尝试为现有设备构建工具链变得容易。
发行版和框架
尽管自己动手很有趣,但有时时间不允许这样做。嵌入式 Linux 领域存在许多商业参与者,并且还有许多免费开发的工具可用于构建完整的可嵌入环境。以下列表包含一些您可能考虑使用的工具
Buildroot:一组用于构建完整可嵌入系统的 Makefile 和补丁。它生成从交叉编译器、内核和软件库到用户空间应用程序的所有内容。生成的系统使用 uClibc。
Ångström 发行版:另一个用于构建嵌入式 Linux 系统的构建框架。它还配备了一个包管理器。这使得可以直接从设备添加和删除应用程序,而不是必须构建和下载整个系统镜像或手动将应用程序的文件复制到正确的位置。
ScratchBox:一个用于简化嵌入式 Linux 应用程序开发的构建框架。它通过 Maemo 开发平台(Nokia N7/8/9xx 互联网平板电脑)获得了采用。它支持交叉编译整个发行版,可以在 glibc 和 uClibc 之间切换,并使用 QEMU 模拟目标。
在所有这些情况下,都需要做一些工作才能使发行版在新系统上运行。与嵌入式系统一样,没有任何东西是标准化的,并且尺寸通常很重要,因此进行一些调整或多或少是不可避免的。但是,拥有一个用于构建工作系统的框架可以真正节省时间。
资源
Crosstool: www.kegel.com/crosstool
Das U-Boot: www.denx.de/wiki/U-Boot
RedBoot: www.sourceware.org/redboot
uClibc: www.uclibc.org
newlib: www.sourceware.org/newlib
dietlibc: www.fefe.de/dietlibc
Buildroot: buildroot.uclibc.org
Ångström Distribution: www.angstrom-distribution.org
ScratchBox: www.scratchbox.org
BusyBox: www.busybox.net
Boa: www.boa.org
Qt: qt.nokia.com
GTK+: www.gtk.org
Johan Thelin 自 1995 年以来一直从事软件开发工作,自 2000 年以来一直从事 Qt 工作。在见证了服务器端企业软件、桌面应用程序和 Web 解决方案之后,他现在作为顾问专注于嵌入式系统。可以通过 johan@thelins.se 与他联系。