YARV是面向对象脚本语言Ruby的一个实现。和普通的Ruby不一样,它的特点是把脚本转换成虚拟机上的bytecode,能高速执行。
最近,Parrot,CLR,JavaVM等基于虚拟机的编程语言好像比较热门。想着能深入了解某一种就好了,于是选择了YARV。另外,我连10行的Ruby都没编过,在学习Yarv的同时,也能顺便学习一下Ruby。
1.资料
现在时点(2006/11/)Yarv的最新版本是0.4.1。为什么没有从cvs下来最新的代码呢?因为手里没有bison(GNU版的YACC),所以就偷点懒了。
可以参考的网站
YARV: Yet Another Ruby VM
Yarv的老家。源代码的下载,和Yarv的架构设计文档等。
YARV Maniacs
Rubyist Magazie的连载文章,介绍Yarv的。Yarv的作者写的,介绍了在源代码上如何实装等的一系列文章。
Ruby源代码完全解说
青木峰郎写的Ruby源代码解读的书。除了Yarv核心以外,其它的Ruby等内容,可以参考这本书
main @ main.c
main函数中初始化完了后,就可是执行代码了。这里看一下这之前的一些流程。先来看一下main函数。Ruby/Yarv就是从这里开始执行的。去掉依存于环境的#ifdef等先不管,简单的main函数就是这个样子:
- int
- main(int argc, char **argv, char **envp)
- {
- {
- RUBY_INIT_STACK ruby_init();
- ruby_options(argc, argv);
- ruby_run();
- }
- return 0;
- }
最初的RUBY_INIT_STACK是GC所需要的,记住stack开始位置的宏。所做的事情主要的就是把stack的开始地址赋给变量rb_gc_stack_start(IA64系统好像是别的变量)。宏定义如下:
- #define RUBY_INIT_STACK \
- VALUE variable_in_this_stack_frame; \
- ruby_init_stack(&variable_in_this_stack_frame);
这样,在stack声明了一个变量variable_in_this_stack_frame,并把把这个变量的地址传给了ruby_init_stack 。 ruby_init_stack中包括对stack的增长方向检查等稍稍复杂的工作,抛开这些和当前讨论有关的内容简单来说如下面这样:
- void ruby_init_stack(VALUE *addr)
- {
- ... 略 ...
- rb_gc_stack_start = addr;
- ... 略 ...
- }
像上面那样。这段代码(变量),可能在之后会被GC不停的访问,今天就不深入研究了。
接着,顺序调用下面3个函数
ruby_init (处理本体和内置模块的初始化)
ruby_options (命令行传过来的参数的解析)
ruby_run (脚本的执行)
好了,接着顺次地进行读解下面的内容
ruby_init @ eval.c
下面是初始化函数ruby_init 的主要部分的摘要:
- void
- ruby_init()
- {
- ... 略 ...
- Init_yarv();
- Init_stack((void *)&state);
- Init_heap();
-
- PUSH_TAG(PROT_NONE);
- if ((state = EXEC_TAG()) == 0) {
- rb_call_inits();
- ruby_prog_init();
- ALLOW_INTS;
- }
- POP_TAG_INIT();
- ... 略 ...
- }
Init_yarv!终于见到YARV了!
在继续之前,先看一下那句后面的if语句。这个if语句对第一次看Ruby源代码的人来说应该像迷一样吧。
- PUSH_TAG(PROT_NONE);
- if ((state = EXEC_TAG()) == 0) {
- ...
- }
- POP_TAG_INIT();
嗯,再看看EXEC_TAG的定义。
- #define TH_EXEC_TAG() \
- (FLUSH_REGISTER_WINDOWS, ruby_setjmp(_th->tag->buf))
-
- #define EXEC_TAG() \
- TH_EXEC_TAG()
ruby_setjmp是setjmp或_setjmp的#define定义。这里好像是调用了setjmp。Setjmp最开始的时候返回0,里面如果有logjmp的话,则返回非0的值。
- if ((state = EXEC_TAG()) == 0) { // ※
- // 最初EXEC_TAG==setjmp返回0,所以代码执行到这里
- // 如果这附近出错的话,里面会调用longjmp来进行跳转
- // 所以在跳向※的时候返回非0值。
- ...
- }
- // 处理将转向到这里
这里的处理有点像begin~rescue 的处理过程。不过如果考虑的例外的话可能就要费很大精力了,所以先不过if (…) 这句,继续看下面的代码。
(※稍微看了一下 Ruby Hacking Guide,好像像***_TAG这样的jump tag在Ruby进行评价(eval)的时候用了很多。Yarv怎么样还不知道,如果使用到了,到那时候再调查。)
接着往下,ruby_init调用的初始化函数中,和yarv有关的只有两个,最初被调用的Init_yarv和在rb_call_init中被调用的Init_yarvcore。
- Init_yarv
- rb_call_inits
- …
- Init_yarvcore
- …
其它的普通的Ruby内部对象的处理等因为和Yarv没什么关系,都可以忽略不看。
Init_yarv @ yarvcore.c
第一个初始化函数是 Init_yarv:
- void
- Init_yarv(void)
- {
-
- yarv_vm_t *vm = ALLOC(yarv_vm_t);
- yarv_thread_t *th = ALLOC(yarv_thread_t);
-
- vm_init2(vm);
- theYarvVM = vm;
-
- th_init2(th);
- th->vm = vm;
- yarv_set_current_running_thread_raw(th);
- }
上面的代码先初始化了两个构造体。一个是表示yarv虚拟机构造体(yarv_vm_t),另一个是表示线程的构造体(yarv_thread_t)。vm_init2只是把memroy进行了zeroclear(使用了MEMZERO()函数),th_init2里面应该是frame的stack的做成,但是还没有看到Yarv的执行部分,具体是什么意思也不太清楚。等后面到了线程构造的部分在来检查吧。
先记着生成了这两个构造体就行了。两个局部标量初始化之后,会把虚拟机变量赋给theYarvVM,这个值可以用宏GET_VM()获得它的值。线程构造体变量th在yarv_set_current_running_thread_raw函数里会赋给变量yarvCurrentThread,这个值可以用宏GET_THREAD()获得。
Init_yarvcore @ yarvcore.c
目前的yarv是作为ruby library来工作的(现在的yarv已经合并到Ruby里了),这个函数定义了Ruby的扩展库模块。
- mYarvCore = rb_define_module("YARVCore");
- rb_define_const(mYarvCore, "VERSION",
- rb_str_new2(yarv_version));
- ... 略 ...
先不管这些,知道是一个Library就行了。途中会看到
这样的注释,开始时候会感到疑惑,难道刚才没有建立吗vm之类的变量吗?保险起见,仔细看了一下make vm以后的内容。
- VALUE vmval = vm_alloc(cYarvVM);
- yarv_vm_t *vm;
- yarv_thread_t *th;
- vm = theYarvVM;
-
- xfree(RDATA(vmval)->data);
- RDATA(vmval)->data = vm;
- vm->self = vmval;
vm_alloc(cYarvVM) 方法创建了YarvVM类的实力(实际上是构造体变量),再把刚才做成的theYarvVM赋给vm,vmval也会赋给vm构造体的属性里。可以看出,theYarvVM是YARV内部使用的虚拟机变量。这里做成的vmval变量,是一个能在ruby脚本中能使用的包装对象(VALUE类型)。
继续往下,到了注释create main thread 的地方了
- vm->main_thread_val = yarv_thread_alloc(cYarvThread);
- GetThreadPtr(vm->main_thread_val, th);
-
- vm->main_thread = th;
- vm->running_thread = th;
- GET_THREAD()->vm = vm;
- thread_free(GET_THREAD());
- th->vm = vm;
- yarv_set_current_running_thread(th);
跟上面的一样yarv_thread_alloc(cYarvThread)方法里创建了YarvThread的一个实例。
- yarv_thread_alloc
- thread_alloc
- thread_init
- th_init
yarv_thread_alloc里面又用th_init建立了一个thread类型的构造体,然后在后面,又用thread_free把刚才做成的thread 变量(GET_THREAD())给释放了,又在set_current_running_thread里面把刚才建立的变量th赋给了yarvCurrentThread。
为什么这样做呢?因为Init_yarvcore有可能被反复地调用,所以在调用之前就把前面的thread给销毁了。但是这样的话为什么要在Init_yarv要先做成一个thread呢?目前还不明白,留作以后的课题吧。
ruby_option @ eval.c
初期化完了之后,就是对命令行参数的解析了,也没有什么特别值得一提的东西,就略过了。
ruby_run @ eval.c
最后,在main函数里调用了ruby_run函数。在ruby_run中调用了很多函数,但是主要的流程还是如下面所列的那样。
- main
- ruby_run
- ruby_exec
- ruby_exec_internal
- yarvcore_eval_parsed @ yarvcore.c
终于到了yarvcore!
- VALUE
- yarvcore_eval_parsed(NODE *node, VALUE file)
- {
- VALUE iseq = th_compile_from_node(GET_THREAD(), node,
- file);
- return yarvcore_eval_iseq(iseq);
- }
参数node是已经解析完的ruby语法书(syntax tree ,日语为构文木),参数file是ruby脚本源文件。这个函数比较容易读,主要是把语法树编译(转换)成iseq(Instruction Sequence,指令序列?),然后交给yarvcore_eval_iseq去真正的执行。
总结
下周,读compile和eval哪个比较好呢?还是先看data检查比较好呢?
接下来将阅读
PUSH_TAG, EXEC_TAG 等tag jump的实装
th_init2 : Ruby的做成一个线程的函数的详细内容
Init_yarv 做成的线程的意义
*中间有段注释老是乱码(IE有,FireFox没有),不知道是什么问题?乱码部分内容如下:
if ((state = EXEC_TAG()) == 0) { // ※
// 最初EXEC_TAG==setjmp返回0,所以代码执行到这里 …
// 如果这附近出错的话,里面会调用longjmp来进行跳转
// 所以在跳向※的时候返回非0值。
…
}
// 处理将转向到这里