文件结构
提供外部使用的头文件
- lua.h 定义了Lua的核心API。
- lauxlib.h 定义了Lua的辅助API,此文件中定义的函数,是对lua.h中函数的简单封装,方便使用。
- lualib.h 定义了Lua的标准库(coroutine, table, io, os, string, utf8, math, debug, package)。
- luaconf.h 定义编译相关的宏,主要用于区分别不同平台的特性以及一些其他编译特性。
核心代码
Lua的核心代码主要分为以下几个模块:
核心API
- lapi.h 定义了几个栈的操作宏(api_incr_top:栈顶加一, adjustresults:调节栈顶以容纳更多的返回值, api_checknelems:检查栈中是否有对应的栈空间)。
- lapi.c lua.h头文件中定义核心API的实现。
- lctype.h C语言的标准字符检查函数头文件,主要检查单个字符的类型,增加了EOZ,允许传入-1(EOZ)进行检测。
- lctype.c 定义了符号表。
- ldo.c 处理Lua的栈和调用结构。
- lfunc.c 定义了用于操纵函数原型和闭包的辅助函数。
- llimits.c 定义了一些基础的类型和安装依赖信息。
- lmen.c 内存管理接口。
- lobject.h Lua中的对象类型定义。
- lobject.c 对象上的一些操作。
- lstate.h 定义了lua_State相关的结构,这是Lua的核心结构。
- lstate.c lstate.h中定义结构的相关操作。
- lstring.c Lua字符串表,处理和缓存Lua中使用的字符串。
- ltable.c 定义了Lua中Tale相关的操作。
- ltm.c 元表相关的定义和操作。
- ldump.c 将Lua函数转储为预编译块。
- lundump.c 加载预编译的Lua块。
- lzio.c 定义了一个buffer流,用于读取数据。
GC管理
- lgc.c 垃圾回收器
词法,语法和语义分析器
- llex.c 词法分析器。
- lparser.h 语法和语义分析器。
Lua虚拟机(解释器)
- lopcodes.h 定义了Lua虚拟机的操作码(OP_MOVE, OP_LOADI等)跟汇编的MOV指令类型,虚拟机就是用来执行这些指令的。
- lopnames.h 定义了操作码对应的名字。
- ljumptab.h 定义了Lua解释器跳转表相关的宏(vmdispatch, vmcase, vmbreak)。
- lvm.c Lua的虚拟机用于执行opcode。
调试
- ldebug.c 获取调试信息(函数调用, 行号信息等)。
标准库代码
- lauxlib.c 实现了Lua的辅助API,此文件中定义的函数,是对lua.h中函数的简单封装,方便使用。
- lbaselib.c 实现了Lua中使用的一些全局函数(assert, dofile, error, ipairs, pairs, print, getmetatable, setmetatable, xpcall, tostring, type等)。
- lcorolib.c 协程(coroutine)。
- ldblib.c 调试库(debug)。
- liolib 标准IO操作(io)。
- lmathlib.c 数学库(math)。
- loadlib.c 加载库(package)。
- loslib.c 系统库(os)。
- lstrlib.c 字符串库(string)。
- ltablib.c 表(table)。
- lutf8lib.c utf8库(utf8)。
- linit.c 注册所有的标注库到Lua, 核心函数luaL_openlibs,会遍历注册所有的标准库。
解释器和编译器
- lua.c 通过上面的API构建的一个Lua解释器。
- luac.c Lua的编译器,主要用于将Lua源文件编译成二进制格式。
核心代码
核心结构
global_State 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53// 全局的State, 被所用State共享的,用于记录一些全局数据
struct global_State
{
lua_Alloc frealloc; // 内存分配函数,现在是使用的realloc(有连续空间,扩展内存是不会进行拷贝数的)
void *ud; // frealloc 的辅助数据,现在没有用,如果使用其他的内存分配函数可以使用这个预留的字段,在lauxlib.c中的l_alloc函数中是未使用的
l_mem totalbytes; // 当前分配的总内存(单位:Byte)
l_mem GCdebt; // 垃圾收集器还未归还的内存(单位:Byte)
lu_mem GCestimate; // 正在使用的非垃圾内存的预估值(单位:Byte)
lu_mem lastatomic; // 代垃圾收集的一个标志
stringtable strt; // 用于存储Lua中使用的短字符(长度小于等于40,通过宏LUAI_MAXSHORTLEN定义的)串表
TValue l_registry; // 注册表(Table类型),默认存储了全局主线程的State和全局Table,主要用来存储用户数据的元表
TValue nilvalue; // 定义的全局nil值
unsigned int seed; // 随机数种子
// 垃圾收集相关
lu_byte currentwhite;
lu_byte gcstate; // 垃圾收集器状态(GCSpropagate, GCSenteratomic, GCSatomic, GCSswpallgc, GCSswpfinobj, GCSswptobefnz, GCSswpend, GCScallfin, GCSpause)
lu_byte gckind; // 当前运行的GC类型(KGC_INC(增量GC), KGC_GEN(分代GC))
lu_byte gcstopem; // 是否停止紧急收集
lu_byte genminormul; // 次代收集倍数
lu_byte genmajormul; // 主代收集倍数
lu_byte gcrunning; // GC是否在运行
lu_byte gcemergency; // 是紧急收集
lu_byte gcpause; // 两个连续GC的暂停阈值
lu_byte gcstepmul; // GC的步长,用于控制GC的速度
lu_byte gcstepsize; // 单步长收集的的大小
GCObject *allgc; // 所有可收集对象的列表
GCObject **sweepgc; // 当前扫到的位置
GCObject *finobj; // 进入了析构的对象列表(还未真正的释放)
GCObject *gray; // 灰色对象列表
GCObject *grayagain; /* list of objects to be traversed atomically */
GCObject *weak; // 弱值弱引用表
GCObject *ephemeron; // 弱key引用表
GCObject *allweak; // 所有的若引用
GCObject *tobefnz; // 要GC的userdata列表
GCObject *fixedgc; // 不被GC回收的对象列表
// 分代收集器相关的字段
GCObject *survival; // 存活了一个GC周期的对象头
GCObject *old1; /* start of old1 objects */
GCObject *reallyold; /* objects more than one cycle old ("really old") */
GCObject *firstold1; /* first OLD1 object in the list (if any) */
GCObject *finobjsur; /* list of survival objects with finalizers */
GCObject *finobjold1; /* list of old1 objects with finalizers */
GCObject *finobjrold; /* list of really old objects with finalizers */
struct lua_State *twups; // 开放了上值的现场列表
lua_CFunction panic; // 为保护模式下发生异常时调用的函数
struct lua_State *mainthread; // 主线程的State对象
TString *memerrmsg; // 缓存的一个内存分配的错误消息
TString *tmname[TM_N];// 元方法名字(__index, __len等)
struct Table *mt[LUA_NUMTAGS]; 基础类型的元表()
TString *strcache[STRCACHE_N][STRCACHE_M]; // 字符串缓存,避免在使用一些字符串时频繁创建
lua_WarnFunction warnf; // 警告函数
void *ud_warn; // 警告函数的辅助数据
};
lua_State
1 | // 每个线程的State |
CallInfo
1 | struct CallInfo { |
TString
1 | // 字符串 |
Closure
1 | // 闭包的公共头 |
Proto
1 | // 函数原型 |
Upvaldesc
1 | // 函数原型的上值描述 |
UpVal
1 | // 闭包的上值 |
Udata
1 | // userdata的头,分配的内存区域放在此结构的后面 |
Table
1 | // Lua的核心数据结构,我们常用的表的结构 |
flags字段说明:
7 位 表示alimit字段是否代表了数组的真实大小(0:是,1:不是),否则数组的真实大小是不小于alimit的2的整数次幂。 0-6 位 表示是否有TM_INDEX, TM_NEWINDEX, TM_GC, TM_MODE, TM_LEN, TM_EQ(0:有,1:没有)主要用于快速检测这几个常用的元方法是否存在。
Table同时使用数组和Hash表的结构,当使用的key是整形且不大于alimit时,Lua会使用array字段,当大于alimit时,则会转换成hash表的方式来。当数组或Hash表的大小需要扩容,应尽量避免这些没有避免的性能消耗。解决Hash冲突的方式是结合了开放寻址和链表的形式。
TValue
1 | // Lua中的值(所有类型都是用的此结构) |
Tag(标记)字段说明:
0-3 位 表示LUA_T*定义的类型(最多16种类型) 4-5 位 表示变体标志(额外附加的一些标志位,比如要表示一个Number类型是整形还是浮点型,则可以加上一个变体标志来表示,如果LUA_TNUMBER(3)宏默认定义为整形,为了表示是浮点型,则可以 1 & (3<<4) 这就在变体位加上了一个1,假设想区分单/双精度浮点则我们可以用1表单精度,2表示双精度,则可以使用2&(3<<4)来表示) 6 位 表示是否是可收集(GC)对象
StackValue
1 | // 用于表示Lua栈上的值 |
核心函数
State相关
1 | // 线程状态+额外空间 |
Proto和Closure相关
1 | // 分配一个Proto对象 |
CallInfo相关
1 | // 调用lua解析器生成Lua闭包和函数原型 |
Table相关
创建和释放:
1 | // 创建一个Tablew |
获取:
1 | // 获取函数 |
设置:
1 | // 设置值 |
HelloWorld
通过HelloWorld函数程序来说明Lua程序是如何运行起来的。
创建LuaState并注册标注库
1 | int main() |
解析Lua代码生产指令 Lua解释器将Lua源码转换成Lua指令的过程。核心代码如下:
1 | // 加载解析并执行Lua代码 |
解析完后会生成一个原型(Proto)对象,对象的code字段存放的就是对应的指令码,指令码有如下5种格式: 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 iABC C(8) | B(8) |k| A(8) | Op(7) | iABx Bx(17) | A(8) | Op(7) | iAsBx sBx (signed)(17) | A(8) | Op(7) | iAx Ax(25) | Op(7) | isJ sJ(25) | Op(7) |
开头的7位表示操作码,后面的表示操作码对应的参数。
执行指令
1 | void luaV_execute (lua_State *L, CallInfo *ci) { |
总结
通过对源码的阅读,对Lua虚拟机的内部结构以及运行机制有更深入的理解。但是并不是所有细节和模块都进行了阅读,比如像Lua词法,语法和语义分析以及GC相关的代码就是直接跳过的。Lua本身也不是一篇文章就能理解完的,对于Lua虚拟机的学习先暂时告一段落。后续闲了会再对GC相关的代码进行阅读,至于词法,语法和语义分析这块的代码暂时不打算去学习,主要是在实际项目中暂时不需要这方面的内容。