您在下一项目编程时所使用的最佳语言是什么? 如果您是一名嵌入式系统设计人员,可能认为这一问题实在可笑。您会使用C——或者如果您希望加强管理,C衍生为C++。可能会有一些关键代码片段是以汇编语言编写的。但是,据Barr集团最近的一项研究,目前95%的嵌入式系统代码是采用C或者C++编写的。
然而,世界是在变化的。新的程序员、新挑战以及新体系结构让C松开了在嵌入式软件上的“抓手”——有人会说是C僵冷麻木的抓手。据最近的一项研究,嵌入式计算发展最快的语言是Python,当然还有很多其他参与竞争的语言。这些语言仍然占少数。但是逐渐的,一直坚持使用C/C++的程序员像20年前汇编语言专家那样开始冒险了:采用更快、更紧凑、更可靠的程序。那么,为什么要改变呢?
是什么促使一名经验丰富的程序员进行改变呢? 哪些语言在嵌入式系统中毫无疑问会最重要呢? 最重要的是,新的多语言环境中会遇到哪些问题? 图1提出了一个。让我们更深入的了解一下。
迁移潮
一个主要推动力量是来自其他工作环境的程序员进入了嵌入式领域。这方面最明显的是刚毕业的学生进入职场。刚毕业的学生以前在C课程中学会了编程,会在C或者C++中完成他的大部分项目。已经不再如此了。Intel软件工程经理David Stewart注意到,“现在,大部分计算机科学课程使用Python作为他们的入门语言。”计算机科学专业毕业的学生很有可能在Python、Ruby,以及几种脚本语言上有丰富的经验,但是从未认真的使用过C。
其他影响也越来越多。使用Android作为平台进行相关的或者用户友好的嵌入式设计,为Android的自然语言——Java,带来了机会。在这一复杂景象的另一端,机器人、无人机或者相似的小项目业余爱好开发人员通常都有Arduino或者Raspberry-Pi背景。他们的经验会体现在非常紧凑、简单的程序发生器环境里,或者B#等小型语言。
对物联网(IoT)的广泛研讨也产生了影响,网络开发人员也参与到讨论中。他们提出疑问,如果嵌入式系统的外部接口是RESTful Web,编程语言难道不应是JavaScript,或者其服务器侧相关的Node.js? 在窃笑之前,热衷于C的人应观察一下node.js——在企业规模的开发中,贝宝和沃尔玛等公司大量使用了这一可扩展平台,据跟踪调查网站modulecounts.com,在任何编程语言中,该平台的辅助支持系统增长是最快的。
选择Node.js的动机部分是源自文化,但是也有体系结构方面的因素。IoT会在客户侧、互联网,以及服务器侧之间分配其嵌入式系统任务,其中客户侧连接实际环境,通常要求尽可能少的采用硬件。客户侧很自然的采用硬件专用库支持的Web应用程序,而服务器侧则是服务器应用程序。这样,对于Web程序员,IoT系统很显然会使用JavaScript和Node.js。
日益复杂的嵌入式算法是变革的另一推动力量。简单的控制环路被卡尔曼滤波器、神经网络,以及基于模型的控制功能所替代,高性能计算语言——还是Python,当然也有开放计算语言(OpenCL™)等语言,以及MATLAB等基于模型的环境,这些语言逐渐站稳了脚跟。
强烈的动机
那么这些新人们为什么不静下心来学习C呢? Stewart说:“真正的动机在于开发人员的效能。”反对C的人一直质疑这种语言编写慢,容易出错,会带来意想不到的硬件相关问题,除了最初的程序员,其他人很难读懂它。所有这些因素都不会提高效能,反而对提高效能和设计重用产生不利影响。
作为对比,很多最近出现的语言逐步转向快速学习和高效的代码重用。虽然目前几乎所有语言都至少受益于C高度简洁的语法,但现在的重点已经转回可读性,而不是最少字符数。不但在现代语言中鼓励实现协调一致的文档,而且结构化约定也通常会这样定义文档——C程序员一直把这类文档看成是自由表述的例子,作者自己不会牵连其中。例如,这些约定使得实用工具程序能够从Python组件的结构化注释中生成用户手册。
现代语言还采用了高级数据结构。当然可以在C++中建立您需要的任何对象,并重用它——如果您能够记住采用指针完成的一些聪明的工作,Python还是提供了自然List和Dictionary数据类型。Ruby等其他语言,基本上是面向对象的,支持结构和重用,使其融入到程序员的习惯中。
其他两项重要的因素影响了现代语言的是否易于重用。一项是动态输入,这是很有争议的因素。当您使用变量时,解释程序确定您传送给表达式的数值的当前数据类型——几乎所有这些服务器侧语言都是解释性的,而不是编译的。然后,解释程序会选择相应的操作,评估含有该数据类型的表达式。这样,程序员不用太过担心他要调用的函数需要整数还是实数变量。但是,嵌入式程序和代码可靠性专家很快指出,动态输入在运行时实际上效率不高,会导致非常古怪的后果——有意的还是无意的。
另一因素是对模块化的偏见。有时候会有人说,Python编程实际上不是编程,而是脚本:把别人用C编写的函数调用串起来。
这些因素——可读性、协调一致的文档、动态输入,以及函数大量的重用等,催化了开源领域中辅助支持系统的爆发。程序员本能的会在巨大的开源库中找到他们能够使用的函数,例如,npm (用于Node.js)、PyPI (用于Python),或者Rubygems.org (用于Ruby)。如果他们不用修改组件或者编写新的组件,他们会回到库中工作。结果,库会越来越大:npm目前大概有25万个组件。这些大规模的辅助支持系统也相应的大幅度提高了程序员的效能。
缺点
尽管有这么多的优点,但也有缺点。要在嵌入式计算领域占据一席之地的新语言有很多不足。至于为什么还没有席卷业界,是有很多充分的原因的。
大部分这些语言最明显的问题是,它们是解释性的,而不是编译的。这意味着可观的运行时程序包,包括解释程序本身、其工作存储、动态输入开销、运行时库,等等,都要适配到嵌入式系统中。基本上,所有这些会非常紧凑:某些Java虚拟机可以装入到几十K字节中。但是Node.js、Python,以及来自服务器侧相似的语言需要自己的空间。一个兼容性还可以的Python虚拟机在加入您自己的代码之前就有可能占用了几M字节。
还有性能问题。解释程序读取每一行代码——源程序或者预处理过的中间级代码,解析它,进行运行时检查,调用执行所需操作的例程。在C中,这会导致一行代码有很多操作,编译成很多机器语言指令。执行时间和能耗的成本都会增加。
而运行时效率并不是太大的障碍。对此进行改进的一种方法是使用即时(JiT)编译器。正如其名称所暗示的,一个JiT编译器与解释程序并行工作,为循环中的代码生成编译后的机器指令,因此,后续的代码能够更快的执行。Stewart说:“JiT技术非常有趣。PyPy JiT编译器把Python执行速度提高了两倍。”
此外,程序调用的很多函数最初都是采用C编写的,通过外部函数接口来调用。使用较多的函数能够以编译后C的速度运行,只是因为它是编译后的C代码。
还研究了其他方法。例如,如果函数是无阻塞的,或者使用了信令机制,有很多函数调用的程序即使是在采用循环展开等方法来建立更多的线程之前,也可以含有很多线程。因此,可以在一个组件上应用很多多线程内核——这是高性能计算已经研究应用的方向。更进一步,Ruby语言本身支持多线程,因此,即使底层操作系统不支持线程,它也能够产生线程代码。某些团队则寻求在硬件加速器中实现库或者组件,例如,图形处理单元(GPU)、Xeon Phi,或者FPGA。实际上,解释程序本身就会有适合加速的任务。
服务器侧语言遇到的其他困难是缺少处理实际环境的结构。在服务器环境中,除了网络和存储,没有实时限制,也没有I/O。可以通过几种方式来解决这一问题。
最简单的是,Android环境以硬件几乎无关的抽象方式包封Java代码:具有图形、触摸屏、音频和视频、多个网络,以及物理传感器的虚拟机。对于更强调物理I/O,甚至能够在微控制器上运行的轻型平台,则提供嵌入式Java。
Python等语言需要不同的方法。由于CPython解释程序运行在Linux上,因此,原理上能够运行在有足够速度和物理存储器的任何嵌入式Linux系统中。在这方面已经有了工作基础,通过减小装入时间开销,为物理I/O访问提供功能,使用硬件加速器,进一步适应CPython,运行时系统适应实时约束。最新的一个实例是STM32微控制器的裸金属Micro Python环境。虽然这看起来几乎不可能,但是已经在Node.js下面的JavaScript引擎上有了类似的工作。
安全呈现出更多的问题。很多安全和可靠性标准不鼓励甚至禁止使用未经正式认证或者全面测试的开源代码。这些限制使得不可能重用组件,或者过于复杂,对效能的提高不大。同样等级的详细检查也延伸到虚拟机等开源环境中。在可靠和安全大环境下,CPython等开源平台很容易受到攻击。
最后,考虑到有众多的推动力量促使新语言进入嵌入式世界中,可以预见到会有多语系统,这种系统含有的组件来自多种源语言,每一种选择要使用的关键库,或者方便某类开发人员使用。当然,您可以在不同的CPU内核上放置几种虚拟机,在一个内核管理程序下相统一,然后,通过函数调用,为任务间消息的传送提供约定。但是,最终系统会非常大。
另一种可能是一组语言相关解释程序,为JiT编译器生成公共中间代码(图2)。当然,还能列出其他问题——例如,不同的任务间通信模型、存储器模型,以及调试环境,但总是能够解决的。
在嵌入式系统中,一个运行时环境能够处理各种解析语言。
如果这些最终会到来,那么,一名经验丰富的嵌入式程序员该如何应对? 您可以从Web编程、服务器甚至业余爱好者环境中研究这些语言。您可以在下一项目中,尝试在C++和解析语言中开发组件。这需要学习时间,但是独立并行开发是值得的——可靠性的最佳实践。
您会注意到,目前大部分嵌入式代码仍然是采用C编写的。您已经是一名经验丰富的C程序员了。C产生更好的代码。所有这些说法都是真的。但是,“C”要替代“汇编语言”,这正是20年前老一代程序员所说的,尽管现在他们已经消失殆尽。
虽然如此,历史本身不会重复。不是吗?