Lua的内存布局结构
0.Lua编译过程?
1.Lua内存布局?
2.内存中的状态?
我们知道每种语言都有自己的内存布局的状态,比如C++,Lua也不例外(动态语言:即边解释边执行),只是没有 C++ 那么复杂,C++ 是面向对象语言静态语言,由于本身语言特性比较多,比如继承、虚函数等,所以对象的内存布局模型会复杂很多。下面我们结合一个简单的脚本示例及加载、执行脚本主干流程产生的数据结构对象来分析Lua中的内存布局是怎么样的。 Lua脚本通过编译成字节码中间状态才能在虚拟机上运行。这里主要分析脚本编译过程及加载到执行每个阶段内存中布局状态。各阶段涉及到创建的重要数据结构及加载执行中的内存布局。对应代码的流程为luaL_loadfile到lua_pcall的过程。
Lua从加载到执行可以理解整个过程就是两个状态:静态(Proto)和动态(LCloure)。
静态:编译器生成字节码后的中间产物:Proto(函数原型);
动态:是虚拟机执行过程中产生的LClosure(函数闭包或者说是函数对象)以及此闭包可以访问到的变量(upvalue:理解为函数上面的外部局部变量或者理解函数作用域的值);
动态与静态的产物的关系可以理解为:LClosure = Proto + upvalues;
LClosure在Lua里中也为数据类型。
1.脚本及ByteCode:
test.lua源码:
function f()
print("hello thunder")
end
脚本编译ByteCode(luac -l -l luac.out):
main <test.lua:0,0> (4 instructions at 0x7fbfdf4061b0)
0+ params, 2 slots, 1 upvalue, 0 locals, 1 constant, 1 function
1 [1] VARARGPREP 0
2 [3] CLOSURE 0 0 ; 0x7fbfdf4062c0
3 [1] SETTABUP 0 0 0 ; _ENV "f"
4 [3] RETURN 0 1 1 ; 0 out
constants (1) for 0x7fbfdf4061b0:
0 S "f"
locals (0) for 0x7fbfdf4061b0:
upvalues (1) for 0x7fbfdf4061b0:
0 _ENV 1 0
function <test.lua:1,3> (5 instructions at 0x7fbfdf4062c0)
0+ params, 2 slots, 1 upvalue, 0 locals, 2 constants, 0 functions
1 [1] VARARGPREP 0
2 [2] GETTABUP 0 0 0 ; _ENV "print"
3 [2] LOADK 1 1 ; "hello thunder"
4 [2] CALL 0 2 1 ; 1 in 0 out
5 [3] RETURN 0 1 1 ; 0 out
constants (2) for 0x7fbfdf4062c0:
0 S "print"
1 S "hello thunder"
locals (0) for 0x7fbfdf4062c0:
upvalues (1) for 0x7fbfdf4062c0:
0 _ENV 0 0
2.脚本编译过程:
源码通过编译(luac)把所有的函数信息表示成Proto数据结构存储比如参数、常量、指令集、调试信息、本地变量及各数据size等等,保存编译的结果。最终通过虚拟机读取Proto指令数值中指令一个个来执行(lvm.c#luaV_execute)。执行过程中的数据单元为函数闭包(CLosure)。通过这个过程的了解,我们知道接下来要分析的是什么数据对象了。
3.Lua脚本加载内存布局
加载(luaL_loadfile)到内存后的内存布局:
在Lua中脚本加载通过luaL_loadfile生成一个函数闭包压入栈顶,然后luaY_parser词法、语法分析之后生成对应的数据存入到Proto中,字节码对应可详见函数部分Lua字节码文件结构及加载过程。如图主要为数据栈,函数调用栈以及全局状态中的数据及重要的栈指针。其中stack_last(最大stack位置,范围[stack,stack_last])、top(栈顶)、stack(栈底)指向虚拟栈中对应的位置。具体数据含义可以参照:虚拟机篇
4.ByteCode VM上运行内存布局
Lua ByteCode加载到虚拟机之后执行(lua_pcall)字节码的内存布局状态:
Lua执行通过lua_pcall函数,这里会涉及到一个比较重要的数据结构CallInfo,到lua_pcallk函数中,首先构建一个CallInfo指针实例指向lua_State(CallInfo *ci)当前函数及当前函数index等。最终通过luaV_execute函数pc = ci->u.l.savedpc、i(指令index) = *(pc++)循环执行的。具体可以详见:虚拟机篇。执行过程中除加载阶段涉及到的数据结构外比较重要的是CallInfo,状态中为双向链表结构,可以理解成函数调用栈,通过luaL_State结构体ci、base_ci指向函数栈当前CallInfo和栈底CallInfo。
5.总结
脚本加载、执行过程中主要围绕这几个数据结构(lua_State、global_State、CallInfo、LCloure、Proto)展开来的就是Lua内存布局结构(编译时结构和运行时结构)。通过上面我们应该很清晰什么是内存布局了,其实就是内存中的数据结构。