此文章翻译自ThirtyThreeForty的Mastering Embedded Linux, Part 1: Concepts

折腾运行Linux的低价嵌入式设备是我的一项爱好。这些设备随处可见,有着高性价比的处理器、极大的存量,价格通常低于10美元。这些嵌入式设备不仅可以用于折腾改造,还可以作为你的其他产品的一部分。

我想在这向你展示这类系统的多样性,希望能够在以后帮助到你。这一系列文章将带领你选择硬件、编译你的第一个镜像,并构建自己的第一块Linux单板电脑。之后将说明如何提供系统更新、自定义Linux内核。

简而言之,这一系列文章将介绍整个嵌入式Linux生态系统。在开始之前,希望你能有一些基础的Linux命令行知识,以及关于嵌入式系统组成部分(内存、处理器、外设)的基础知识。最重要的是有足够的时间来学习和实践知识。

我们首先从高级别的概念开始介绍,让你对整个系统有基础的认识,防止在深入时过于迷惑。

嵌入式Linux概述

如果你已经熟悉微处理器(MCU,microcontroller),你会发现所有你需要的东西都封装在了一块芯片上。如果你购买了一片STM32F1或其他MCU,你会得到闪存(flash memory)、内存(RAM)、处理器核心和一些外设设备。如果你购买的是一些特殊用途的MCU,甚至还会内置蓝牙、USB 3.0 或 MIPI 相机接口等设备。这些设备的供应商通常提供了一个开发环境供你编写代码,而这些代码则会直接运行(裸机运行,无操作系统)在处理器上。最后,这些芯片通常有着方便焊接的封装形式(如SOIC或QFP),方便非专业人士制作自己想要的东西。

这种结构与运行Linux的处理器有一些差异。通常来说,这些处理器有着所有你能想到和想不到的外设。相对应的,这些处理器通常外置内存和存储设备。在此基础上,这类处理器的启动流程增加了Boot ROM。Boot ROM是处理器的一段内置代码,用来加载你存储在外置设备上的Bootloader。

Bootloader会加载Linux内核,而内核则会配置剩下的外设并运行需要的程序。就像传统的Linux系统一样,网络、应用逻辑和用户交互都是在这时运行。从这种层面看,嵌入式Linux和桌面Linux没有什么不同,它们使用相同的API、相似的文件系统,以及相同的网络协议。

上面的这些东西都存放在小型的系统镜像中。根据设备的需求,系统镜像最小可至4MB。需要注意的是,嵌入式Linux系统运行的软件几乎都会与桌面版本一致。Linux系统为你的程序提供了一个通用抽象,使你可以专心的为你的硬件开发Bootloader和移植Linux。

组件

几乎所有的组件都是可以替换的,所以构建一个嵌入式硬件需要你决定每个组件的选择。

微处理器

微处理器的选择将决定你的系统的绝大多数功能。大多数你能接触到的微处理器都是arm或MIPS架构的,而arm核心的市场占有率还在逐年攀升(值得一提的是RISC-V架构,这是一种开源的处理器架构,虽然当前还不是很流行,但还是引起了很多人的关注).

在架构的基础上,芯片厂商会为这些处理器添加许多外设。这些外设(如USB和SPI)存放在芯片上的一个单独部分,通常可以使用内存映射寄存器对他们操作。厂商通常会将旧产品中的外设重用(复制)到新的芯片中,这为我们编写驱动带来了便利。

内存

这些处理器通常都没有内置的内存,需要外接内存才可以正确运行。SDRAM通常提供给低端设备使用,而DDR、DDR2和DDR3则会用于高端一些的设备。虽然使用DDR4的设备还暂时较为罕见,但DDR4迟早还是会用在嵌入式设备上的。处理器提供了一个模块用于管理内存,Boot ROM或Bootloader会正确的初始化这个模块。

部分模块和开发板上已经板载了内存,所以在使用这类设备的时候可以不用额外的内存。

但如果你想从0开始构建嵌入式系统,你就得小心了。在PCB上为内存布线是一项非常复杂的工作,所以我们尽量不选择这类芯片。在下一章中,我将介绍一些我找到的内置内存的芯片。

存储

存储和内存不一样,是用于存放你的数据和代码地方。在嵌入式系统上,存储数据的设备通常都是闪存的一种。你可能想说有些设备可以从网络上启动,但它的Bootloader还是存放在闪存中的。

闪存又分为很多种,最常见的是SD卡。SD卡由存储设备和控制器构成,控制器抽象处理底层的一些交互。虽然有人会告诉你SD卡通常不是很可靠(树莓派玩家可能深有体会),但我会告诉你SD卡做了多少工作来提高可靠性。

eMMC是SD卡的嵌入式版本,和SD卡一样是由存储设备和控制器构成的。但不幸的是,eMMC通常是BGA封装的,这对个人玩家非常不友好。

除了带控制器的存储设备,还有不带控制器的设备。这类设备通常较便宜,可以分为NOR和NAND两类。NOR闪存写入缓慢,存储密度不高,但价格便宜。Boot ROM通常知道怎么通过SPI接口读取NOR闪存的数据。如果文件写入不频繁的话,这种存储是非常合适的。NAND闪存写入更快、存储密度更高,价格相较而言也昂贵一些。SPI接口的NAND是一个不错的选择,但NAND总线接口的NAND可以提供更快的速度。

不带控制器的存储设备并没有想象中那么美好。这些设备通常是按块擦写的,这意味着你必须先擦除一块区域才能修改这块区域的数据。每块区域还有一定的擦写寿命限制(1k~100k),寿命用完后这块区域就损坏了。

软件工程师通常会使用软件来解决上面的硬件问题。Linux UBI系统可以帮助我们绕开上面的硬件限制,使其更易使用。

软件

如果你正在开发嵌入式系统,你就需要提供Bootloader、内核以及文件系统。令人欣慰的是,一旦Linux启动成功,Linux内核就能为你提供一系列外设驱动的接口,方便你使用。

通常来说,这些软件使用了一种通用且易于理解的模式,以便你将其应用到其他地方。

你不需要同时处理这些东西,嵌入式Linux发行版提供了一些工具集,能够帮助你构建你需要的镜像。在后面的文章中我们会介绍如何编译和使用嵌入式系统。

Bootloader

Bootloader是开发者能够自定义的第一个程序。虽然这个程序只要能够加载内核并运行就可以了,但在实际中通常是一个非常复杂的程序。

嵌入式Linux系统通常都会使用 U-Boot。这个Bootloader包括了精简的存储驱动和一些外设驱动,以提供读取并执行内核的功能。

Boot ROM

这里还是得提一句Boot ROM。Boot ROM是一段厂家内置的启动代码,通常非常简单。Boot ROM决定了各个外设的启动顺序,可以查阅处理器的数据手册来得到准确的启动顺序。

Boot ROM可能还会提供一些诸如USB等炫酷的功能。USB功能可以让你在未烧录的设备上通过USB接口直接运行代码或烧录设备。这项功能不同厂商有不同的名字,NXP/Freescale称为下载模式,而全志则称为FEL模式。使用这项模式通常需要额外的下载程序,不同的处理器使用的程序也可能是不同的。

内核

相信你已经知道Linux是什么了。Linux必须移植到特定架构、特定部分和特定设备上才能正常工作。这些设备驱动通常随Linux内核源代码一并提供。虽然设备驱动繁多,但因为厂商通常会重用外设IP,所以这还是能管理的。通常来说,一项Linux移植通常包括了

  • 架构代码:提供了诸如寄存器操作、同步等基础功能。移植Linux到一个全新的架构是一项非常庞大的任务,你在近期大概是不会接触到这项任务的了。
  • 驱动代码:是Linux内核代码的重要部分。Linux源代码提供了所有Linux支持的设备的驱动,而绝大部分的驱动你都不会用上。
  • 设备树:描述了各个硬件是如何连接到系统的,是移植中非常重要的部分。设备树就是Linux驱动的“配置文件”。使用设备树使得重用Linux驱动成为可能,你只需要编译相关的设备驱动,在设备树中添加相应的节点,驱动就会自行根据此节点配置进行初始化。这一部分的详细信息我们稍后再来讨论。

用户空间程序

正如我之前所说,运行于嵌入式Linux系统上的其他软件通常都与桌面系统一致。这里的“其他”指的就是运行于用户空间的程序,或者简称用户程序。在用户空间运行程序,可以直接使用封装好的Linux API,而无需直接与硬件寄存器交互,这就是其最大的有点。

我们将在后面详细讨论用户空间。现在你只需要知道三个概念:文件系统、初始化系统(init system)和shell。

文件系统的重要性在于,部分文件系统更适用于嵌入式系统。还记得我们在上面说的SD卡的可靠性问题吗?文件系统可以帮助我们解决这一问题。如果你正在使用eMMC或SD卡,那么Linux会认为其是一个块设备。你可以使用通常的文件系统(如ext2/3/4),或使用 f2fs 文件系统以增强稳定性。但如果你正在使用不带控制器的闪存,你就会需要为这种闪存设计的文件系统,如 JFFS2UBIFS。我个人的习惯是在UBI分区上使用squashfs文件系统。我们将在自定义我们的镜像的时候继续讨论这个问题。

接下来是初始化系统,初始化系统用于管理用户空间程序。你可能听过systemd,它就是一种初始化系统。systemd通常用于桌面操作系统,或更庞大一些的嵌入式操作系统。但它的体积有些过于庞大了,对于一般的嵌入式操作系统来说,通常会使用SysV初始化系统,或者直接使用shell脚本。大多数嵌入式操作系统都使用这两种初始化系统。等到后面我们要自定义镜像的时候,我会告诉你如何将你的程序设置为开机自启动。

最后是Shell。Shell是和你交互的命令行。通常来说,你会使用UART串口来与Shell交互,或者通过一个屏幕来交互。如果你的系统能够顺利进入到Shell,那么就说明你的系统已经成功的运行了!

接下来……

感谢你能看完上面这一大串文字!这个系列应该会包含以下部分:

  • 硬件:选择一块开发板。我在这选择了树莓派0作为第一个教程的硬件。当然,我也会介绍其他的开发板。如果你希望自己做一块开发板的话,我建议你使用那些易于焊接的硬件。
  • Buildroot:为了方便,我们将会使用Buildroot来从0开始编译一个完整的操作系统。我们将会在真实硬件上烧写我们的系统,这部分我将会着重进行说明。
  • 自定义固件镜像:然后,我们将会深入Buildroot的配置,为我们的树莓派0自定义固件。你做的每一步都非常的简单与直白,但是如果你把他们组合起来,你就会得到一个与原厂完全不一样的系统!
  • 裁剪镜像:在这一章中,我们将抛弃树莓派,而转向只有4MB存储空间的小型开发板。你永远也想像不到,有多少功能是可以被裁剪的。(就像node_modules一样)
  • 修改U-Boot和Linux:当你的开发板的Uboot代码和Linux内核代码不再受到支持的时候,你要怎么做?
  • 从0开始构建自己的开发板:从挑选硬件开始,构建自己的开发板!

下一章中,我们会讨论使用的硬件。如果你感兴趣的话,欢迎订阅此系列教程。


1 条评论

何坡坡 · 2020 年 4 月 28 日 下午 5:11

写的太棒了,但是刚来看的来劲,就没了。

发表回复

Avatar placeholder

您的邮箱地址不会被公开。 必填项已用 * 标注