Evan Phoenix 谈 Rubinius:虚拟机内幕面面观

阅读数:114 2007 年 7 月 25 日

话题:Ruby开源DevOps语言 & 开发架构

数周以前,Evan Phoenix加盟EngineYard,在那里他每天的工作中有一半时间花在Rubinius项目上。我们对 Evan 进行了一次采访,了解项目项目的进展和虚拟机的内部情况,并且(在采访的第二部分)探讨了最新的一些特性的实现方式。

其它的 Ruby 实现,比如说 JRuby、XRuby、Gardens Point Ruby.NET、IronRuby,要么针对已有的虚拟机,要么和 Ruby 1.x 一样使用 C 语言编写,而 Rubinius 另辟蹊径,它从 Smalltalk 虚拟机处获得灵感,尤其是在 Squeak Smalltalk 之中Squeak是用它自己 Smalltalk 的一个子集写成,这个子集名叫 Slang,它基本上是使用了 Smalltalk 语法的 C 语言代码,并加入了一定的限制。Rubinius 的目标之一,就是使用这种方式贯穿项目始终,它使用的语言名叫Garnet(以前被称为“Cuby”),目前仍处于开发阶段。Evan 就目前进展解释说:

这仍然是我计划在近期要做的事情,而不是今后的。之前,我们遇到了许多需要处理的问题,而且我们还没有时间回来着手 Garnet(Cuby 的新名字)上的工作。目前我们尚未找到什么特别的问题,但我相信我们会发现的。

第一眼看过去 Garnet 语言和 Ruby 长得没什么区别,但是里面一些东西代表的语义进行了一些修改。

举例来说,在 Garnet 语言中,“d = c.to_ref”这样的代码看似调用了 c 对象的 to_ref 方法,但实际上 Garnet 会把这段代码翻译成“d = &c”这样的 C 代码。我们可以把它看作一个非常先进的 C 预处理器,试图尽可能将自己和对应的 C 语言构造映射起来。我们的想法就是写出看起来像 Ruby 的代码,而行为则和 C 语言类似。

目前,虚拟机的基础部分是使用 C 语言编写的,也采取源自 Smalltalk 的方式。Evan 就此解释了基础实现(primitives)的本意:

他们是可以在 Ruby 中调用的小块 C 语言代码,用来实现 Ruby 无法完成的功能。一个很好的例子是给一个对象分配 (内存)。在系统后端,与垃圾回收机制交互,为特定对象的分配准备出足够的空间。这样的操作在 Ruby 之中无法实现,这种操作方式只存在于系统的最底层, 也就是所谓的基础实现。

Rubinius 的基础实现与 Smalltalk 的基础实现机制很相近。如果方法指定了原生 (primitive) 的操作号,当方法被执行时,则会调用原 生 (primitive) 操作而不是常规的 Ruby 代码。如果 primitive 操作执行失败(primitive 自身报告执行已经失败),那么 Ruby 代码就会作为后备的行为被执行。Ruby 代码可以使用如抛出包含操作失败原因的异常的方式,将参数转换并再次进行尝试,或者做一些其他的操作。

为了展现它实际运行的方式,这里附一段 rubinius 的 primitive 操作代码作为例子:

  def fixnum_size(_ = fixnum)

<<-CODE

stack_push(I2N(sizeof(int)));

CODE

end

接着 Evan 给我们解释了一遍代码的运行过程:

primitives 和指令使用一系列有趣的格式来保持其操作的简单性。所有的操作都是 Ruby 方法,而程序体是 包含 C 代码的字符串。在编译的时候,这些文件被执行,然后某些代码会调用每一个方法,收集 C 的代码并且在其它 C 代码的 #include 部分声明。这样做的 主要原因是基础实现和指令在 C 语言的声明中可以包装为一个大的 switch 表达式,如果使用手工的方式来完成无疑将会是非常痛苦的。

而且,它 提供给我们一些程序预处理的能力。例如,在这里(代码示例),你看见我们在定义参数的原生类型 (primitive)fixnum_size 的时候使用了 一些小的技巧。首先,代码的关键是在调用的方法的代码执行前会自动注册 C 语言代码段“POP(self, FIXNUM_P)”。调用并且输出 C 语言代码的方法将会正确的声明和写入这些代码。用这样的形式,我们可以方便基础实现(primitives)的编 写。

但是我声明,目前并非所有基础实现(primitives)都使用了这样的形式。很快,我们将会进行审查,将所有的实现都统一使用这种形式。

可以看出,随着可用的 Garnet 越来越多之后,Ruby 和 C 语言之间的界限会变得模糊,原有的界限将在今后发生改变。对于更深一层的软件栈来说,标准的类库将使用 Ruby 来实现。

目前,虚拟机的核心(实际上就是操作码)和基础实现(primitives)都是使用 C 语言编写的。基础实现(primitives)是我想在 Garnet 中实现的第一个特性。两者的垃圾回收机制也都同样是使用 C 语言编写(虽然他们所占的的代码量很少)。

除了这些,所有的一切都是用 Ruby 实现的。所有解释器都可以解析命令行参数字符串(就像 rubinius -d -v 这样开启调试和警告信息)。所有代码的运行时环境可以轻松的操作(修改),因为它们也都是 Ruby 的。

Rubinius 虚拟机在过去一段时间内发展很快。为了保持发展的势头并吸引更多的开发者,在更多的开发者开始贡献代码还有更多的测试人员开始加入到 Rubinius 项目中之后,他们需要得到更多项目相关的信息。

目前,IRC 讨论组#rubinius是得到信息的最好渠道(当然,直接读源代码也是非常有效的方式)。讨论组的一些历史记录信息可以在线找到。Evan 详细的阐述了增加项目透明度的计划:
我们尽力去保持整个过程的透明度。目前,项目的 IRC 讨论组是最主要的交流方式,并且我们设法将 IRC 的记录整合到站点 http://rubini.us 之上。同时我们鼓励用户使用我们在http://rubini.us上的论坛来反馈问题。论坛拥有 RSS 的 feed 读取方式,并且大多数的开发者可以通过订阅 RSS 来加入讨论(尽管我不得不承认,有些功能还有待真正实现)。

关于如何使得项目更加透明,通过各种方式提出的建议,我都非常欢迎。我们可以一直考虑满足用户提出的规格。一旦我们拥有一个完全规范的套件,其余的功能将很容易的添加在其中。

感兴趣的读者可以继续关注这段采访的下一个章节,下个章节探讨了 Rubinius 调试器、垃圾回收、ObjectSpace 和线程方面的一些最新特性的实现方式。

查看英文原文:Evan Phoenix on Rubinius - VM Internals Interview