第1章 迈入面向嵌入式的时代

以“面向嵌入式”为卖点,mruby 应运而生。我身为 Ruby 语言的设计者和 mruby 的主要开发者,将在这里亲自向大家介绍 mruby。在第1章,我们将介绍 mruby 的开发背景、设计方针、以及实际应用案例。
—— 松本行弘

我从1993年开始开发 Ruby,到现在Ruby 的普及程度已经远远超出我当初的预想。特别是2004年 Ruby on Rails 问世以后,Ruby 的人气迅速蹿升,迄今已经名副其实地风靡全球了。使用 Ruby 搭建的 Web 网站形形色色,规模大小各异。以日本为例,Ruby 的使用范围也是极其广泛,并囊括了 Cookpad1 和“食べログ2”等知名网站。

1. Cookpad 是日本规模最大的食谱社区型网站。收录了150万种以上的食谱,用户数量超过2000万。 ——译者注

2. “食べログ”是日本最大的餐馆消费指南网站,类似国内的“大众点评网”。 ——译者注

Ruby 的现状

近几年,美国的西海岸爆发了创业风潮。我在美国的时候,拜访过一些共用工作空间(Coworking Space),里面聚集的都是年轻的创业家们,在那里不断听到有人说“我也在使用 Ruby”。虽然没有经过正式调查,但我感觉在硅谷的 Web 方向创业公司中,有超过半数都在使用 Ruby 做开发。

编程语言人气排行榜“TIOBE index”是根据网络上各编程语言的热门程度排名的榜单,Ruby 现在排在第10位(2013年5月)。因此说 Ruby 在数量繁多的编程语言中跻身前列,也不为过吧。

我实在无法想象全世界有多少 Ruby 的用户。由于 Ruby 是开源软件,可以自由地使用或复制,因此无法统计用户数量。但我想恐怕有数十万,或者会不会甚至有数百万 Ruby 用户呢?

我最初开始开发 Ruby,仅仅是因为想按照自己的想法来设计语言并使用它而已,当时只是当成自己的兴趣爱好去做。然而,Ruby 能够被那么多的人接受,能够有那么多的人在真正地使用它,这是我在1993年刚开始开发的时候无法想象的。

我和我的小伙伴们都惊呆了。

摩尔定律的推动

Ruby 能够达到今天的普及程度的原因之一,必然要数摩尔定律3带来的计算机性能的提升。现在来看1993年的计算机的配置虽然寒碜,但同之前相比已经有了显著的进步。当时出现了 Linux 等运行在个人电脑(PC)上的 UNIX 操作系统。能够在自己的个人计算机上运行 UNIX,对当时的我真是不小的震撼。而这也成了我开发 Ruby 这样的语言的契机。

3. 摩尔定律:大规模集成电路(LSI)的集成度每两年翻一倍。与此同时,计算机的性能也按照大致相同的速度,以指数关系飞速增长。

从那以后,计算机也没有停下性能提升的脚步。在1993年,以 Ruby 的性能只能勉强用于小规模的个人项目。而现在,Ruby 已经能够胜任规模庞大并且对性能有一定要求的领域。

当软件的复杂度达到一定程度后,比起性能,软件开发的生产效率更容易成为症结。尽管计算机的性能一直随着时代发展在不断提升,但人类的生产效率却无法如此轻易地提升,因此可以说后者成为了瓶颈。而 Ruby 的长处在于易用性和灵活性,因此可以认为 Ruby 已经迎来能够大展拳脚的时代了。

好了,自卖自夸就到此为止吧。

用高级语言开发嵌入式软件

即使Ruby已经称霸(真的吗?) Web 领域,但软件开发是个广阔的世界,还有很多 Ruby 完全无法涉足的领域。比如,Ruby 在学术研究编程领域的普及寸步难行,无法与 Python、R 或 MATLAB 等语言匹敌。另外,在“嵌入式软件”这个外界公认是日本擅长的领域,可以说几乎看不到 Ruby 的身影。

但是,Ruby 的高生产效率这一特性如果真的对软件开发有效的话,那么在这些领域也应该能够有用武之地啊。

这里我们主要来看一下“嵌入式”。光说“嵌入式软件”的话好像有多种解释,此处我们将它定义为“包括硬件在内的整个系统中,软件所占的那部分”,也就是构成家电或控制机器这样的“系统”中的软件。外界普遍认为,日本在这类传统制造业的实力不俗。与之对应,“嵌入式软件”也有着悠久的传统,同样盛产技术人员。

摩尔定律同样影响嵌入式领域

摩尔定律同样也在给嵌入式软件领域带来 IT 技术的革新。

首当其冲的是嵌入式软件平台的 CPU 性能、内存及其他容量的增大。用同样的价钱能够买到与十年前的 PC 性能相当的平台,已经不足为奇了吧。

与计算能力的提升相对应,操作系统(OS)也不再是以前那种位于硬件上层的类似监视程序(Monitor)的小软件,而是搭载了 Linux 这种“真正的”操作系统。软件开发的形态也随之发生着变化。

平台的变化给软件带来的影响并不限于此。随着计算能力的提升,“系统”中通过软件来实现的部分所占的比重也越来越大。话虽如此,嵌入式软件说到底也不过是依附于硬件的产物。因此经常被置于开发的后期阶段(下游工程),很容易集中爆发开发进度落后等不良状况。由于处于下游工程的地位,本来就有被上游工程的人“俯视”的倾向,再加上计划延期、开发规模扩大、产品的复杂化等变数,软件工程师们经常苦不堪言。

要解决这个问题,果然还是只能靠活用摩尔定律。利用取得了长足进步的计算机性能来提高软件开发的生产效率,从而克服嵌入式软件开发流程上的缺陷。

在剧烈变化的部分使用高级语言

其中的一个趋势是使用高级语言来开发嵌入式软件。作为使软件开发更有效率的一种试验,采用生产效率高的高级语言,依靠其灵活性来编写嵌入式软件中变化剧烈的部分。最近,一种来自巴西的名为“Lua”的语言受到了大家的关注。

Lua 被广泛用于各种嵌入式软件,如用于扩展路由器的功能,嵌入到游戏程序中实现角色的 AI(人工智能),或用来定义地图等。最近貌似还出现了用 Lua 来开发 iPhone 应用的案例。

Lua 提供了完备的 API 来嵌入其他系统,程序容量相对较小,性能也不错,真是一门相当出色的语言(表1)。但是,它也存在如下不足之处:作为一门语言它的功能匮乏(没有面向对象功能)、程序库匮乏、了解 Lua 的技术人员还很少等。

由此想到,如果能够使用 Ruby 来开发嵌入式软件会如何呢?Ruby 的语言规格多年以来得到了众多软件开发人员的赞赏,类库的易用性也受到了广泛好评。另外,由于 Web 开发的普及,Ruby 程序员的数量很多。如果是Ruby,或许就能够克服 Lua 的不足。嵌入式软件领域应该也会有想要用 Ruby 的需求吧。

表1 各语言在用于嵌入方面的比较

语言CRuby(原生 Ruby)mrubyLua
嵌入式 API重点是追加功能有意识地用于嵌入有意识地用于嵌入
性能良好善战优秀
面向对象功能强力强力匮乏
嵌入程序库强力Ruby 的子集匮乏

mruby 的诞生背景

遗憾的是,现实并非那么一帆风顺。虽然存在多种 Ruby 的实现方式,但用于嵌入式软件的话都存在一定的问题,因为最初在设计 Ruby 的时候没有考虑过嵌入,而 Lua 从一开始就有意识地向易于被嵌入的方向设计。相比之下,拿当前的 Ruby 来说,预想的架构是“使用 Ruby 来开发应用程序。如果遇到缺失的功能,就使用 C 语言开发扩展程序库,然后添加到 Ruby 中”。换言之,以从属关系来看,是 Ruby 为主,C 为辅的关系。

然而,这与嵌入式软件中常见的“使用 C/C++ 开发程序,而仅把需要灵活性或生产效率的那部分交给 Ruby”的架构不符。应用程序中语言的从属关系正好相反。

虽然存在运行在 JVM(JAVA 虚拟机)上面的 JRuby,和使用 C++ 来开发内核的 Rubinius 等多种 Ruby 的实现方式,但没有一种实现在设计时考虑过上述问题。

Ruby 这么出色的语言,如果是由于内部实现的不支持而无法扩张到新的领域,便眼睁睁地错过嵌入式软件的广阔世界,真是太可惜了。

这种想法促使我开始开发 mruby。mruby 的 m 是“embed(嵌入)”的“m”,同时也是 minimalistic(极简的)的“m”。

在福冈县的支持下成为现实

面向嵌入式软件的 Ruby,最初是为了申报2009年内阁府的“尖端科学研究”援助项目而构思的点子。当时的构想是,在 Ruby 还未涉足的领域中,选择了 Ruby 最有希望进入的嵌入式和云计算这两个领域,希望能够解决技术性的难题。但是政府换届导致预算规模缩小,最终没有被采纳。

但是这个想法后来得到了福冈县政府的支持,这一次集中于嵌入式领域,使用全新的构思申报了经济产业省4的“平成22年度5地方创新研究开发项目”,作为为期2年的项目成功被采纳。在此基础上,以福冈 CSK 为中心的福冈县内企业、九州工业大学、还有我所在的网络应用通信研究所三方携手并进,启动了这个项目。

4. 经济产业省是日本行政机关之一,以提高民间经济活力、对外经济关系顺利发展为中心,发展日本的经济与产业,并确保矿物资源及能源之稳定且高效率的供应。 ——摘自维基百科

5. 日本的年度以4月1日开始。平成22年度即公元2010年4月1日至公元2011年3月31日。 ——译者注

福冈县内制造业和其工厂众多,因此县内擅长嵌入式软件的企业也很多。似乎从这一点上判断,这个项目和“地方创新”联系到了一起。

虽然我自己也认为“嵌入式领域是 Ruby 在未来战略上的重要一步”,但是从零开始构建 Ruby 的另一种实现需要相当大的决心。因此,在此之前我迟迟无法踏出第一步。如果当初这个项目没有被采纳,mruby 一定也就无法诞生了吧。

顺便说一下,被“地方创新研究开发项目”采纳的项目名称叫“轻量 Ruby”。它包括了开发面向嵌入式的 Ruby 语言实现,和开发利用 FPGA(Field Programmable Gate Array,现场可编程逻辑门阵列)的并带有 Ruby 执行优化功能的 CPU,以及相应的开发板(Reference Board)。mruby 是其中 Ruby 语言实现部分的软件名称。其中 CPU 那部分被称为“Ruby 芯片(Ruby Chip)”。

我在这个项目中主要负责开发 mruby 的核心部分。本书也将围绕着轻量 Ruby 项目成果中的 mruby 进行讲解。

mruby 的设计方针

作为面向嵌入式软件的 Ruby,mruby 的设计方针如下所示,它有意识地参考了 Lua 这门公认的面向嵌入式的语言。

  • 尊重 JIS/ISO
  • 嵌入式 API
  • 移植性
  • 节约内存
  • 软实时性

下面我们就依次来看看这些设计方针的原则吧。

● 尊重 JIS/ISO

在开发 mruby 的同时,我正好在制定 Ruby 的 JIS 标准6。之前我就对嵌入式领域非常重视标准规格有所耳闻,因此在设计 mruby 的规格时,对 JIS 标准十分重视。

6. 日本工业规格(Japanese Industrial Standards,缩写JIS)由日本工业标准调查会(JISC)组织制定和审议。JIS 是日本国家级标准中最重要、最权威的标准。 ——摘自维基百科

mruby 基本的语言功能及语法当然遵循了 JIS 标准。其他由于嵌入式的制约而无法提供全套的类库时,也很贴心地筛选出了具体的类和方法。

然而,JIS 标准中包含的文件输入输出等内容,在嵌入式环境中未必存在。很遗憾,这些内容在核心部分中无法实现,因此不能说完全遵循了 JIS 标准规格。“尊重”这个词,也表达了我的无奈。

另外,我在进行轻量 Ruby 项目时,于2011年3月制定了 Ruby 的 JIS 标准——JIS X3017。1年以后,又制定了 ISO 标准——ISO/IEC 30170。

虽然 Ruby 标准规格的未来并未确定,但我现在有一个设想是利用开发 mruby 的经验教训,来制定一个类似于“嵌入式标准文档”的东西。我正在考虑要让 mruby 完全遵循此标准。

● 嵌入式 API

原生 Ruby(CRuby)中,C API 的易用度广为人知。但说到底这种架构是这样的:1个进程只有单独1个虚拟机7,然后向这个虚拟机中添加功能。CRuby 并没有考虑到下面这些嵌入式软件中的常见用法:1个进程中配有多个虚拟机,或者将虚拟机嵌入到应用程序中来调用 Ruby 写的程序。

7. 这里指的是 Ruby 虚拟机,也称 Ruby 解释器。 ——译者注

编程语言要用于嵌入式,最好一开始就考虑到这些情况并提供相应的 API。

因此,mruby 中提供了遵循下列方针的 API。

  • 将需要的数据保存到结构体(struct)
  • 与系统无关

根据“将需要的数据保存到结构体”这一方针,虚拟机在执行过程中所需的信息都保存在名为 mrb_state 的结构体中。mruby API 中的大部分函数都将此结构体的指针作为第1个参数。这样一来,就能够实现在1个进程中生成多个虚拟机,并将它们分配给各个线程(Thread)或任务(Task)。因此,mruby 中规定了“不使用全局变量”(global variable)。

想要嵌入 mruby 的应用程序,说到底也只是想要利用 Ruby,而并非为了 Ruby 去开发应用程序。因此,必须要做到在开发应用程序中与 Ruby 无关的部分时,不需要顾虑 mruby。“与系统无关”的意思是,不对嵌入 mruby 的程序有什么特殊的要求。比如,不会为了使用 mruby 而必须更换内存管理或线程管理的函数等。

在这一点上,我们给 mruby 设计了极其普通的程序库。而 CRuby 由于没有充分地考虑到这一点,因此不适合嵌入式软件。

● 移植性

嵌入式软件运行在各种各样的平台上,会遇到各式各样的 CPU 和形形色色的操作系统。

为了适配如此多样化的环境,mruby 在实现上尽可能地考虑了可移植性。具体表现为,mruby 在实现上只采用了C99(1999年制定的 C 语言国际标准),而且为了能够运行在没有文件系统的平台上,mruby 能够剥离文件输入输出这部分功能。

另外我们还收到了来自游戏开发者的请求:“游戏业界广泛使用 Visual C,而 Visual C 没有遵循 C99。”考虑到这一点,mruby 也没有使用 C99 中 Visual C 没有遵循的那部分功能。

多亏了这些努力,mruby 能够运行在

  • Windows
  • Mac OS X
  • Linux
  • VxWorks(实时操作系统)
  • 无操作系统

等各种各样的平台上。

即使像 BeagleBoard7 这样的超小型计算机也能够运行 mruby,真是让人感动。不过想一想,BeagleBoard 配有 1GHz 的 ARM 处理器,都可以运行 Ubuntu 了,所以能运行 mruby 也是理所当然啊。摩尔定律真是太不可思议了。

7. 德州仪器公司推出的低功率开源单板计算机。 ——译者注

还有一个比较特别的例子是,将乐高积木与计算机相结合的LEGO Mindstorm机器人也能够使用 mruby 来控制。其实,LEGO Mindstorm 只有 224K 的 ROM 和 64K 的 RAM,使用标准的 mruby 的话容量是不够的。虽然如果将 mruby 精简到只剩虚拟机,就可以把程序的大小缩减到 100K 出头,但问题是 RAM 的容量远远不够。然而,有报告称通过改造 mruby 而能够使其成功运行在 LEGO Mindstorm 上8。我正在考虑近期内将这个成果加入到 mruby 中。

8. http://www.csk.com/fukuoka/services/mruby/nxt01.html

● 节约内存

从上述的 LEGO Mindstorm 的例子中也能看到,有些情况下,嵌入式软件无法获得充足的内存资源。

在严峻的环境中也照样能够运行,这就是 mruby 节约内存的目标。虽然截至目前,在执行过程中的节约内存策略还有很大的提升空间,但我们已经在缩减程序容量方面下了一番功夫。

其中一点就是,可以通过设定将程序的一部分剥离。mruby 能够去除把 Ruby 代码编译成虚拟机能解释和执行的中间语言的这部分。

举例来说,我们在开发阶段需要链接编译器和中间代码生成器,而当开发结束并正式投入使用之前,会事先将 Ruby 程序转换为中间语言。这样一来,即使不链接编译器和中间代码生成器,程序也照样能够运行。去掉这两项,能够使程序的大小减少 100K 左右。现在超过 1M 的程序已经屡见不鲜,所以可能有人觉得 100K 没什么大不了。但是,有时就差这 100K 的案例也确实是存在的。

还有一项举措是,把一些不常用的类移除出了类库。mruby 把并非核心中必需的功能都放到了名为“mrbgem”的模块中。编译时可以自由指定哪些要使用,而哪些用不到。

截至2013年5月,mruby 的代码仓库中包含了19个 mrbgem,而除此之外还有许多的 mrbgem 在项目外部流传。

虽然经过这么一番精简以后,程序的容量也只减少了 50K 左右,但正如上面所说,在那些无论如何都想要减少容量的情况下,哪怕再微小的容量也会显得很关键。

另外,编译时通过设定选项将浮点数由双精度(double)更改为单精度(float),也能够减少一些容量。

而关于运行时的节约内存,我觉得还有很大的改善空间。

● 软实时性

嵌入式软件非常重视实时性。实时性是指,将处理的耗时控制在一定范围内。举例来说,如果机器人的姿势控制程序,或者防抱死刹车系统(ABS)的处理如果没有在规定时间内完成,机器人就可能会摔倒,而刹车系统则可能会导致事故。诸如此类“无论如何都必须要在规定时间内完成”的处理被称为“硬实时性”。

比起处理的性能,实时性更重视可预测性。相比于大多数时候能将处理时间控制在在0.1秒以内,但1000次中就会有1次要耗时10秒的系统,我更倾向于始终能保证在1秒以内完成处理的系统。

然而这样的实时性处理,对于 Ruby 这种拥有 GC(Garbage Collection, 垃圾回收)和复杂运行环境(run-time system)的高级语言来说,实现起来很困难,应该不会有人会用 Ruby 来实现那样的系统吧。Ruby 还是适用于对实时性要求低一些的部分。

话虽如此,mruby也并非对实时性完全没有要求。假设使用 mruby 实现了电子家电的用户界面,如果一个操作需要等待2秒钟,用户肯定会抱怨的。像这种要求不能有太大延迟的弱实时性需求,就叫做“软实时性”,这也是 mruby 的奋斗目标。

Ruby 在实现实时性方面遇到的最大问题,果然还是 GC。因为“垃圾收集”这个与本来正在进行的处理无关的工作被插了进来,导致原来的处理执行被中断。

渐进式 GC

为了减缓由 GC 造成的处理耗时的波动,mruby 采用了名为增量回收(Incremental GC)的算法。

在通常的 GC 中广泛使用的标记-清除(Mark and Sweep)算法,是从变量等根部开始,通过递归地扫描可能被引用的对象,确定“存活”对象(标记),然后将“除了存活对象以外”的对象作为“垃圾”回收(清除)。

“标记”处理所需的时间与“存活”对象的数量成正比,而“清除”的时间与对象的总数量成正比。因此,随着对象数量的增加,GC 的执行时间也越来越长,从而对实时性造成了阻碍。

另一种叫做复制收集的 GC 算法为,递归地复制“存活”对象,然后将残留下来的“死亡对象”回收。这种方法归根结底,中断时间同样与“存活”对象的数量成正比。

而 mruby 所采用的增量回收算法的做法是,将 GC 的处理(标记处理和清除处理)细分成多个部分逐一执行。这样一来即使对象数量增加,GC 处理造成的中断时间也不会变长。

增量回收也不是没有缺点,比如处理被分割引起的性能下降(GC 处理消耗的总时间变长),以及回收对象的时机被延后导致内存消耗量略有上升。前面说过,在嵌入式领域大家是不愿意看到内存消耗量增加的,但这里也是为了实时性而权衡过后做出的妥协。

试试 mruby 吧

mruby 是在GitHub上开发的,可以使用版本管理工具Git来取得 mruby 的源代码。如果使用的是 Linux,只要1个 make 命令就能编译源代码。

$ git clone https://github.com/mruby/mruby.git ← 从 GitHub 取得源代码

Cloning into 'mruby'...
remote: Counting objects: 15321, done.
remote: Compressing objects: 100% (7701/7701), done.
remote: Total 15321 (delta 9400), reused 13178 (delta 7361) 
Receiving objects: 100% (15321/15321), 3.84 MiB | 99 KiB/s, done. 
Resolving deltas: 100% (9400/9400), done.
$ cd mruby ← 移动至 mruby 目录
$ make
ruby ./minirake
(in /tmp/foobar/mruby)
CC tools/mrbc/mrbc.c -> build/host/tools/mrbc/mrbc.o 
....以下略....
$ bin/mirb ← 执行 mirb(交互式 mruby)
mirb - Embeddable Interactive Ruby Shell

This is a very early version, please test and report errors.
Thanks :)

> print "hello world\n"
hello world
 => nil 
> quit
$

取得并执行 mruby

编译完 mruby 后,bin 目录下就有了 mruby、mrbc 和 mirb 3个命令。mruby 是 Ruby 解释器(interpreter),mrbc 是将 Ruby 转换为中间语言的编译器,而 mirb 是交互式 Ruby 解释器。

mruby 的实际应用

我觉得 mruby 的应用领域是相当广泛的。在轻量 Ruby 项目的实践性试验中,已经将 mruby 适用于以下项目。

● 太阳能发电管理系统

这是东芝信息系统的开发案例。太阳能发电管理系统有内部的 Web 服务器,通过 PC 等设备的浏览器来查看当前的发电量等信息。最近,支持地面数字电视技术的电视机中有些内置了浏览器,这也是我们考虑的范围。

当前的这套商品使用搭载了 UNIX 的计算机来提供服务。如果能够换成使用搭载 mruby 的小型计算机实现,就会在耗电量和价格方面有所期待。虽然 mruby 核心中缺少 Web 服务器所需的套接字(Socket)等功能,但通过自己动手实现,现在已经成功使项目的原型(Prototype)运行起来了。

● 搭载 mruby 的路由器

这是 Internet Initiative Japan(IIJ)公司的开发案例。通过在 IIJ 的路由器中搭载 mruby实现可通过程序扩展的路由器。这款路由器运行的是 PC-UNIX,搭载 CRuby 也绰绰有余,但由于闪存的容量不够,所以才试着搭载更为轻量的 mruby。

作为演示案例介绍的路由器在通过 USB 与电表连接后,能够将当前的电量发布到 Twitter 上。访问 Twitter 所需的类,包括对 OAuth 的支持等内容都是在 IIJ 内部开发的。

● 自动售货机的内置打印机

这是富士电机(杭州)公司的开发案例。富士电机在日本的自动售货机市场份额独占鳌头, 富士电机(杭州)是它在中国的软件子公司。该公司很早就尝试过先使用 Ruby 编写用于自动售货机的软件原型,等到规格确定之后,再将代码转换为 C 语言并搭载到售货机上。mruby 问世后,就可以直接使用 Ruby 来开发售货机软件了。自动售货机里面内置了用于打印销售额等内容的打印机,这次的尝试就是使用 mruby 来实现控制该打印机的部分。

以上介绍的都是轻量 Ruby 项目范围内的开发内容,而2012年4月底 mruby 的源代码公开后,涌现出了很多有志之士们开发的丰富多彩的应用案例。

有人尝试开发嵌入 mruby 的 Apache 或 nginx 模块;有人尝试开发能够转换成 Objective-C 的框架(MobiRuby),以便用 mruby 写 iOS程序;有人尝试将 mruby 运行在 Android 上;有人实现了让 mruby 能够使用 node.js 背后的 libuv,以实现异步 I/O;有人尝试在搭载了 MIPS CPU 的单板机上运行 mruby……我们已经陆续看到了各种各样的报告。

mruby 在未来的可能性相当值得期待。

下章预告

本章介绍了 mruby 的背景等内容。从下一章开始,我想谈谈 mruby 的技术详情,以及在应用程序中嵌入 mruby 的步骤。

目录