Ubuntu Firefox 开源 mysql shell linux centos Android 微软 php Python apache nginx google java 程序员 wordpress linux命令 Windows HTML5

学习Lua源代码的一些经验和参考资料推荐

断断续续在博客更新Lua5.1.4分析的一些文章,当前写的还是很杂乱。前面大体分析了一些数据结构(通用数据结构,表),一些流程的处理(赋值,逻辑跳转),还有函数相关的代码。后面还有不少没有分析到,目测还有表相关的操作,GC,Lua调试器等等,内容还是不少。

今天暂时打住,记录一下我看Lua代码这段历程来用到的一些知识点,经验和参考资料。

起初决定看Lua代码的理由很简单。每个对代码有点喜欢的人,都会觉得能创造一门语言是很酷而且充满神秘感的事情,尤其对我这样非科班出身的人。龙书很早就买了,一直搁着,上面布满了灰尘。中间也接触过一些编译类的书籍,比如编译原理与实践就实现了一个Toy级别的脚本语言,虽然五脏俱全,但是毕竟离正经产品级的代码还是有距离。

Lua也是一早就听说过,但是一直没有太多了解。直到2011年换工作之后,使用C++ + Lua做游戏服务器。这个服务器大体的框架是C++层做为所谓的引擎层,实现网络,数据库查询,AOI等,C++提供一个逻辑线程,所有网络收报之后的处理将数据放到Lua层来做逻辑处理。看上去没有什么太特别的。而惊艳的地方在于,在引入了Lua脚本层之后,一般情况下对逻辑的调试修改都是在Lua层进行,由于脚本可以实现热更新,加断点调试,这样在修改调试的时候大部分时间不用再重启服务器了。游戏服务器不同于其他本身不需要保存状态的服务器,启动的时候需要加载数据,所以启动会比较慢,调试不用重启服务器这对开发效率是极大的提高。

这是Lua给我带来最爽的快感。Lua不是一个自己能跑起来的脚本语言,不像Python,Ruby那样,它需要一个宿主环境,从诞生起它就做好了给别人当配角的打算,对于CC++这样的静态编译语言来说,需要一些动态特性的时候搭配上Lua是绝好的搭配。

于是,怀着对创造一门语言的神秘感,和Lua带来的极大方便,我开始打算好好看Lua的代码。这各时间点,大概是2012年初了吧。

我最开始的计划,是自上而下的,也就是从常规的词法分析,语法分析到语义分析。然则这里遇到了比较大的问题。在编译原理与实践中的教学语言,使用的非常传统的方式,词法分析,语法分析之后生成语法树,然后再到语义分析。这是大概两遍扫描文件的流程,第一遍先生成语法树,第二遍再语义分析。然而Lua中并没有生成语法树,而是一边分析一边进行语义分析,一遍分析就搞定。由于省略了这一个中间步骤,给我阅读Lua代码带来极大困难,因为我之前并没有正经学过编译原理,只是大概知道怎么回事。所以开始的方式,是先去补编译原理的一些知识,这个过程大概持续了几个月,到现在,我仍然不认为我把这套流程完全明白了。

由于在前面遇到太多挫折,我后来反思了一下最开始的计划。可能最开始想的太完美,反而会进步缓慢,于是开始从Lua的opcode下手。提起这里参考最多的资料,应该是《A No Frills Intro To Lua51 VM Instructions》。这个资料最好的一点是,它将Lua中每个opcode基本过了一个遍,对每个指令给出例子场景和相应生成的opcode。我个人在阅读Lua代码的过程,将opcode像它那样分为几种类型,比如函数类,赋值类,表操作类,一个一个写示例代码调试。我写的demo例子代码是这样的:

#include 
#include 
#include 
#include 

int main(int argc, char *argv[]) {
  char *file = NULL;
  if (argc == 1) {
    file = "my.lua";
  } else {
    file = argv[1];
  }

  lua_State *L = lua_open();
  luaL_openlibs(L);
  luaL_dofile(L, file);

  return 0;
}

很简单,加载一个叫my.lua的文件来执行。需要注意的一点的是,生成任何Lua opcode的时候,最后都会调用函数luaK_code。那么这个事情就很简单了,你按照你想分析的Lua执行写一段代码,然后开gdb,在这个函数下断点,然后开始执行,它中间会产生什么opcode就一目了然了,慢慢分析吧。另外一个重要的函数就是lvm.c中的luaX_execute函数,这是Lua虚拟机开始执行指令的入口。你把握住了这两个入口点,生成什么指令和如何执行指令,就把握住了,剩下的就是耐心和毅力。所以我这时候的学习方式,就是不停的在my.lua中加入我想分析的Lua指令,在这两个重要的函数上下断点,分析其流程。这段时间是我对Lua了解进步很快的时间。

至于其他的资料,老实说很多都是看了一遍不会再看的(当然讲解Lua语法的书得准备一下随时查阅)。只有这份资料是每次开始看Lua代码时都会打开的。

回头看学习Lua代码的这一段历程,最开始学习编译原理以分析Lua语义分析的流程是最痛苦的。而当我现在对Lua源码有一定了解之后,我觉得如果你不是对编译特别感兴趣,只想弄明白Lua本身,实际上可以不必在这里耗费太多时间,直接按照我前面说的那样,把Lua的opcode过一遍收获可能会更大。而当你搞定了这些,再回头啃这块硬骨头的时候,心态也会不一样。做为一个过来人,我建议就是先以了解Lua本身为主,顺带学习编译为锦上添花的点。

将阅读Lua代码本身能学习和涉及到的一些知识点罗列如下,我看分为几大块:
1)编译原理相关的,如果你要学习如何创造一门语言这部分可以看看,包括词法分析,语法分析,语义分析。
2)Lua本身相关的,你要对Lua本身有个通透的了解,最好把它的opcode全部过一遍,Lua的opcode不算多,30多个吧,上面提到的参考资料每个都分析了,可以按照我的方法也每种指令都写Lua代码来调试跟踪整个流程。这个过程走下来之后,对Lua的了解整个就上了一个档次。

另外需要提一个点,Lua是使用Lua栈与C进行交互的,把这个点理解好可以理解Lua执行流程的关键点,网上有一篇文章分析的不错,在这里

3) 表是Lua最重要的数据结构,可以说无处不在都是表,所以我把表相关知识点单独拿出来算一项以提高它的重要性。与它相关的知识点出了Lua表的实现,相关的opcode(表相关的只有三个),还包括如何使用Lua表实现面向对象,meta表,registry表,G表。
4)GC,Lua中使用的垃圾收集机制。
5)Lua如何进行热更新,如何实现一个Lua调试器。
6)其他的杂项,比如Lua中使用的一些编码技巧,如何用C实现异常处理,Lua代码优雅的点,等等。

从前面的说法来看,我觉得1)不是最重要的,能完整的把2)走完,对Lua的了解会倍增同时也会得到很多信心,这样就可以再按照其他知识点依次看下去。

最后的最后,做为一门生命期已经超过15年,而且流行程度能在世界排前20的语言,Lua只有不到2W行代码,而再把一些不重要的地方去掉,代码量还会更少,可能核心的代码不到1W行吧。另外做为一门纯C语言编写的项目,我认为其代码质量比大家都评价很高的Nginx还要高,这个问题我以后会专门聊一聊。我说了这些,是想说从学习优秀开源项目代码的角度来说,学习Lua的性价比很高,坚持下去收获很大。

延伸阅读

评论