很抱歉,这周实在是太忙了。。。不废话,直接进入主题。
引言
所谓的分析思路,无非就是找到程序的入口点,按执行流程一步一步地阅读分析,或者从自己感兴趣的部分入手。这个系列暂时还不是对Lua的全面剖析,只是选取其中一部分来分析。因此我们要找到自己感兴趣的部分,有针对性地进行分析。
由于Lua的官方文档比较齐全,浏览一下官方提供的资料集会比较便于我们着手分析。
所谓的源码分析,就是从源代码构建可执行程序,调试调试,看看执行流程,看看主要的数据结构和算法、程序的运行状态之类的,最后再品味一下设计(问题背景、原因、优缺点),仅此而已。
那我们先来看看源代码的布局。
代码布局
可以从Makefile文件入手,不过这里用的是Windows + Visual Studio,可以省去这个步骤。
看看从官方下载的源码包,提供了以下文件:
./lua-5.3.0/
Makefile README
/doc/
contents.html logo.gif lua.1
lua.css luac.1 manual.css
manual.html osi-certified-72x60.png
readme.html
/src/
lapi.c lapi.h lauxlib.c lauxlib.h
lbaselib.c lbitlib.c lcode.c lcode.h
lcorolib.c lctype.c lctype.h ldblib.c
ldebug.c ldebug.h ldo.c ldo.h
ldump.c lfunc.c lfunc.h lgc.c
lgc.h linit.c liolib.c llex.c
llex.h llimits.h lmathlib.c lmem.c
lmem.h loadlib.c lobject.c lobject.h
lopcodes.c lopcodes.h loslib.c lparser.c
lparser.h lprefix.h lstate.c lstate.h
lstring.c lstring.h lstrlib.c ltable.c
ltable.h ltablib.c ltm.c ltm.h
lua.c lua.h lua.hpp luac.c
luaconf.h lualib.h lundump.c lundump.h
lutf8lib.c lvm.c lvm.h lzio.c
lzio.h Makefile
第一件事当然是看README啦还用说,当然专业的做法可以看Makefile,这里为了避免引入其他无关知识,还是选用简单的方法。
按照说明查看doc/readme.html文件其中的【Building Lua on other systems】节,我们发现它介绍了lua可执行程序大致的组成和依赖关系如下:
library:
lapi.c lcode.c lctype.c ldebug.c
ldo.c ldump.c lfunc.c lgc.c
llex.c lmem.c lobject.c lopcodes.c
lparser.c lstate.c lstring.c ltable.c
ltm.c lundump.c lvm.c lzio.c
lauxlib.c lbaselib.c lbitlib.c lcorolib.c
ldblib.c liolib.c lmathlib.c loslib.c
lstrlib.c ltablib.c lutf8lib.c loadlib.c
linit.c
interpreter:
library, lua.c
compiler:
library, luac.c
根据这个简单的依赖关系,我用VS2015建立了相应的工程,便于调试。其实官方资料集里也提供了现成的VS工程的下载链接:
TODO:此处应有下载链接^_^
因为我们分析的重点是编译原理和虚拟机的部分,而不是相关的库的实现部分。因此应该从 lua.c 或 luac.c 开始入手,其实我们从文件列表中也可以看出,里面有几个比较重要的文件:
llex.c lopcodes.c lparser.c lvm.c
到底从哪里入手比较好,就见仁见智了。这里我还是采用了官方提供的资料来帮助选择。
官方的资料集的wiki中提供了一个页面。该页面介绍了这些文件的用途、编程约定、模块结构等等。
为了避免这个页面失效,下面还是可耻地复制粘贴略带翻译地提供给大家,网上也有一些翻译,只是他们翻译时省略掉了一些我觉得有用的信息。个别简单的,我就不翻译了。
Lua源码的模块结构
实用功能模块
ldebug.c
– 调试接口。包括以下功能:访问调试钩子 (lua_sethook, lua_gethook, lua_gethookcount),
访问运行时栈信息 (lua_getstack / lua_getlocal / lua_setlocal),
检查字节码 (luaG_checkopenop / luaG_checkcode),
引发错误 (luaG_typeerror / luaG_concaterror / luaG_aritherror /luaG_ordererror / luaG_errormsg / luaG_runerror)
lzio.c
– 一种缓冲输入流接口。lmem.c
– 内存管理接口。它实现了这些内存分配函数:luaM_realloc / luaM_growaux_lgc.c
– 增量式GC (内存管理)
基本数据类型的实现模块
lstate.c
– 全局状态。包括:用于打开和关闭Lua states的函数(lua_newstate/lua_close)
线程相关函数 (luaE_newthread / luaE_freethread)
lobject.c
– 一些针对Lua对象的通用函数。包括:数据类型与其字符串形式的互相转换
原始数据相等性测试(luaO_rawequalObj)
日志基础设施2(luaO_log2)
lstring.c
– string table (持有由Lua处理的所有字符串)lfunc.c
– 用来操纵原型和闭包的辅助函数。ltable.c
– Lua tables (hash)
语法分析与代码生成相关模块
lcode.c
– Lua的代码生成器. 由 lparser.c 来使用llex.c
– 词法分析器. 由 lparser.c 来使用lparser.c
– Lua 解析器.lundump.c
– 加载经过预编译的Lua代码块:实现了用于加载预编译后的代码块的luaU_undump 函数。
还提供了用来解析函数头的 luaU_header 函数(在luaU_undump()内部被调用)。
ldump.c
– 用于保存经过预编译的Lua代码块:实现了用于转储函数对象的luaU_dump()函数。这种函数对象是从文件或字符串预编译而来的Lua代码块。
处理执行Lua字节码的模块
lopcodes.c
– 由Lua虚拟机使用的操作码。通过 luaP_opnames 和 luaP_opmodes这两个映射表,定义了所有操作码的名称和相关信息。
lvm.c
– Lua虚拟机:用于执行字节码 (luaV_execute).
还暴露了少量函数供 lapi.c 使用(如:luaV_concat).
ldo.c
– Lua函数调用和栈管理。处理函数调用 (luaD_call / luaD_pcall), 栈生长, 协程处理等ltm.c
– 标签方法(tag methods)。 实现了查询对象中的元方法的功能。
标准库的实现模块
lbaselib.c - (base functions)
lstrlib.c - string
ltablib.c - table
lmathlib.c - math
loslib.c - os
liolib.c - io
loadlib.c - package
ldblib.c - debug
以下模块定义了 C API
lapi.c
– Lua API. Lua C API的主要实现部分(lua_* functions).lauxlib.c
– 定义了 luaL_* 函数linit.c
– 实现了 luaL_openlibs 用于从C语言环境中加载上述模块。
lua 和 luac 程序的实现模块
lua.c
– 独立的Lua 解析器print.c
– 定义了 “PrintFunction?” 函数,这些函数用于打印字节码 (通过luac.c “-l” 选项来使用)luac.c
– Lua 编译器 (保存字节码到文件/列出字节码)
感兴趣的模块
搞清楚Lua的模块结构后,我们需要着重分析的模块就出来了:
lua 和 luac 程序的实现模块
处理执行Lua字节码的模块
语法分析与代码生成相关模块
基本数据类型的实现模块
当然,到了后期,如果有时间,我还会再分析一下Lua的垃圾收集器,但最近实在是太忙太忙了。
从Makefile来看模块间的依赖关系
以下是对 lua-5.3.0/src/Makefile 文件重要部分的节选
LUA_A= liblua.a
CORE_O= lapi.o lcode.o lctype.o ldebug.o ldo.o ldump.o lfunc.o lgc.o llex.o \
lmem.o lobject.o lopcodes.o lparser.o lstate.o lstring.o ltable.o \
ltm.o lundump.o lvm.o lzio.o
LIB_O= lauxlib.o lbaselib.o lbitlib.o lcorolib.o ldblib.o liolib.o \
lmathlib.o loslib.o lstrlib.o ltablib.o lutf8lib.o loadlib.o linit.o
BASE_O= $(CORE_O) $(LIB_O) $(MYOBJS)
LUA_T= lua
LUA_O= lua.o
LUAC_T= luac
LUAC_O= luac.o
根据我们感兴趣的模块,大概是以下目标文件:
lua.o
luac.o
llex.o
ldo.o
lstate.o
lparser.o
lopcodes.o
lvm.o
lcode.o
ldump.o
lundump.o
ltm.o
lobject.o
lstring.o
ltable.o
lfunc.o
(不得不吐槽这Markdown编辑器的Bug,莫名其妙给其他其他条目自动加粗了。。。我原本都不是这么写的!!!!)
其实还是挺多的,不过比较重要的都用粗体标出来了。大致就是按这个顺序去分析。这些其实也可以直接看源文件中引用的头文件,但那样太麻烦了,并且,Lua源码有时候并不是在文件的顶部写include指令,而是在中间的某个地方,比较蛋疼。看Makefile是比较方便而且专业的做法。我们继续看一下Makefile 文件中上述目标文件依赖关系:
lua.o: lua.c lprefix.h lua.h luaconf.h lauxlib.h lualib.h
luac.o: luac.c lprefix.h lua.h luaconf.h lauxlib.h lobject.h llimits.h \
lstate.h ltm.h lzio.h lmem.h lundump.h ldebug.h lopcodes.h
llex.o: llex.c lprefix.h lua.h luaconf.h lctype.h llimits.h ldo.h \
lobject.h lstate.h ltm.h lzio.h lmem.h lgc.h llex.h lparser.h lstring.h \
ltable.h
ldo.o: ldo.c lprefix.h lua.h luaconf.h lapi.h llimits.h lstate.h \
lobject.h ltm.h lzio.h lmem.h ldebug.h ldo.h lfunc.h lgc.h lopcodes.h \
lparser.h lstring.h ltable.h lundump.h lvm.h
lstate.o: lstate.c lprefix.h lua.h luaconf.h lapi.h llimits.h lstate.h \
lobject.h ltm.h lzio.h lmem.h ldebug.h ldo.h lfunc.h lgc.h llex.h \
lstring.h ltable.h
lparser.o: lparser.c lprefix.h lua.h luaconf.h lcode.h llex.h lobject.h \
llimits.h lzio.h lmem.h lopcodes.h lparser.h ldebug.h lstate.h ltm.h \
ldo.h lfunc.h lstring.h lgc.h ltable.h
lopcodes.o: lopcodes.c lprefix.h lopcodes.h llimits.h lua.h luaconf.h
lvm.o: lvm.c lprefix.h lua.h luaconf.h ldebug.h lstate.h lobject.h \
llimits.h ltm.h lzio.h lmem.h ldo.h lfunc.h lgc.h lopcodes.h lstring.h \
ltable.h lvm.h
lcode.o: lcode.c lprefix.h lua.h luaconf.h lcode.h llex.h lobject.h \
llimits.h lzio.h lmem.h lopcodes.h lparser.h ldebug.h lstate.h ltm.h \
ldo.h lgc.h lstring.h ltable.h lvm.h
这里简要地说一下Makefile语法:冒号左边的.o是编译时生成的目标文件,冒号右边是生成这个目标文件需要的源文件,\ 表示还没写完,下一行继续写。上一段Makefile代码中,等号左边是一个变量,等号右边是这个变量的值。
关于Makefile:Makefile是GNU/Linux系统中用于自动化构建的DSL,供gnu make使用,和Android开发中的.gradle文件作用类似。不过VS和Qt也都使用了各自的Makefile格式和工具。VS有nmake,Qt有qmake,跨平台的有cmake,这里就不展开说了,自己去看文档吧。
现在,我们可以比较有针对性地去看源文件了。
关于下一期
下一期将介绍一下Lua源码中的编程约定(其实还是做个搬运工+翻译工,这里的翻译就凑合着看吧,翻译质量应该没有太大问题)。前面这几期都是比较无聊又不可或缺的,只能忍忍啦。预报一下,后天将更新第三弹。