lua state

Lua除了是一门扩展的言语外,还是一门“胶水语言”。最著名的就是作为游戏的脚本开发。

这里主要说lua怎样与c交互。
Luac语言通信的主要方法是一个无所不在的虚拟栈。几乎所有的API调用都会操作这个栈上的值。所有的数据交换,无论是LuaC语言或C语言到Lua都通过这个栈来完成。栈可以解决LuaC语言之间存在的两大差异,第一种差异是Lua使用垃圾收集,而C语言要求显式地释放内存;第二种是Lua使用动态类型,而C语言使用静态类型。

环境搭建

1
lua_State *L = luaL_newstate();//创建一个新的环境

luaL_newstate用c运行库的内存分配函数。
lua_newstate可自定义内存分配函数。

头文件lua.h定义了lua提供的基础函数,包括创建lua环境、调用lua函数(如lua_pcall)、读写lua环境中全局变量,以及注册供lua调用的新函数等。Lua.h中定义所有内容都有一个lua_前缀。
头文件lauxlib.h定义了辅助库(auxiliary library,auxlib)提供的函数。它的所有定义都以luaL_开头(如luaL_loadbuffer)。辅助库是一个使用lua.hAPI编写出的一个较高的抽象层。Lua的所有标准库编写都用到了辅助库。

Lua脚本的编译执行是相互独立的,在不同的线程上执行。通过luaL_newstate()函数可以申请一个虚拟机,返回指针类型lua_State。今后其他所有Lua Api函数的调用都需要此指针作为第一参数,用来指定某个虚拟机。所以lua_State代表一个lua虚拟机对像,luaL_newstate()分配一个虚拟机。lua类库管理着所有的虚拟机。销毁指定虚拟机的所有对像(如果有垃圾回收相关的无方法则会调用该方法)并收回所有由该虚拟机动态分配产生的内存,在有些平台下我们不需要调用此函数,因为当主程序退出时,资源会被自然的释放掉,但是但一个长时间运行的程序,比如后台运行的web服务器,需要立即回收虚拟机资源以避免内存过高占用。

Lua虚拟机相关的数据结构与栈

解释器要做的就是模拟计算机的执行,这主要分为以下两大块。

CPU:用于指令的执行。
内存:用于数据的存储。
指令执行的部分在lua虚拟机(lua虚拟机工作流程)大体介绍过,即解释器分析Lua文件之后生成Proto结构体,最后到luaV_execute函数中依次取出指令来执行。
而“内存”部分,在Lua解释器中就存放在Lua栈中。 Lua中也是把栈的某一个位置称为寄存器(非真CPU中的寄存器)。
每个Lua虚拟机对应一个lua_State结构体,它使用TValue数组来模拟栈,其中包括几个与找相关的成员。
stack :栈数组的起始位置。
base : 当前函数栈的基地址 。
top : 当前栈的下一个可用位置 。
这些成员的初始化操作在stack_init函数中完成。

1
2
3
4
5
6
7
8
static void stack_init (lua_State *L1, lua_State *L) {
/* initialize CallInfo array */
L1->base_ci = luaM_newvector(L, BASIC_CI_SIZE, CallInfo);
L1->ci = L1->base_ci;
L1->size_ci = BASIC_CI_SIZE;
L1->end_ci = L1->base_ci + L1->size_ci - 1;
...
}

再看看lua_State

1
2
3
4
5
6
7
8
9
struct lua_State {
CommonHeader;
lu_byte status;
StkId top; /* first free slot in the stack */
StkId base; /* base of current function */
global_State *l_G;
CallInfo *ci; /* call info for current function */
...
};

lua_State里面存放的是一个Lua虚拟机的全局状态,当执行到一个两数时,需要有对应的数据结构来表示函数相关的信息。这个数据结构就是CallInfo,这个结构体中同样有topbase这两个与栈相关的成员。

1
2
3
4
5
6
7
8
9
10
11
/*
** informations about a call
*/
typedef struct CallInfo {
StkId base; /* base for this function */
StkId func; /* function index in the stack */
StkId top; /* top for this function */
const Instruction *savedpc;
int nresults; /* expected number of results from this function */
int tailcalls; /* number of tail calls lost under this entry */
} CallInfo;

无论函数怎么执行,有多少函数,最终它们引用到的栈都是当前Lua虚拟机的栈。 这好比一个操作系统中的进程无论有多少,最终引用的内存实际上都还是由操作系统内核来管理的。
lua_State结构体中,有一个base_ciCallInfo数组,存储的就是CallInfo的信息。而另一个ci成员,指向的就是当前函数的CallInfo指针。
在调用函数之前,一般会调用luaD_precall函数,它主要完成如下几个操作 。

(1)保存当前虚拟机执行的指令savedpc到当前CallInfosavedpc中。此处保存下来是为了后面调用完毕之后恢复执行
(2)分别计算出待调用函数的basetop值,这些值的计算依赖于函数的参数数量
(3)从lua_Statebase_ci数组中分配一个新的CallInfo指针,存储前面两步计算出来的信息,切换到这个函数中准备调用。

可以看到,lua_State结构体中的topbase指针是与函数执行相关的变量,在函数执行前后都会有所变化。

需要注意的是,前后调用的函数中Lua栈的大小是有限的,同时CallInfo数组的大小也是有限的。 栈的使用和函数的嵌套层次都不能过多,以防这些资源、用尽了。 这就好比操作系统内核不可能无限制新建进程,也不可能无限制分配内存,资源总是有限的。

从lua-5.1.1中分离出来的lua_State实现代码

lua_State


Donde no hay harina, todo es mohina.