它们是怎么交互的。skynet、openresty 都是深度使用 lua 语言的典范;学习 lua 不仅仅要学习基本用法,还要学会使用 c 与 lua 交互,这样才学会了 lua 作为胶水语言的精髓。
openresty(nginx + lua):openresty使用多进程,每个进程都有自己的lua虚拟机。
skynet中调用层次:skynet是多线程的,actor的调度和执行由线程池分配。
二、Lua环境搭建
(1)下载lua包并解压:
代码语言:Bash
自动换行
AI代码解释
wget -c http://www.lua.org/ftp/lua-5.3.0.tar.gz tar zxvf lua-5.3.0.tar.gz
(2)编译安装:
代码语言:Bash
自动换行
AI代码解释
cd lua-5.3.0 make linux sudo make install
(3)测试:
代码语言:Bash
自动换行
AI代码解释
lua -v # 或者 lua
代码语言:Lua
自动换行
AI代码解释
Lua 5.3.0 Copyright (C) 1994-2015 Lua.org, PUC-Rio >print("Hello World!") Hello World!
(4)使用lua库时,编译带上:
代码语言:Bash
自动换行
AI代码解释
-llua -ldl -lm
三、虚拟栈
- 栈中只能存放 lua 类型的值,如果想用 c 的类型存储在栈中,需要将 c 类型转换为 lua 类型。
- lua调用c的函数都得到一个新的栈,独立于之前的栈。
- c 调用 lua,每一个协程都有一个栈;需要维护这个栈。
- c 创建虚拟机时,伴随创建了一个主协程,默认创建一个虚拟栈。
- 无论何时 Lua 调用 C , 它都只保证至少有 LUA_MINSTACK 这么多的堆栈空间可以使用。 LUA_MINSTACK 一般被定义为 20 ,因此,只要不是不断的把数据压栈, 通常不用关心堆栈大小。
- c调用lua时,一定确保不要出现栈溢出问题,关注栈中有多少数据,不能超过栈的大小。特别是使用完函数后,一定要把栈清空。
对lua不熟悉可以查看Lua参考手册查看接口函数,里面都提供了c语言的api和lua的api。
四、c语言调用lua的函数
c代码只需要编译一次,lua可以随时改动;因为lua是动态语言。嵌入lua的好处是c只需要写一次代码,aly.onfirmax.com22编译一次程序,所有的变化都可以通过修改lua的代码,重启程序就可以了。当然,也可以在c语言里面添加逻辑实现自动检测lua文件的改变并自动加载新的lua文件。
4.1、实现步骤
(1)包含头文件:
代码语言:C
代码运行次数:0
自动换行运行
AI代码解释
#include <lua.h> #include <lauxlib.h> #include <lualib.h>
(2)创建lua虚拟机和加载lua库:
代码语言:C
代码运行次数:0
自动换行运行
AI代码解释
lua_State *L=luaL_newstate(); //创建lua虚拟机 luaL_openlibs(L); //加载lua库,比如math库、table库等
(3)加载lua文件到c语言的内存空间:
代码语言:C
代码运行次数:0
自动换行运行
AI代码解释
luaL_dofile(L,filename); //加载lua文件到c语言内存中,进行语法检查,不会编译
(4)取全局变量并压栈传参数:
代码语言:C
代码运行次数:0
自动换行运行
AI代码解释
lua_getglobal(L, func_name); // 查找lua文件中的全局函数,并压入虚拟栈 lua_pushinteger(L, 1); // 压栈,传入参数
(4)执行函数,lua_call 的参数中第二个是参入参数个数,第三个是返回值个数:
代码语言:C
代码运行次数:0
自动换行运行
AI代码解释
lua_call(L, 1, 0); // 栈弹出,执行函数
(5)关闭lua:
代码语言:C
代码运行次数:0
自动换行运行
AI代码解释
lua_close(L);
4.2、完整示例代码
vm_test.c
代码语言:C
代码运行次数:0
自动换行运行
AI代码解释
#include <lua.h> #include <lauxlib.h> #include <lualib.h> static void call_func(lua_State *L, const char* funcname) { lua_getglobal(L, funcname); // 查找lua文件中的全局函数,并压入虚拟栈 lua_pushinteger(L, 1); // 压栈,传入参数 lua_call(L, 1, 0); // 调用方法 } int main(int argc,char **argv) { lua_State *L=luaL_newstate(); //创建lua虚拟机 luaL_openlibs(L); //加载lua库,比如math库、table库等 if(argc>1) { lua_pushboolean(L,1); lua_setfield(L,LUA_REGISTRYINDEX,"LUA_NOENV"); if(LUA_OK!=luaL_dofile(L,argv[1])) //加载lua文件到c语言内存中,进行语法检查,不会编译 { const char * err=lua_tostring(L,-1); fprintf(stderr,"err:\t%s\n",err); return -1; } } // 执行lua的方法、函数 call_func(L, "Init"); call_func(L, "Loop"); call_func(L, "Release"); // 删除虚拟机 lua_close(L); return 0; }
vm_test.lua
代码语言:Lua
自动换行
AI代码解释
package.cpath = "luaclib/?.so" function Init(args) print("call [init] function", args) end function Loop() print("call [loop] function") for k, v in ipairs({1,2,3,4,5}) do print("value = " .. v) end end function Release() print("call [release] function") end
编译:
代码语言:Bash
自动换行
AI代码解释
gcc -o vm_test vm_test.c -llua -ldl -lm
执行:
代码语言:Bash
自动换行
AI代码解释
./vm_test vm_test.lua
执行结果:
代码语言:Bash
自动换行
AI代码解释
call [init] function 1 call [loop] function value = 1 value = 2 value = 3 value = 4 value = 5 call [release] function
五、Lua调用c语言的函数
lua只识别动态库,c语言需要编译被调用模块,然后编译成动态库。那么lua怎么识别c语言的动态库的函数呢?lua只识别以luaopen开头的函数。
5.1、原理
- c语言编写模块,编译成动态库。aly.sichelle.com22
- lua加载c语言的动态库:.so,.dll,.dylib。
- 约定读取动态库中以luaopen_*命名的函数。
- lua获取模块对象后,通过函数名可以找到c函数地址,从而调用c代码
5.2、实现步骤
(1)c语言中实现一个函数。
代码语言:C
代码运行次数:0
自动换行运行
AI代码解释
static int lecho (lua_State *L) { const char* str = lua_tostring(L, -1); fprintf(stdout, "%s\n", str); return 0; }
(2)创建一个导出给lua使用的数组,类型是luaL_Reg。
代码语言:C
代码运行次数:0
自动换行运行
AI代码解释
static const luaL_Reg l[] = {// 导出给lua使用数组 {"echo", lecho}, {NULL, NULL}, };
(3)c语言实现一个luaopen开头的函数,创建一张新的表,并预分配足够保存下函数指针数组内容的空间。
代码语言:C
代码运行次数:0
自动换行运行
AI代码解释
int luaopen_tbl_c(lua_State *L) { // local tbl = require "tbl.c" // 创建一张新的表,并预分配足够保存下数组 l 内容的空间 // luaL_newlibtable(L, l); // luaL_setfuncs(L, l, 0); luaL_newlib(L, l); return 1; }
(4)lua中导入c语言动态库并使用函数。
代码语言:Lua
自动换行
AI代码解释
package.cpath = "luaclib/?.so" local so = require "tbl.c"
(5)编译c语aly.segalinc.com11
5.3、从lua角度看调用过程
(1)lua首先会进行动态库的导入,即
代码语言:Lua
自动换行
AI代码解释
local so=require "table.c"
(2)Lua就会去查找动态库中以luaopen_*开头的函数,*就是展开了的c文件名,比如展开为luaopen_table_c的函数名。(3)找到函数后就执行函数,函数里面新建了一张函数地址表(函数数组),然后以table形式传给变量so。(4)接下来就可以使用c语言的函数了。
5.4、完整示例代码
注意:在Linux上直接使用apt-get安装的lua是没有自动安装lua库的,需要下载lua的源码编译安装。tbl.c
代码语言:C
代码运行次数:0
自动换行运行
AI代码解释
#include <lua.h> #include <lauxlib.h> #include <lualib.h> #include <stdio.h> static int lecho (lua_State *L) { const char* str = lua_tostring(L, -1); fprintf(stdout, "%s\n", str); return 0; } static const luaL_Reg l[] = {// 导出给lua使用数组 {"echo", lecho}, {NULL, NULL}, }; int luaopen_tbl_c(lua_State *L) { // local tbl = require "tbl.c" // 创建一张新的表,并预分配足够保存下数组 l 内容的空间 // luaL_newlibtable(L, l); // luaL_setfuncs(L, l, 0); luaL_newlib(L, l); return 1; }
test.lua
代码语言:Lua
自动换行
AI代码解释
package.cpath = "luaclib/?.so" local so = require "tbl.c" function Init(args) print("call [init] function", args) end function Loop() print("call [loop] function") for k, v in ipairs({1,2,3,4,5}) do so.echo(v) end end function Release() print("call [release] function") end
Makefile示例:
代码语言:Bash
自动换行
AI代码解释
# $@ 目标文件 $^ 所有的依赖文件 $< 第一个依赖文件 # -Wl,-E PLAT ?= linux # 在Makefile中,.PHONY后面的target表示的也是一个伪造的target, 而不是真实存在的文件target; # 注意Makefile的target默认是文件。 shell .PHONY : clean all cleanall LUA_CLIB_PATH ?= luaclib LUA_CLIB_SRC ?= lualib-src # LUA_CLIB = tbl uv reg1 reg2 ud LUA_CLIB = tbl LUA_INC ?= ./lua/src SHARED := -fPIC --shared EXPORT := -Wl,-E ifeq ($(PLAT), macosx) SHARED := -fPIC -dynamiclib -Wl,-undefined,dynamic_lookup endif CFLAGS = -g -O2 -Wall -I$(LUA_INC) CC ?= gcc LUA_STATICLIB := lua/src/liblua.a all : \ $(LUA_STATICLIB) \ test-vm \ $(foreach v, $(LUA_CLIB), $(LUA_CLIB_PATH)/$(v).so) $(LUA_STATICLIB) : cd lua && $(MAKE) CC='$(CC) -std=gnu99' $(PLAT) test-vm : test-vm.c $(CC) $(CFLAGS) $^ -o $@ -L$(LUA_INC) $(EXPORT) -llua -ldl -lm $(LUA_CLIB_PATH) : mkdir -p $(LUA_CLIB_PATH) $(LUA_CLIB_PATH)/tbl.so : $(LUA_CLIB_SRC)/lua-tbl.c | $(LUA_CLIB_PATH) $(CC) $(CFLAGS) $(SHARED) $^ -o $@ clean: rm -f $(LUA_CLIB_PATH)/*.so rm -f test-vm cleanall : rm -f $(LUA_CLIB_PATH)/*.so cd lua && $(MAKE) clean rm -f test-vm
六、C 闭包
- 通过 lua_pushcclosure 用来创建 C 闭包;
- 通过 lua_upvalueindex 伪索引来获取上值(lua 值);
- 可以为多个导出函数(c 导出函数给 lua 使用)共享上值,这样可以少传递一个参数。
示例:
lua-uv.c
代码语言:C
代码运行次数:0
自动换行运行
AI代码解释
#include <lua.h> #include <lauxlib.h> #include <lualib.h> #include <stdio.h> // 闭包实现: 函数 + 上值 luaL_setfuncs // lua_upvalueindex(1) // lua_upvalueindex(2) static int lecho (lua_State *L) { lua_Integer n = lua_tointeger(L, lua_upvalueindex(1)); n++; const char* str = lua_tostring(L, -1); fprintf(stdout, "[n=%lld]---%s\n", n, str); lua_pushinteger(L, n); lua_replace(L, lua_upvalueindex(1));// 更新UpValue return 0; } static const luaL_Reg l[] = { {"echo", lecho}, {NULL, NULL}, }; int luaopen_uv_c(lua_State *L) { // local tbl = require "tbl.c" luaL_newlibtable(L, l);// 1 lua_pushinteger(L, 0);// 2 ,绑定上值UpValue luaL_setfuncs(L, l, 1);// 上值 // luaL_newlib(L, l); return 1; }
test-uv.lua
代码语言:Lua
自动换行
AI代码解释
package.cpath = "luaclib/?.so" local so = require "uv.c" so.echo("hello world1") so.echo("hello world2") so.echo("hello world3") so.echo("hello world4") so.echo("hello world5") so.echo("hello world6") so.echo("hello world7") so.echo("hello world8") so.echo("hello world9") --./lua/src/lua test-uv.lua
用lua来执行:
代码语言:Bash
自动换行
AI代码解释
./lua/src/lua test-uv.lua
执行结果:
代码语言:Bash
自动换行
AI代码解释
[n=1]---hello world1 [n=2]---hello world2 [n=3]---hello world3 [n=4]---hello world4 [n=5]---hello world5 [n=6]---hello world6 [n=7]---hello world7 [n=8]---hello world8 [n=9]---hello world9
七、userdata在c语言的使用
userdata 是指向一块内存的指针,该内存由 lua 来创建,通过void *lua_newuserdatauv(lua_State *L, size_t sz,int nuvalue) 这个函数来创建。注意:这块内存大小必须是固定的,不能动态增加,但是这块内存中的指针指向的数据可以动态增加。lua 5.4的 userdata 可以绑定若干个 lua 值(又称uservalue)。
userdata 与 uservalue 的关系是引用关系,也就是 uservalue 的生命周期与 userdata 的生命周期一致。
- int lua_getiuservalue (lua_State *L, int idx, int n) 来获取绑定在 userdata 上的 uservalue。
- int lua_setiuservalue (lua_State *L, int idx, int n) 来设置 userdata 上的 uservalue。
使用示例:lua-ud.c
代码语言:C
代码运行次数:0
自动换行运行
AI代码解释
#include <lua.h> #include <lauxlib.h> #include <lualib.h> #include <stdio.h> #include <stdlib.h> #include <string.h> struct log { int count; }; static int lagain(lua_State *L) { struct log *p = (struct log *)luaL_checkudata(L, 1, "ud.log"); //检查userdata, 返回userdata地址 lua_getuservalue(L, -1); const char* str = lua_tostring(L, -1); fprintf(stdout, "ud[n=%d]----%s\n", p->count, str); return 0; } static int lecho(lua_State *L) { struct log *p = (struct log *)luaL_checkudata(L, 1, "ud.log");//检查userdata, 返回userdata地址 const char* str = lua_tostring(L, -1); // 获取UpValue p->count++; lua_setuservalue(L, -2); fprintf(stdout, "ud[n=%d]----%s\n", p->count, str); return 0; } static int lnew (lua_State *L) { // lua虚拟机分配一块内存作为userdata的空间 struct log *q = (struct log*)lua_newuserdata(L, sizeof(struct log)); q->count = 0; lua_pushstring(L, ""); //绑定UpValue,初始值是“” lua_setuservalue(L, -2); // 从栈上弹出一个值并将其设为给定索引处用户数据的关联值 if (luaL_newmetatable(L, "ud.log")) { // 如果注册表中已存在键 "ud.log",返回0。 否则为用户数据的元表创建一张新表 // 数组保存函数地址 luaL_Reg m[] = { {"echo", lecho}, {"again", lagain}, {NULL, NULL}, }; // 创建一张新的表,并预分配足够保存下数组 m 内容的空间 luaL_newlib(L, m); // 设置元表__index段,而 -2是栈顶的索引对应的那个值,当key不存在或不是表时调用m表。 lua_setfield(L, -2, "__index"); lua_setmetatable(L, -2); } return 1; } // 数组 l 内容,保存函数地址 static const luaL_Reg l[] = { {"new", lnew}, {NULL, NULL}, }; int luaopen_ud_c(lua_State *L) { // 创建一张新的表,并预分配足够保存下数组 l 内容的空间 luaL_newlib(L, l); return 1; }
test-ud.lua
代码语言:Lua
自动换行
AI代码解释
package.cpath = "luaclib/?.so" local so = require "ud.c" local ud = so.new() ud:echo("hello world1") ud:again() ud:echo("hello world2") ud:again() ud:echo("hello world3") ud:again() ud:echo("hello world4") ud:again() ud:echo("hello world5") ud:again() ud:echo("hello world6") ud:again() ud:echo("hello world7") ud:again() ud:echo("hello world8") ud:again() ud:echo("hello world9") ud:again() --./lua/src/lua test-ud.lua
用lua来测试:
代码语言:Bash
自动换行
AI代码解释
./lua/src/lua test-ud.lua
执行结果:
代码语言:Bash
自动换行
AI代码解释
[n=1]---hello world1 [n=1]---hello world1 [n=2]---hello world2 [n=2]---hello world2 [n=3]---hello world3 [n=3]---hello world3 [n=4]---hello world4 [n=4]---hello world4 [n=5]---hello world5 [n=5]---hello world5 [n=6]---hello world6 [n=6]---hello world6 [n=7]---hello world7 [n=7]---hello world7 [n=8]---hello world8 [n=8]---hello world8 [n=9]---hello world9 [n=9]---hello world9
八、注册表在c语言的使用
可以用来在多个 c 库中共享 lua 数据(包括 userdata 和lightuserdata )。
- 一张预定义的表,用来保存任何 c 代码想保存的 lua 值。
- 使用 LUA_REGISTRYINDEX 来索引。
Lua有一个全局表,有一个LUA_REGISTRYINDEX的key,LUA_REGISTRYINDEX的value又是一个表,专门用来存放全局数据([key]=userdata)。
使用示例:lua-reg1.c
代码语言:C
代码运行次数:0
自动换行运行
AI代码解释
#include <lua.h> #include <lauxlib.h> #include <lualib.h> #include <stdio.h> static int lecho (lua_State *L) { lua_getfield(L, LUA_REGISTRYINDEX, "reg.c"); // 此时栈顶放了一张表如下 // {["reg.c"] = {1000, 2000}} lua_rawgeti(L, -1, 1); // -1 是读取栈顶的表 1 是指读这个表中索引为1 的field // 相当于 取出 1000 放在栈顶 // 此时 栈上 1 的位置 为 table 2的位置为 1000 lua_Integer n = lua_tointeger(L, -1); // 将栈顶的值转化为整数 n++; lua_pop(L, 1); // 将栈顶的值 pop 出 // 此时栈上只有一个 table lua_pushinteger(L, n); // 将运算后的 n push 到栈上 // 此时栈上 1 的位置为 table 2的位置为 1001 lua_rawseti(L, -2, 1); // 将 栈顶的值 设置到 1 位置中table 索引为 1的位置 // 此时 table = {1001,2000} // 此时栈上只有一个 table 因为设置的时候 把栈顶的值丢掉了 const char* str = lua_tostring(L, 1); fprintf(stdout, "reg1[n=%lld]----%s\n", n, str); return 0; } static const luaL_Reg l[] = { {"echo", lecho}, {NULL, NULL}, }; int luaopen_reg1_c(lua_State *L) { // local tbl = require "tbl.c" // 创建一张新的表,并预分配足够保存下数组 l 内容的空间 // luaL_newlibtable(L, l); // luaL_setfuncs(L, l, 0); {[reg.c] = {1000,2000}} // { // ["reg.c"] = {1000, 2000} // } if (lua_getfield(L, LUA_REGISTRYINDEX, "reg.c") == LUA_TNIL) { lua_createtable(L, 2, 0); //1 lua_pushinteger(L, 1000); //2 lua_rawseti(L, -2, 1); //1 {1000} lua_pushinteger(L, 2000);//2 lua_rawseti(L, -2, 2);// 1 [reg.c] = {1000,2000} lua_setfield(L, LUA_REGISTRYINDEX, "reg.c"); fprintf(stdout, "luaopen_reg1_c 注册表 reg.c\n"); } luaL_newlib(L, l); return 1; }
test-reg.lua
代码语言:Lua
自动换行
AI代码解释
package.cpath = "luaclib/?.so" local so1 = require "reg1.c" so1.echo("hello world") so1.echo("hello world") so1.echo("hello world")
九、总结
- 热更新,在c代码中实现当lua文件发生改变时自动创建一个新的lua虚拟机加载新的lua文件,加载完成后销毁旧的lua虚拟机,就可以不需要重启程序。
- lua函数其实是c语言类型的函数,但是lua函数比c语言函数多了UpValue,即lua=c+UpValue。
- 牢记,c调用lua的函数时,一定要维护虚拟栈;lua调用c时不需要维护虚拟栈(因为每次调用都会生成一个新的栈)。