1,lua_State在Lua中的定义如下:
struct lua_State {
CommonHeader;
lu_byte status;
StkId top; /* first free slot in the stack */
global_State *l_G;
CallInfo *ci; /* call info for current function*/
const Instruction *oldpc; /* last pc traced */
StkId stack_last;
/* last free slot in the stack*/
StkId stack; /* stack base */
int stacksize;
unsigned short nny;
/* number of non-yieldable calls in stack */
unsigned short nCcalls;
/* number of nested C calls*/
lu_byte hookmask;
lu_byte allowhook;
int basehookcount;
int hookcount;
lua_Hook hook;
GCObject *openupval;
/* list of open upvalues in this stack */
GCObject *gclist;
struct lua_longjmp *errorJmp;
/* current error recover point */
ptrdiff_t errfunc;
/* current error handling function (stack index) */
CallInfo base_ci;
/* CallInfo for first level (C calling Lua) */
};
2,创建一个虚拟机,返回指针类型lua_State
不知道是不是老的版本,我在网上看到有网友的例子里这样写:
lua_State *L = lua_open(); /* opens Lua */
我下了Lua5.2编译后执行例子是不行的,找了lua源码也没有找到lua_open()的定义。
暂不论它,现在生成lua_State对像的方式应如下:
lua_State *L = luaL_newstate();
luaL_newstate在源码中的定义如下:
LUALIB_API lua_State *luaL_newstate (void)
{
lua_State *L = lua_newstate(l_alloc, NULL);
if (L) lua_atpanic(L, &panic);
return L;
}
3,luaL_openlibs在源码中的定义:
LUALIB_API void luaL_openlibs (lua_State *L)
{
const luaL_Reg *lib;
/* call open functions from 'loadedlibs' and set results to global table */
for (lib = loadedlibs; lib->func; lib++) {
luaL_requiref(L, lib->name, lib->func, 1);
lua_pop(L, 1); /* remove lib */
}
/* add open functions from 'preloadedlibs' into 'package.preload' table */
luaL_getsubtable(L, LUA_REGISTRYINDEX, "_PRELOAD");
for (lib = preloadedlibs; lib->func; lib++) {
lua_pushcfunction(L, lib->func);
lua_setfield(L, -2, lib->name);
}
lua_pop(L, 1); /* remove _PRELOAD table */
}
把所有标准类库加载到指定的虚拟机。
4,意义
“Lua脚本的编译执行是相互独立的,在不同的进程上执行,通过luaL_newstate()函数可以申请一个虚拟机,
返回指针类型lua_State。今后其他所有Lua Api函数的调用都需要此指针作为第一参数,用来指定某个虚拟机。”
5,lua_close
代码:
LUA_API void lua_close (lua_State *L) {
L = G(L)->mainthread; /* only the main thread can be closed */
lua_lock(L);
luai_userstateclose(L);
close_state(L);
}
文档说明:
销毁指定虚拟机的所有对像(如果有垃圾回收相关的无方法则会调用该方法)
并收回所有由该虚拟机动态分配产生的内存,在有些平台下我们不需要调用此函数,因为当主程序退出时,
资源会被自然的释放掉,但是但一个长时间运行的程序,比如后台运行的web服务器,
需要立即回收虚拟机资源以避免内存过高占用。
c语言中如何执行lua代码:
c语言测试代码:
lua_State* L = NULL;
// 内部调用lua函数
double f(double x, double y)
{
double z;
lua_getglobal(L, "f"); // 获取lua函数f
lua_pushnumber(L, x); // 压入参数x和y
lua_pushnumber(L, y);
if(lua_pcall(L, 2, 1, 0) != 0)
error(L, "error running function 'f': %s", lua_tostring(L, -1));
if(!lua_isnumber(L, -1))
error(L, "function 'f' must return a number");
z = lua_tonumber(L, -1);
lua_pop(L, 1);
return z;
}
int main(void)
{
L = lua_open();
luaL_openlibs(L);
if(luaL_loadfile(L, "c:\\luatest\\functest.lua") || lua_pcall(L, 0, 0, 0))
error(L, "cannot run configuration file: %s", lua_tostring(L, -1));
printf("%f\n", f(1.0, 2.0));
return 0;
}
functest.lua:
f = function(a, b)
return a + b
end
这其中最关键的是调用函数的使用,在C中调用Lua函数的API主要由以下几个:
(1)void lua_call (lua_State *L, int nargs, int nresults);
函数调用,nargs表示参数的个数,nresults表示返回值的个数
首先将lua函数压栈,然后将参数依次压栈,最后调用函数即可
函数调用时,参数和函数都会pop出栈,调用返回后,结果会push进栈
nresults==LUA_MULTRET,所有的返回值都会push进栈
nresults!=LUA_MULTRET,返回值个数根据nresults来调整
Lua语句:
a = f("how", t.x, 14)
在C中的实现:
lua_getglobal(L, "f"); // 函数入栈
lua_pushstring(L, "how"); // 参数1入栈
lua_getglobal(L, "t"); // 表t入栈
lua_getfield(L, -1, "x"); // 参数2入栈
lua_remove(L, -2); // 跳t出栈
lua_pushinteger(L, 14); // 参数3入栈
lua_call(L, 3, 1); // 调用函数,参数和函数都会出栈
lua_setglobal(L, "a"); // 给a赋值,栈顶出栈
上述代码执行完毕后,堆栈状态恢复原样。
(2)int lua_pcall (lua_State *L, int nargs, int nresults, int msgh);
函数调用,在安全模式下,并且可以添加错误处理函数。
如果调用期间发生error,lua_pcall会捕获之,然后push stack一个错误信息(会先将函数和参数pop出栈),并且返回一个error code(非0的一个值)。
发生error时,如果指定了错误处理函数,会在error message入栈前调用错误处理函数,具体由msgh参数来决定:
(1)msgh==0,不指定错误处理函数,入栈信息不变;
(2)msgh!=0,msgh表示错误处理函数的堆栈index,错误处理函数会以error message为参数,并将返回的新的error message入栈。主要用来给error message添加 更多的debug信息,比如堆栈跟踪,因为这些信息在pcall调用完之后是收集不到的。
函数返回代码:
LUA_OK(0):调用成功
LUA_ERRRUN:runtime error
LUA_ERRMEM:内存分配错误,这种情况下不会调用错误处理函数
LUA_ERRERR:调用错误处理函数时出错,当然,不会再进一步调用错误处理函数
LUA_ERRGCMM:调用metamethod.__gc时报错,由gc引起,和函数本身没关系
(3)int lua_pcallk (lua_State *L, int nargs, int nresults, int errfunc, int ctx, lua_CFunction k);
函数调用,在安全模式下,并且允许函数yield。
ngx每个worker中执行下面函数创建各自的lua虚拟机。
ngx_http_lua_init_worker
---》ctx = ngx_http_lua_create_ctx(r);
---》 L = ngx_http_lua_init_vm(lmcf->lua, lmcf->cycle, r->pool, lmcf, r->connection->log, &cln);
向HTTP模块介入的方式:
通过向全局的ngx_http_core_main_conf_t的phases[xxx]. handlers数组中添加ngx_http_handler_pt方法来实现。该方法是通过必定会被调用的ngx_http_module_t的postconfiguration方法向全局的ngx_http_core_main_conf_t的phases[xxx]. handlers数组中添加ngx_http_handler_pt方法来达成的,这个处理方法将应用于全部的http请求。
当access_by_lua_file指令配置中存在时候处理函数ngx_http_lua_access_by_lua中设置如下:
llcf->access_handler = ngx_http_lua_access_handler_file;
lmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_lua_module);
lmcf->requires_access = 1;
lmcf->requires_capture_filter = 1;
ngx_http_lua_module模块的ngx_http_lua_init,/* postconfiguration */中功能如下:
当requires_access使能时候则向该阶段注册处理函数。
if (lmcf->requires_access) {
h = ngx_array_push(&cmcf->phases[NGX_HTTP_ACCESS_PHASE].handlers);
if (h == NULL) {
return NGX_ERROR;
}
*h = ngx_http_lua_access_handler;//最终会执行llcf->access_handler(r);即前面设置的ngx_http_lua_access_handler_file函数。
}
例如通过指令access_by_lua_file加载lua文件处理流程:
ngx_http_lua_access_handler_file
--》rc = ngx_http_lua_cache_loadfile(r->connection->log, L, script_path, llcf->access_src_key);
--》rc = ngx_http_lua_clfactory_loadfile(L, (char *) script);
--》最后执行ngx_http_lua_access_by_chunk(L, r)--》 co = ngx_http_lua_new_thread(r, L, &co_ref);--》 co = lua_newthread(L);
多个线程
参考:https://blog.csdn.net/sharemyfree/article/details/46754915
从C API的角度来看,将线程想象成一个栈可能更形象些。从实现的观点来看,一个线程的确就是一个栈。每个栈都保留着一个线程中所有未完成的函数调用信息,这些信息包括调用的函数、每个调用的参数和局部变量。也就是说,一个栈拥有一个线程得以继续运行的所有信息。因此,多个线程就意味着多个独立的栈。
当调用Lua C API中的大多数函数时,这些函数都作用于某个特定的栈。当我们调用lua_pushnumber时,就会将数字压入一个栈中,那么Lua是如何知道该使用哪个栈的呢?答案就在类型lua_State中。这些C API的第一个参数不仅表示了一个Lua状态,还表示了一个记录在该状态中的线程。
只要创建一个Lua状态,Lua就会自动在这个状态中创建一个新线程,这个线程称为“主线程”。主线程永远不会被回收。当调用lua_close关闭状态时,它会随着状态一起释放。调用lua_newthread便可以在一个状态中创建其他的线程。
lua_State *lua_newthread(lua_State *L)
这个函数返回一个lua_State指针,表示新建的线程。它会将新线程作为一个类型为“thread”的值压入栈中。如果我们执行了:
L1 = lua_newthread(L);
现在,我们拥有了两个线程L和L1,它们内部都引用了相同的Lua虚拟机。每个线程都有其自己的栈。新线程L1以一个空栈开始运行,老线程L的栈顶就是这个新线程。
除了主线程以外,其它线程和其它Lua对象一样都是垃圾回收的对象。当新建一个线程时,线程会压入栈,这样能确保新线程不会成为垃圾,而有的时候,你在处理栈中数据时,不经意间就把线程弹出栈了,而当你再次使用该线程时,可能导致找不到对应的线程而程序崩溃。为了避免这种情况的发生,可以保持一个对线程的引用,比如在注册表中保存一个对线程的引用。
当拥有了一个线程以后,我们就可以像主线程那样来使用它,以前博文中提到的对栈的操作,对这个新的线程都适用。然而,使用多线程的目的不是为了实现这些简单的功能,而是为了实现协同程序。
为了挂起某些协同程序的执行,并在稍后恢复执行,我们可以使用lua_resume函数来实现。
int lua_resume(lua_State *L, int narg);
lua_resume可以启动一个协同程序,它的用法就像lua_call一样。将待调用的函数压入栈中,并压入其参数,最后在调用lua_resume时传入参数的数量narg。这个行为与lua_pcall类似,但有3点不同。
lua_resume没有参数用于指出期望的结果数量,它总是返回被调用函数的所有结果;
它没有用于指定错误处理函数的参数,发生错误时不会展开栈,这就可以在发生错误后检查栈中的情况;
如果正在运行的函数交出(yield)了控制权,lua_resume就会返回一个特殊的代码LUA_YIELD,并将线程置于一个可以被再次恢复执行的状态。
当lua_resume返回LUA_YIELD时,线程的栈中只能看到交出控制权时所传递的那些值。调用lua_gettop则会返回这些值的数量。若要将这些值移到另一个线程,可以使用lua_xmove。
为了恢复一个挂起线程的执行,可以再次调用lua_resume。在这种调用中,Lua假设栈中所有的值都是由yield调用返回的,当然了,你也可以任意修改栈中的值。作为一个特例,如果在一个lua_resume返回后与再次调用lua_resume之间没有改变过线程栈中的内容,那么yield恰好返回它交出的值。如果能很好的理解这个特例是什么意思,那就说明你已经非常理解Lua中的协同程序了,如果你还是不知道我说的这个特例是什么意思,请再去读一遍《Lua中的协同程序》,如果你还不懂,那你就在下放留言吧(提醒:这个特例主要利用的是resume-yield之间的传参规则)。