本文翻译自 jaycarlson 的 blog: So you want to build an embedded Linux system?
After I published my $1 MCU write-up, several readers suggested I look at application processors — the MMU-endowed chips necessary to run real operating systems like Linux. Massive shifts over the last few years have seen internet-connected devices become more featureful (and hopefully, more secure ), and I’m finding myself putting Linux into more and more places.
在我发表了关于 1 美元 MCU (microcontroller unit, 微控制器) 的文章后, 一些读者建议我考虑一下应用处理器——运行 Linux 等真实操作系统所需的带有 MMU (memory management unit, 内存管理单元) 的芯片. 过去几年里这一方面的发展非常迅速, 联网设备的功能变得更加丰富 (并且有望更加安全 ), 并且我自己也正在将 Linux 运用到越来越多的地方.
Among beginner engineers, application processors supplicate reverence: one minor PCB bug and your $10, 000 prototype becomes a paperweight. There’s an occult consortium of engineering pros who drop these chips into designs with utter confidence, while the uninitiated cower for their Raspberry Pis and overpriced industrial SOMs.
对以前的工程师来说, 应用处理器备受敬畏: 一个微小的 PCB 错误, 你价值 10,000 美元的样板就变成了费纸一张. 在这一领域, 有一群工程专家, 他们能够满怀自信地将这些芯片放入设计中, 而那些不懂行的人则只能对着他们的树莓派和定价过高的工业 SoM (system on module, 系统模组) 瑟瑟发抖.
This article is targeted at embedded engineers who are familiar with microcontrollers but not with microprocessors or Linux, so I wanted to put together something with a quick primer on why you’d want to run embedded Linux, a broad overview of what’s involved in designing around application processors, and then a dive into some specific parts you should check out — and others you should avoid — for entry-level embedded Linux systems.
本文针对熟悉微控制器但不熟悉微处理器或 Linux 的嵌入式工程师, 因此我想整理一些内容, 包括为什么要运行嵌入式 Linux, 以及应用处理器设计所涉及的内容的广泛概述, 然后深入研究入门级嵌入式 Linux 系统中应该着重学习的一些部分和应当避开的坑.
Just like my microcontroller article, the parts I picked range from the well-worn horses that have pulled along products for the better part of this decade, to fresh-faced ICs with intriguing capabilities that you can keep up your sleeve.
就像我微控制器相关的文章一样, 我选择的硬件从在过去十年推动行业发展的陈旧老马到你可能想要上手的具有有趣功能的新面孔都有.
If my mantra for the microcontroller article was that you should pick the right part for the job and not be afraid to learn new software ecosystems, my argument for this post is even simpler: once you’re booted into Linux on basically any of these parts, they become identical development environments.
如果你记得我在关于微控制器的文章中提到的格言是你应该为工作选择合适的硬件, 并且不要害怕学习新的软件生态, 那么我这篇文章的要说的就更简单了: 一旦你在这些硬件中的任何一个启动了 Linux, 那么你就明白其他硬件也是完全相同的开发环境.
That makes chips running embedded Linux almost a commodity product: as long as your processor checks off the right boxes, your application code won’t know if it’s running on an ST or a Microchip part — even if one of those is a brand-new dual-core Cortex-A7 and the other is an old ARM9. Your I2C drivers, your GPIO calls — even your V4L-based image processing code — will all work seamlessly.
这使得运行嵌入式Linux的芯片几乎变成了商品化的产品一样: 只要你的处理器满足必要条件, 你的应用代码就不会知道它是运行在ST的芯片上, 还是Microchip的芯片上——即使其中一个是全新的双核Cortex-A7, 另一个是老旧的ARM9. 你的I2C驱动、GPIO调用, 甚至是基于V4L的图像处理代码, 都能无缝运行.
At least, that’s the sales pitch. Getting a part booted is an entirely different ordeal altogether — that’s what we’ll be focused on. Except for some minor benchmarking at the end, once we get to a shell prompt, we’ll consider the job completed.
至少, 这是销售说辞. 但启动一个硬件是一个完全不同的考验——而这就是我们将关注的重点. 除了最后的一些基准测试之外, 一旦出现 shell 提示符, 我们就认为启动硬件的工作完成了.
As a departure from my microcontroller review, this time I’m focusing heavily on hardware design: unlike the microcontrollers I reviewed, these chips vary considerably in PCB design difficulty — a discussion I would be in error to omit. To this end, I designed a dev board from scratch for each application processor reviewed. Well, actually, many dev boards for each processor: roughly 25 different designs in total. This allowed me to try out different DDR layout and power management strategies — as well as fix some bugs along the way.
与我对 MCU 的讲解不同, 这次我主要关注硬件设计: 不可忽视的是, 与 MCU 不同, 这些芯片在 PCB 设计难度上差异很大. 为此, 我为我提到的每个应用处理器从头开始设计了一个开发板. 好吧, 实际上我给每个处理器设计了多个开发板, 总共大约有 25 种不同的设计. 这让我能够尝试不同的 DDR (Double Data Rate Synchronous Dynamic Random Access Memory, 双倍数据率同步动态随机存储器) 布局和电源管理策略, 在此过程中也修复了一些 bug.
I intentionally designed these boards from scratch rather than starting with someone else’s CAD files. This helped me discover little “gotchas” that each CPU has, as well as optimize the design for cost and hand-assembly. Each of these boards was designed across one or two days’ worth of time and used JLC’s low-cost 4-layer PCB manufacturing service.
我有意从头开始设计这些开发板, 而不是从别人的 CAD 文件开始. 这帮助我发现每个 CPU 的小 “陷阱”, 并且为成本和手工焊接做了优化. 每块板的设计都花费了一两天的时间, 并使用了 JLC 的低成本 4 层 PCB 制造服务.
These boards won’t win any awards for power consumption or EMC: to keep things easy, I often cheated by combining power rails together that would typically be powered (and sequenced!) separately. Also, I limited the on-board peripherals to the bare minimum required to boot, so there are no audio CODECs, little I2C sensors, or Ethernet PHYs on these boards.
这些开发板在功耗或 EMC (electromagnetic compatibility, 电磁兼容性) 方面没有任何优势: 我经常将一般需要单独供电 (并且有启动顺序! ) 的电源轨组合在一起来简化设计. 此外, 我将板载外设限制为启动所需的最基本配置, 因此这些板上没有音频编解码器 (CODEC, compress/decompress)、小型 I2C 传感器或以太网 PHY (Port Physical Layer, 端口物理层).
As a result, the boards I built for this review are akin to the notes from your high school history class or a recording you made of yourself practicing a piece of music to study later. So while I’ll post pictures of the boards and screenshots of layouts to illustrate specific points, these aren’t intended to serve as reference designs or anything; the whole point of the review is to get you to a spot where you’ll want to go off and design your own little Linux boards. Teach a person to fish, you know?
因此, 我为这篇文章画的开发板类似于你高中历史课上的笔记或你自己练习一首音乐以供以后学习的录音. 因此, 虽然我将发布电路板的图片和布局的屏幕截图来说明具体要点, 但这些并不是为了用作参考设计或任何东西; 这篇文章的重点是让你了解如何设计自己的小型 Linux 板. 授人以渔, 明白了吗?
Microcontroller vs Microprocessor: Differences#
微控制器与微处理器: 差异
Coming from microcontrollers, the first thing you’ll notice is that Linux doesn’t usually run on Cortex-M, 8051, AVR, or other popular microcontroller architectures. Instead, we use application processors — popular ones are the Arm Cortex-A, ARM926EJ-S, and several MIPS iterations.
相比微控制器, 你首先会注意到的是 Linux 通常不在 Cortex-M、8051、AVR 或其他流行的微控制器架构上运行. 相反, 它们使用应用处理器——常用是 Arm Cortex-A、ARM926EJ-S 和几个 MIPS 版本.
The biggest difference between these application processors and a microcontroller is quite simple: microprocessors have a memory management unit (MMU), and microcontrollers don’t. Yes, you can run Linux without an MMU, but you usually shouldn’t: Cortex-M7 parts that can barely hit 500 MHz routinely go for double or quadruple the price of faster Cortex-A7s. They’re power-hungry: microcontrollers are built on larger processes than application processors to reduce their leakage current. And without an MMU and generally-low clock speeds, they’re downright slow.
这些应用处理器和微控制器之间的最大区别非常简单: 微处理器有内存管理单元 (MMU), 而微控制器没有. 是的, 你可以在没有 MMU 的情况下运行 Linux, 但通常不推荐这样做: 频率勉强达到 500 MHz 的 Cortex-M7 芯片的价格通常是更快的 Cortex-A7 部件的好几倍. 它们非常耗电: 微控制器采用比应用处理器更大的制程来生产, 以减少漏电流. 由于没有MMU且时钟速度通常较低, 它们的性能相当慢.
Other than the MMU, the lines between MCUs and MPUs are getting blurred. Modern application processors often feature a similar peripheral complement as microcontrollers, and high-end Cortex-M7 microcontrollers often have similar clock speeds as entry-level application processors.
除了 MMU 之外, MCU 和 MPU 之间的界限也变得越来越模糊. 现代应用处理器通常具有与微控制器类似的外设补充, 而高端 Cortex-M7 微控制器通常具有与入门级应用处理器类似的时钟速度.
Why would you want to Linux?#
为什么你想用 Linux?
When your microcontroller project outgrows its super loop and the random ISRs you’ve sprinkled throughout your code with care, there are many bare-metal tasking kernels to turn to — FreeRTOS, ThreadX (now Azure RTOS ), RT-Thread, μC/OS, etc. By an academic definition, these are operating systems. However, compared to Linux, it’s more useful to think of these as a framework you use to write your bare-metal application inside. They provide the core components of an operating system: threads (and obviously a scheduler ), semaphores, message-passing, and events. Some of these also have networking, filesystems, and other libraries.
当你的微控制器项目无法满足于只在主循环中处理, 你不得不小心翼翼地在代码中的添加各种中断服务程序时, 有许多裸机任务调度内核可以选择——如 FreeRTOS、ThreadX (现在是Azure RTOS)、RT-Thread、μC/OS 等. 从学术定义来看, 它们是操作系统. 然而, 相比于 Linux, 更有用的方式是将它们看作在裸机环境中编写应用程序的框架. 它们提供了操作系统的核心组件: 线程 (显然还有调度器)、信号量、消息传递和事件. 有些还提供网络、文件系统和其他库.
Comparing bare-metal RTOSs to Linux simply comes down to the fundamental difference between these and Linux: memory management and protection. This one technical difference makes Linux running on an application processor behave quite differently from your microcontroller running an RTOS. ((Before the RTOS snobs attack with pitchforks, yes, there are large-scale, well-tested RTOSes that are usually run on application processors with memory management units. Look at RTEMS as an example. They don’t have some of the limitations discussed below, and have many advantages over Linux for safety-critical real-time applications.))
将裸机 RTOS 与 Linux 进行比较, 归根结底就是它们与Linux之间的根本区别: 内存管理和保护. 正是这一技术差异使得在应用处理器上运行的 Linux 与在微控制器上运行 RTOS 的行为截然不同. ((叠个甲, 确实存在一些大型的、经过充分测试的 RTOS 用于带有内存管理单元的应用处理器. 以 RTEMS 为例. 这些 RTOS 并没有下文讨论的某些限制, 在安全关键的实时应用中, 相较于 Linux, 它们有许多优势. ))
Dynamic memory allocation#
动态内存分配
Small microcontroller applications can usually get by with static allocations for everything, but as your application grows, you’ll find yourself calling
malloc()
more and more, and that’s when weird bugs will start creeping up in your application. With complex, long-running systems, you’ll notice things working 95% of the time — only to crash at random (and usually inopportune) times. These bugs evade the most javertian developers, and in my experience, they almost always stem from memory allocation issues: usually either memory leaks (that can be fixed with appropriatefree()
calls ), or more serious problems like memory fragmentation (when the allocator runs out of appropriately-sized free blocks).
微控制器上的小型程序通常可以对所有内容进行静态分配, 但随着应用程序的增长, 你会发现自己越来越多地调用 malloc()
, 这时奇怪的错误就会开始在你的程序中蔓延. 对于复杂、长时间运行的系统, 你可能会发现它在 95% 的时间都正常工作, 只是在随机 (通常是不合时宜的) 时间崩溃. 这些错误避开了大多数优秀的开发人员, 但根据我的经验, 它们几乎总是源于内存分配问题: 通常要么是内存泄漏 (可以通过适当的 free()
调用来修复), 要么是更严重的问题, 例如内存碎片 (当分配器用完适当大小的空闲块).
Because Linux-capable application processors have a memory management unit,
*alloc()
calls execute swiftly and reliably. Physical memory is only reserved (faulted in) when you actually access a memory location. Memory fragmentation is much less an issue since Linux frees and reorganizes pages behind the scenes. Plus, switching to Linux provides easier-to-use diagnostic tools (like valgrind) to catch bugs in your application code in the first place. And finally, because applications run in virtual memory, if your app does have memory bugs in it, Linux will kill it — leaving the rest of your system running. ((As a last-ditch kludge, it’s not uncommon to call your app in a superloop shell script to automatically restart it if it crashes without having to restart the entire system.))
由于支持 Linux 的应用处理器具有内存管理单元, *alloc()
调用执行得既迅速又可靠. 物理内存只有在程序实际访问某个内存位置时才会实际存在 (或者说才有可能才触发故障). 内存碎片化问题较小, 因为 Linux 会在后台自动释放并重新组织内存页. 而且, 切换到 Linux 后, 你可以使用更容易上手的诊断工具 (例如 valgrind) 来捕捉应用代码中的错误. 最后, 因为应用程序运行在虚拟内存中, 如果你的应用程序确实存在内存问题, Linux 会终止它——这样就不会影响系统的其他部分运行. ((作为一种补救措施, 在一些情况下, 会通过在 shell 脚本的循环中调用应用程序来确保如果应用崩溃可以自动重启, 而无需重新启动整个系统. ))
Networking & Interoperability#
网络与互操作性
Running something like lwIP under FreeRTOS on a bare-metal microcontroller is acceptable for a lot of simple applications, but application-level network services like HTTP can burden you to implement in a reliable fashion. Stuff that seems simple to a desktop programmer — like a WebSockets server that can accept multiple simultaneous connections — can be tricky to implement in bare-metal network stacks. Because C doesn’t have good programming constructs for asynchronous calls or exceptions, code tends to contain either a lot of weird state machines or tons of nested branches. It’s horrible to debug problems that occur. In Linux, you get a first-class network stack, plus tons of rock-solid userspace libraries that sit on top of that stack and provide application-level network connectivity. Plus, you can use a variety of high-level programming languages that are easier to handle the asynchronous nature of networking.
对于许多简单的应用程序来说, 在裸机微控制器上的 FreeRTOS 下运行 lwIP 之类的东西是可以接受的, 但是以可靠的方式实现像 HTTP 这样的应用程序级网络服务会非常麻烦. 对于桌面程序员来说看似简单的东西 (例如可以同时接受多个连接的 WebSockets 服务器) 在裸机网络堆栈中实现起来可能很棘手. 由于 C 的编程结构对异步调用或异常处理没有良好的支持, 因此代码往往包含大量奇怪的状态机或大量嵌套分支. 调试发生的问题是很可怕的. 在 Linux 中, 你可以获得一流的网络堆栈, 以及位于该堆栈之上并提供应用程序级网络连接的大量坚如磐石的用户空间库. 另外, 你可以使用各种高级编程语言, 更容易处理网络的异步特性.
Somewhat related is the rest of the standards-based communication / interface frameworks built into the kernel. I2S, parallel camera interfaces, RGB LCDs, SDIO, and basically all those other scary high-bandwidth interfaces seem to come together much faster when you’re in Linux. But the big one is USB host capabilities. On Linux, USB devices just work. If your touchscreen drivers are glitching out and you have a client demo to show off in a half-hour, just plug in a USB mouse until you can fix it (I’ve been there before). Product requirements change and now you need audio? Grab a $20 USB dongle until you can respin the board with a proper audio codec. On many boards without Ethernet, I just use a USB-to-Ethernet adapter to allow remote file transfer and GDB debugging. Don’t forget that, at the end of the day, an embedded Linux system is shockingly similar to your computer.
与此相关的是内核中内置的其他基于标准的通信/接口框架. I2S、并行摄像头接口、RGB LCD、SDIO, 基本上所有那些令人害怕的高带宽接口, 在 Linux 环境下似乎能更快地集成. 但是最重要的是 USB 主机功能. 在 Linux 上, USB 设备只需要插上就能用. 如果你的触摸屏驱动程序出现问题, 而你在半小时后有一个客户演示要展示, 在可以修复它之前只需插入 USB 鼠标替代即可 (我之前就经历过这种情况). 产品需求变了, 现在你需要音频功能? 拿一个 20 美元的 USB 音频适配器就行, 之后再考虑重新设计电路板加上一个合适的音频解码器. 对于许多没有以太网的开发板, 我通常使用 USB 转以太网适配器来进行远程文件传输和 GDB 调试. 别忘了, 归根结底, 嵌入式 Linux 系统与你的电脑是非常相似的.
Security #
安全
When thinking about embedded device security, there are usually two things we’re talking about: device security (making sure the device can only boot from verified firmware ), and network security (authentication, intrusion prevention, data integrity checks, etc).
在考虑嵌入式设备安全时, 我们通常会讨论两件事: 设备安全 (确保设备只能从经过验证的固件启动) 和网络安全 (身份验证、入侵防御、数据完整性检查等).
Device security is all about chain of trust: we need a bootloader to read in an encrypted image, decrypt and verify it, before finally executing it. The bootloader and keys need to be in ROM so that they cannot be modified. Because the image is encrypted, nefarious third-parties won’t be able to install the firmware on cloned hardware. And since the ROM authenticates the image before executing, people won’t be able to run custom firmware on the hardware.
设备安全与信任链有关: 我们需要一个引导加载程序来读取加密的镜像, 解密并验证它, 然后才能执行它. 引导加载程序和密钥需要位于无法修改的 ROM 中. 由于镜像已加密, 恶意第三方将无法在山寨的硬件上安装固件. 由于 ROM 在执行之前对镜像进行身份验证, 因此人们将无法在硬件上运行自定义固件.
Network security is about limiting software vulnerabilities and creating a trusted execution environment (TEE) where cryptographic operations can safely take place. The classic example is using client certificates to authenticate our client device to a server. If we perform the cryptographic hashing operation in a secure environment, even an attacker who has gained total control over our normal execution environment would be unable to read our private key.
网络安全是指限制软件漏洞并创建可安全进行加密操作的可信执行环境 (TEE). 典型的示例是使用客户端证书向服务器验证我们的客户端设备. 如果我们在安全的环境中执行加密哈希操作, 即使攻击者完全控制了我们的正常执行环境, 也无法读取我们的私钥.
In the world of microcontrollers, unless you’re using one of the newer Cortex-M23/M33 cores, your chip probably has a mishmash of security features that include hardware cryptographic support, (notoriously insecure) flash read-out protection, execute-only memory, write protection, TRNG, and maybe a memory protection unit. While vendors might have an app note or simple example, it’s usually up to you to get all of these features enabled and working properly, and it’s challenging to establish a good chain of trust, and nearly impossible to perform cryptographic operations in a context that’s not accessible by the rest of the system.
在微控制器领域, 除非你使用较新的 Cortex-M23/M33 之类的内核, 你的芯片才可能具有多种安全功能, 包括硬件加密支持、(众所周知的并不安全的) 闪存读取保护、只执行内存、写保护、TRNG, 也许还有内存保护单元. 虽然供应商可能有应用程序说明或简单的示例, 但所有这些功能的启用和正常工作还是取决于你, 并且建立良好的信任链具有挑战性, 而且几乎不可能在系统其他部分不能访问的上下文中执行加密操作.
Secure boot isn’t available on every application processor reviewed here, it’s much more common. While there are still vulnerabilities that get disclosed from time to time, my non-expert opinion is that the implementations seem much more robust than on Cortex-M parts: boot configuration data and keys are stored in one-time-programmable memory that is not accessible from non-privileged code. Network security is also more mature and easier to implement using Linux network stack and cryptography support, and OP-TEE provides a ready-to-roll secure environment for many parts reviewed here.
安全启动并非在本文涉及的每个应用处理器上都可用, 但它是很常见的. 虽然仍然时不时地披露一些漏洞, 但我的非专家观点是, 这些实现似乎比 Cortex-M 的那些更加健壮: 启动配置数据和密钥存储在一次性可编程内存中, 并且不可以可从非特权代码访问. 使用 Linux 网络堆栈和加密支持, 网络安全也更加成熟且更容易实现, 并且 OP-TEE 为本文提到的许多芯片提供了现成的安全环境.
Filesystems & Databases#
文件系统和数据库
Imagine that you needed to persist some configuration data across reboot cycles. Sure, you can use structs and low-level flash programming code, but if this data needs to be appended to or changed in an arbitrary fashion, your code would start to get ridiculous. That’s why filesystems (and databases) exist. Yes, there are embedded libraries for filesystems, but these are way clunkier and more fragile than the capabilities you can get in Linux with nothing other than ticking a box in menuconfig. And databases? I’m not sure I’ve ever seen an honest attempt to run one on a microcontroller, while there’s a limitless number available on Linux.
想象一下, 你需要在重新启动周期中保留一些配置数据. 当然, 你可以使用结构体和底层闪存代码, 但如果需要以任意方式附加或更改此数据, 你的代码将开始变得荒谬. 这就是文件系统 (和数据库) 存在的原因. 是的, 有文件系统的嵌入式库, 但这些库比你只需在 menuconfig 中勾选一个框即可在 Linux 中获得的功能更加笨拙和脆弱. 还有数据库? 我不确定我是否见过在微控制器上运行数据库的尝试, 但 Linux 上可用的数据库数量是无限的.
Multiple Processes#
多进程
In a bare-metal environment, you are limited to a single application image. As you build out the application, you’ll notice things get kind of clunky if your system has to do a few totally different things simultaneously. If you’re developing for Linux, you can break this functionality into separate processes, where you can develop, debug, and deploy separately as separate binary images.
在裸机环境中, 你仅限于单个应用程序映像. 当你构建应用程序时, 你会发现, 如果你的系统必须同时执行一些完全不同的操作, 那么事情会变得有点笨拙. 如果你正在针对 Linux 进行开发, 则可以将此功能分解为单独的进程, 你可以在其中作为单独的二进制映像单独开发、调试和部署.
The classic example is the separation between the main app and the updater. Here, the main app runs your device’s primary functionality, while a separate background service can run every day to phone home and grab the latest version of the main application binary. These apps do not have to interact at all, and they perform completely different tasks, so it makes sense to split them up into separate processes.
典型的例子是主应用程序和更新程序之间的分离. 在这里, 主应用程序运行设备的主要功能, 而单独的后台服务可以每天运行以获取主应用程序二进制文件的最新版本. 这些应用程序根本不需要交互, 并且它们执行完全不同的任务, 因此将它们分成单独的进程是有意义的.
Language and Library Support#
语言和库支持
Bare-metal MCU development is primarily done in C and C++. Yes, there are interesting projects to run Python, Javascript, C#/.NET, and other languages on bare metal, but they’re usually focused on implementing the core language only; they don’t provide a runtime that is the same as a PC. And [even their language implementation is often incompatible]( http: //docs.micropython.org/en/latest/genrst/index.html). That means your code (and the libraries you use) have to be written specifically for these micro-implementations. As a result, just because you can run MicroPython on an ESP32 doesn’t mean you can drop Flask on it and build up a web application server. By switching to embedded Linux, you can use the same programming languages and software libraries you’d use on your PC.
裸机 MCU 开发主要使用 C 和 C++ 完成. 是的, 有一些有趣的项目可以在裸机上运行Python、Javascript、C#/.NET 和其他语言, 但它们通常只专注于实现核心语言; 它们不提供与 PC 相同的运行环境. 甚至它们的语言实现也常常是不兼容的. 这意味着你的代码 (以及你使用的库) 必须专门为这些微实现编写. 因此, 仅仅因为你可以在 ESP32 上运行 MicroPython 并不意味着你可以在其上放置 Flask 并构建 Web 应用程序服务器. 通过切换到嵌入式 Linux, 你可以使用与 PC 上相同的编程语言和软件库.
Brick-wall isolation from the hardware#
与硬件隔离
Classic bare-metal systems don’t impose any sort of application separation from the hardware. You can throw a random
I2C_SendReceive()
function in anywhere you’d like.
经典的裸机系统不会将任何类型的应用程序与硬件分开. 你可以在任何你想要的地方放置一个随机的 I2C_SendReceive()
函数.
In Linux, there is a hard separation between userspace calls and the underlying hardware driver code. One key advantage of this is how easy it is to move from one hardware platform to another; it’s not uncommon to only have to change a couple of lines of code to specify the new device names when porting your code.
在 Linux 中, 用户空间调用和底层硬件驱动程序代码之间存在硬分离. 这样做的一个关键优势是从一个硬件平台迁移到另一个硬件平台非常容易. 很多情况下移植代码只需更改几行指定新设备名称的代码.
Yes, you can poke GPIO pins, perform I2C transactions, and fire off SPI messages from userspace in Linux, and there are some good reasons to use these tools during diagnosing and debugging. Plus, if you’re implementing a custom I2C peripheral device on a microcontroller, and there’s very little configuration to be done, it may seem silly to write a kernel driver whose only job is to expose a character device that basically passes on whatever data directly to the I2C device you’ve built.
是的, 你可以在 Linux 中插入 GPIO 引脚、执行 I2C 事务并从用户空间触发 SPI 消息, 并且有足够的理由在诊断和调试期间使用这些工具. 另外, 如果你要在微控制器上实现自定义 I2C 外围设备, 并且需要完成的配置很少, 那么相对的编写一个内核驱动程序 (其唯一的工作就是打开一个基本上直接传递任何数据的字符设备) 可能看起来是个有点蠢的操作.
But if you’re interfacing with off-the-shelf displays, accelerometers, IMUs, light sensors, pressure sensors, temperature sensors, ADCs, DACs, and basically anything else you’d toss on an I2C or SPI bus, Linux already has built-in support for this hardware that you can flip on when building your kernel and configure in your DTS file.
但是, 如果你要与现成的显示器、加速度计、IMU、光传感器、压力传感器、温度传感器、ADC、DAC 以及你在 I2C 或 SPI 总线上使用的任何其他设备进行交互, Linux 已经构建了对此硬件的支持, 你可以在构建内核时打开并在 DTS 文件中进行配置.
Developer Availability and Cost#
开发人员的可用性和成本
When you combine all these challenges together, you can see that building out bare-metal C code is challenging (and thus expensive). If you want to be able to staff your shop with lesser-experienced developers who come from web-programming code schools or otherwise have only basic computer science backgrounds, you’ll need an architecture that’s easier to develop on.
当你将所有这些挑战结合在一起时, 你会发现构建裸机 C 代码具有挑战性, 也因此成本高昂. 如果你想只为你的商店配备看网课的或仅具有基本计算机科学背景的经验较少的开发人员, 那么你将需要一个更易于开发的架构.
This is especially true when the majority of the project is hardware-agnostic application code, and only a minor part of the project is low-level hardware interfacing.
当项目的大部分是与硬件无关的应用程序代码, 并且项目的一小部分是低级硬件接口时, 尤其如此.
Why shouldn’t you Linux?#
为什么不应该使用Linux?
There are lots of good reasons not to build your embedded system around Linux:
有很多充分的理由选择不使用嵌入式 Linux 系统:
Sleep-mode power consumption#
睡眠模式功耗
First, the good news: active mode power consumption of application processors is quite good when compared to microcontrollers. These parts tend to be built on smaller process nodes, so you get more megahertz for your ampere than the larger processes used for Cortex-M devices. Unfortunately, embedded Linux devices have a battery life that’s measured in hours or days, not months or years.
首先, 好消息是: 与微控制器相比, 应用处理器的激活时的功耗相当不错. 这些芯片往往使用较小的制程生产, 因此与 Cortex-M 设备所使用的较大制程相比, 你可以获得更多兆赫的电流. 不幸的是, 嵌入式 Linux 设备的电池寿命以小时或天为单位, 而不是以月或年为单位.
Modern low-power microcontrollers have a sleep-mode current consumption in the order of 1 μA — and that figure includes SRAM retention and usually even a low-power RTC oscillator running. Low-duty-cycle applications (like a sensor that logs a data point every hour) can run off a watch battery for a decade.
现代低功耗微控制器的睡眠模式电流消耗约为 1 μA, 该数字包括 SRAM 数据保留, 通常甚至包括运行的低功耗 RTC 振荡器. 低占空比应用 (例如每小时记录一个数据点的传感器) 可以用手表电池运行十年.
Application processors, however, can use 300 times as much power while asleep (that leaky 40 nm process has to catch up with us eventually! ), but even that pales in comparison to the SDRAM, which can eat through 10 mA (yes mA, not μA) or more in self-refresh mode. Sure, you can suspend-to-flash (hibernate ), but that’s only an option if you don’t need responsive wake-up.
然而, 应用处理器即使在休眠状态下, 也可能消耗 300 倍的功率 (那种漏电的40纳米工艺最终还是会影响到我们! ), 但即使如此, 这与 SDRAM 的功耗相比还是显得微不足道, SDRAM 在自刷新模式下可能会消耗 10mA (没错, 是 mA, 而不是 μA) 甚至更多. 当然, 你可以选择挂起到闪存 (休眠), 但这只在你不需要快速唤醒时才是一个可行的选项.
Even companies like Apple can’t get around these fundamental limitations: compare the 18-hour battery life of the Apple Watch (which uses an application processor) to the 10-day life of the Pebble (which uses an STM32 microcontroller with a battery half the size of the Apple Watch).
即使像 Apple 这样的公司也无法摆脱这些基本限制: 比较一下 Apple Watch (使用应用处理器) 的 18 小时电池续航时间和 Pebble (使用带有一半电池的 STM32 微控制器) 的 10 天续航时间就知道了.
Boot time#
开机时间
Embedded Linux systems can take several seconds to boot up, which is orders of magnitude longer than a microcontroller’s start-up time. Alright, to be fair, this is a bit of an apples-to-oranges comparison: if you were to start initializing tons of external peripherals, mount a filesystem, and initialize a large application in an RTOS on a microcontroller, it could take several seconds to boot up as well. While boot time is a culmination of tons of different components that can all be tweaked and tuned, the fundamental limit is caused by application processors’ inability to execute code from external flash memory; they must copy it into RAM first ((unless you’re running an XIP kernel)).
嵌入式 Linux 系统可能需要几秒钟的时间才能启动, 这比微控制器的启动时间长几个数量级. 好吧, 公平地说, 如果你要开始初始化大量外围设备, 安装文件系统, 并在微控制器上的 RTOS 中初始化大型应用程序, 可能也需要个几秒启动. 以及. 虽然启动时间是大量不同组件的集合, 这些组件都可以调整和调优, 但根本的限制是应用处理器无法从外部闪存执行代码造成的; 他们必须首先将其复制到 RAM 中 ((除非你运行的是 XIP 内核)).
Responsiveness#
实时响应
By default, Linux’s scheduler and resource system are full of unbounded latencies that under weird and improbable scenarios may take a long time to resolve (or may actually never resolve). Have you ever seen your mouse lock up for 3 seconds randomly? There you go. If you’re building a ventilator with Linux, think carefully about that. To combat this, there’s been a PREEMPT_RT patch for some time that turns Linux into a real-time operating system with a scheduler that can basically preempt anything to make sure a hard-real-time task gets a chance to run.
默认情况下, Linux 的调度器和资源管理系统充满了无法控制的延迟, 在某些奇怪和不太可能的情况下可能需要很长时间才能解决 (甚至可能根本无法解决). 你有没有遇到过鼠标突然卡住三秒钟? 这就是一个例子. 如果你正在使用 Linux 构建一个呼吸机, 务必认真考虑这一点. 为解决这个问题, 有一个 PREEMPT_RT 补丁, 已经存在了一段时间, 它将 Linux 变成一个真正的实时操作系统, 其调度器可以几乎中断所有任务, 以确保硬实时任务有机会运行.
Also, when many people think they need a hard-real-time kernel, they really just want their code to be low-jitter. Coming from Microcontrollerland, it feels like a 1000 MHz processor should be able to bit-bang something like a 50 kHz square wave consistently, but you would be wrong. The Linux scheduler is going to give you something on the order of ±10 µs of jitter for interrupts, not the ±10 ns jitter you’re used to on microcontrollers. This can be remedied too, though: while Linux gobbles up all the normal ARM interrupt vectors, it doesn’t touch FIQ, so you can write custom FIQ handlers that execute completely outside of kernel space.
另外, 当很多人认为他们需要一个硬实时内核时, 他们实际上只是希望他们的代码具有低抖动. 从微控制器的世界来看, 似乎一个 1000 MHz 的处理器应该能够稳定地生成像 50 kHz 方波那样的波形, 但你错了. Linux 调度器会给你大约 ±10 µs 的中断抖动, 而不是你在微控制器上习惯的 ±10 ns 的抖动. 不过, 这也可以解决: 虽然 Linux 会占用所有常规的 ARM 中断向量, 但它不会涉及 FIQ (快速中断 ), 因此你可以编写自定义 FIQ 处理程序, 完全在内核空间之外执行.
Honestly, in practice, it’s much more common to just delegate these tasks to a separate microcontroller. Some of the parts reviewed here even include a built-in microcontroller co-processor designed for controls-oriented tasks, and it’s also pretty common to just solder down a $1 microcontroller and talk to it over SPI or I2C.
老实说, 在实践中, 将这些任务委托给单独的微控制器更为常见. 本文讲解的一些器件甚至包括一个专为面向控制的任务而设计的内置微控制器协处理器, 而且焊接一个 1 美元的微控制器并通过 SPI 或 I2C 与其通信也是很常见的.