EnditYYs 2019-04-16
你不知道的入式程序员!
作者 | Per Knytt
译者 | 弯月
责编 | 屠敏
出品 | CSDN(ID:CSDNnews)
以下为译文:
寄存器!示波器!胡子! 串口! C! 循环剃须!胡子!中断!汇编!等等,我刚刚提到胡子了吗?
如果我提到“嵌入式程序员”一词,我们行业中的大多数人都会立刻想起一个英雄角色的形象。
一个拥有神秘技能的伟大开发人员,有着渊博的神秘学的知识,对个人卫生的概念持半信半疑的态度,面部胡子拉碴,像神一样的形象。
然而,在秘密地研究了几年这个主题之后,我在这里告诉你,你们的想象统统不正确。
大多数嵌入式程序员都没有胡子,做嵌入式程序员也不必连做梦都要使用汇编语言,而且嵌入式程序员也会洗澡。
我还可以告诉你嵌入式编程非常有趣,收获颇丰,且富有挑战性。如果你对本文感兴趣,那么有可能将来你也会从事这一行。
请注意,以下内容可能有点雄心勃勃。我会介绍嵌入式程序应有的样子,而不是大多数嵌入式程序实际的样子。
这可能是一种理想的状况。突然偶遇嵌入式代码,你可能觉得非常吓人,觉得这些代码都是一些聪明的家伙写的高端代码。
但我想要告诉你,你会对Spotify的嵌入式编程感兴趣,而优秀的代码正是我们的奋斗目标。
所以,如果你也是一个嵌入式程序员,那么可能本文中介绍的代码与你每天使用的有出入,我明白你的心情,现实确实如此,但生活可以更加美好。
首先,我们来介绍一下嵌入式系统编程与嵌入式应用程序编程之间的区别。
嵌入式系统编程:可能就是你想象中的嵌入式编程。
包括让嵌入式硬件平台正常运行所涉及的所有工作:编写设备驱动程序,引导加载程序,移植或编写操作系统,各种位操作,还要考虑到时钟周期问题。调试丢失的中断。
一连盯着示波器几个小时,埋怨编译器,讨厌中断控制器,就仿佛它是一个人一样。时常在午夜惊醒,冷汗淋漓,努力说服自己是猫偷走了中断信号的正面。不好意思,我跑题了。嵌入式系统编程确实涉及非常底层的东西。
嵌入式应用程序编程:指的是为资源有限的系统编写应用程序的技术。这种开发更容易,开发环境很好。
还可以在台式计算机上编写、测试和调试代码。虽然有些约束可能很具挑战性,但是你无需考虑汇编、GPIO引脚或DMA描述符。
然而,你必须考虑内存的使用情况,运行环境,代码的大小和可移植性。但说真的,难道有人不喜欢这些工作吗?
本文讨论的是嵌入式应用程序编程,在Spotify我们大部分时间从事的都是这种类型的嵌入式编程。
简约之美
约束让我们的生活变得更加有趣。
与完整的管弦乐队录制的专辑相比,只用两根柱子和一杯水录制的音乐专辑需要更多的创造力。
虽然我对创造力或对“音乐”一词有着非常规的理解。但是,我想表达的是嵌入式编程很有趣,但是有趣在哪儿呢?
大多数嵌入式程序的首要约束因素是大小,代码必须紧凑,常见的编程习惯(代码需要模块化,易于维护和测试,还要经过测试)仍然适用于嵌入式程序,除此之外还需要将代码量降到最低,而且还需要保证自给自足。
用一个概括就是:优雅。良好的嵌入式代码非常优雅。
那么什么是嵌入式编程呢?什么是嵌入式程序员?让我们继续往下看……
内存的使用率——隐藏的杀手
嵌入式程序员需要避开现代内存管理的概念。你几乎不可能实现垃圾收集。一个垃圾收集器可能就耗尽了代码容量的限制。
而且垃圾收集器还需要不时地运行实际的垃圾收集,这会破坏嵌入式程序的实时效果。
你甚至还需要避免常规的malloc(),调用malloc()可能会花费大量时间,因为分配器可能必须对分配区域进行碎片整理,才能释放出足够大的内存块来响应请求。
嵌入式程序员很乐意直接管理内存,编写自定义分配器,甚至通过静态分配的内存块来杜绝内存分配失败的发生。
大多数嵌入式系统和普通计算机之间的一个重大区别就在于内存的组织方式。
主流的台式机和服务器的处理器架构(如Intel x86)使用的编程模型中,代码和数据存储在同一个地址空间中。
这意味着如果你的机器有64MB的RAM(好大啊,太不可思议了!),而你的程序是40MB(令人难以置信!),那么你的数据就只剩下24MB(这么多,根本用不完啊!)。
在家的时候,我们称之为冯·诺伊曼架构,但在酒吧我们就不敢这么说,因为这会引发唇枪舌战。
由于RAM是处理器中功耗最大的一部分,而且由于RAM占用大量芯片面积,因此在许多嵌入式系统使用的模型中,代码和数据分别存储在不同的存储器中。
代码和静态数据存储在ROM(通常是闪存或EEPROM)中;而动态数据存储在RAM中。 ROM比RAM便宜很多,因此通常使用的也较多(通常是5-10倍)。
RAM和ROM可以具有单独的地址空间(哈佛架构),也可以映射到统一的单个地址空间(改良版的哈佛架构,遗憾的是从未被称为小丑架构)。
除非你需要编写引导加载程序或系统内升级的功能(在这些情况下,你需要向代码的存储空间中写入),否则一般你不会注意到这两者之间的差异。
但这确实意味着代码的大小和RAM的使用率需要单独计算。换句话说:在嵌入式系统中,代码大小的限制不同于RAM使用的限制。请记住,通常RAM的使用是最关键的参数。
你所看到的就是一切
嵌入式程序员确实是软件世界的佼佼者。他们喜欢亲手写程序,创建自己的代码库,使用20世纪60年代的语言,使用机械键盘。
很少有是现成的库可以满足嵌入式系统的特殊限制。虽然有很多JSON解析库,但很少有库能够支持大于RAM容量的文档的解析。
嵌入式程序员总是乐于尝试现有的库,因为他们都很懒。但是,如果没有合适的库,那么嵌入式程序员也很乐意重新发明更小、更快的工具。
由于嵌入式程序员非常重视理解和控制代码的执行和资源的使用,因此几乎所有代码都是用C语言编写的。
有时新的编程语言试图入侵嵌入式的世界,结果却会遭到嵌入式程序员的质疑。
这种语言有一些奇特的线程模型吗?请参见如下有关并发的讨论。
这种语言有垃圾收集吗?请参见如下有关性能的讨论。
这种语言的编译器是否支持人类已知的每种计算机体系结构?
请参见如下有关可移植性的讨论。简而言之,衡量标准设定得很高,而且C非常优秀。
嵌入式软件系统往往很好理解。缺乏第三方的代码库和华丽的继承结构,而且还存在严格的代码大小限制,所有这些都保证了代码很小且易于理解。
在编写良好的嵌入式代码中,你在页面上看到的一切就是所有的代码。嵌入式程序员无需通过层层地剥离才能弄清楚代码的实际作用。
这并不是说实际的嵌入式应用程序的逻辑不会非常复杂,但至少我们不需要通过层层叠叠的抽象模式来隐藏这些逻辑。
并发
嵌入式开发人员都不喜欢并发。对于查尔斯·巴贝奇(可编程计算机的发明者,计算机的先驱)来说,一次做一件事就足够了,你也一样。
大多数形式的并发支持都需要不时地保存状态并切换到新任务。
这需要多个堆栈,每个任务一个,还需要很多的RAM,有时多得不是一点半点。现如今,堆栈很容易就占用1KB甚至更大的空间。
嵌入的程序员无法忍受这一点。他们宁愿手工处理合作性的任务切换、非阻塞的I/O、轮询、回调、手动任务调度、主循环。
这些都是嵌入式程序员最常用的东西。如果你不了解这些东西的含义,也不用担心,你可以参考本文末尾推荐的著作。
聪明的读者可能不禁想问:“难道不是所有类型的的任务切换都需要保存任务的状态吗?你岂不是将这些工作从操作系统转嫁到程序员头上了吗?”
嵌入式程序员会回答说:“然也!”,或者说:“将工作从操作系统转嫁到程序员头上,这就是嵌入式编程的工作啊!”
并发是一个难题。但是,抛开先入为主的多线程的概念,并发实际上就简单多了。你知道你的代码不会被中断,因此你很容易掌握执行顺序。
缺点就在于你需要为I / O等编写更多的代码,因为你不能使用阻塞。
前面说过的可怜的嵌入式系统程序员很不走运,对他们而言,硬件中断会打乱所有的工作。
可移植性
如果你的程序不能在人类所有已知的处理器体系结构上运行,那么你的程序就不算嵌入式程序。
大端、小端、基于堆栈的寻址、基于寄存器的寻址、RISC、 CISC、标量、向量、DSP或PIC,这些都无所谓。
代码无论放到哪里都可以正常运行。一个字节包含8比特?没那么快!嵌入式程序员不会做任何假设,他们会质疑所有问题。
如果有一天,客户要求你的程序在有分段只写内存和分支协处理器的基于Chewbacca 5000 12.5位堆栈的矢量CPU上运行,那么嵌入式程序员也可以坦然地告诉他:没问题。
这对你来说意味着什么?疯狂地刷旧版的C标准吧。
易于测试
由于嵌入式程序可以在“设备外”进行多种类型的测试,因此嵌入式程序员可以在编写测试代码的时候,尽情地尝试现代软件开发。
测试代码可以用Node.JS、Python、或C ++编写。我们可以在测试代码中在为堆栈上分配大对象,世上没有比这更享受的罪恶感了。
为硬件开发嵌入式程序时,测试绝对至关重要。你编写的代码最终会出现在永远不会更新的设备中。
有时你的代码会被刻录到ROM芯片中。如果在发布后才发现Bug,那成本可就十分昂贵了。
因此,坚决地贯彻现代测试的原则,对编写易于测试的代码保持热情,以及百分百完成测试覆盖率的决心,是嵌入式程序员非常宝贵的品质。
性能
对于大多数程序员来说,性能是一个简单的问题:“代码的运行速度够快吗?”
如果代码的运行速度比要求还快,则万事大吉。
对于嵌入式程序员来说,情况则略为复杂:“代码的运行速度够快吗?实时性够吗?功耗是否在计划内?”
除了RAM和代码大小的限制外,代码的运行速度还必须够快。
但不用过快。未使用的周期有助于降低功耗。降低功耗意味着电池寿命更长,产生的热量更少,而且在下一次硬件升级中可以使用更便宜,更慢的CPU。
我们总是与底层的硬件人员博弈。良好的嵌入式代码设计可以促进CPU进入睡眠模式,例如利用显式轮询,让系统可以在轮询之间休眠。这也意味着代码不应该因任何原因造成阻塞。
有些嵌入式系统有硬性的实时要求,例如起搏器。在这样的系统中运行的任何线程或任务都必须保证能够在固定的时间段内返回。
如果运行时间过长,则系统可能会完全失效。拿起搏器举个例子,这样的起搏器会被归类为“次品”。毋庸置疑,为心脏起搏器写代码的工作不适合胆小的人。
大多数嵌入式媒体应用程序都有软性的实时要求——如果线程或任务占用CPU的时间超过应有的时间,那么系统性能会被降级,但不会完全失败。
音频或视频可能会出现故障或卡顿。用户界面可能会感觉延迟。这绝对不是好事,但也不是灾难性的。
编写具有良好实时特性的代码非常类似于编写有助于CPU睡眠模式的代码,两者都是利用很短时间片,非常注意循环长度并且永远不会阻塞。了解你的代码路径。不惜一切代价避免递归。
怎样才能成为嵌入式程序员?
有理想的嵌入式程序员都有哪些重要的特质?
你需要对计算机体系结构的基础知识有深入的了解。了解内存的层次结构,吞吐量瓶颈和硬件级别的并发问题。还有一颗强大的心理掌握寻址的工作原理(指针)。
此外,根据你需要使用的软件类型,可能还需要掌握一些行业的技术。例如,在Spotify,你需要对网络有很好的理解。
如果你觉得你对计算机体系结构的基础知识没有深入的了解?
那么请参照下列推荐书籍:
除此之外,好奇心也是嵌入式程序员身上最重要的品质,渴望了解事物原理的冲动以及坚韧,愿意全身心地投入到工作中,不断学习。
原文:https://labs.spotify.com/2019/04/09/an-opinionated-anthropology-of-the-embedded-programmer-its-habits-and-habitat/
本文为CSDN翻译,转载请注明来源出处。