C和lua互操作实践
August 22, 2023
相信了解 redis 和 openresty 的小伙伴们都知道 lua 代码可以嵌入这两种程序中运行,极大的提高了软件的扩展性;尤其是 openresty 中,通过使用 lua 我们可以很快速(相比c)的定制web服务器,或者增强 nginx 的功能。那么 lua 是如何嵌入到这些程序中的呢?lua 和 c 是如何互操作的呢?
下文的相关环境和工具版本为:Lua 5.4.6; Mac OS 13.4.1 (Darwin Kernel Version 22.5.0) arm64 M2 pro; Apple clang version 14.0.3 (clang-1403.0.22.14.1)
redis 中的 lua #
下面展示了一段 redis 中操作 lua API 的代码:
这里出现了很多 lua_ 开头的函数,这些函数都是 lua 库中的函数,redis 通过这些函数来操作 lua 环境, 这里先不展开讲,后面会详细介绍。
更多的代码,如 luaRegisterRedisAPI 就不展示了,有兴趣的可以去看源码。
// redis-v7.2/src/eval.c#183
/* 初始化 lua 环境
*
* redis 首次启动时调用,此时 setup 为 1,
* 这个函数也会在 redis 的其他生命周期中被调用,此时 setup 为 0,但是被简化为 scriptingReset 调用。
*/
void scriptingInit(int setup) {
lua_State *lua = lua_open();
if (setup) {
// 首次启动时,初始化 lua 环境 和 ldb (Lua debugger) 的一些数据结构
lctx.lua_client = NULL;
server.script_disable_deny_script = 0;
ldbInit();
}
/* 初始化 lua 脚本字典,用于存储 sha1 -> lua 脚本的映射
* 用户使用 EVALSHA 命令时,从这个字典中查找对应的 lua 脚本。
*/
lctx.lua_scripts = dictCreate(&shaScriptObjectDictType);
lctx.lua_scripts_mem = 0;
/* 注册 redis 的一些 api 到 lua 环境中 */
luaRegisterRedisAPI(lua);
/* 注册调试命令 */
lua_getglobal(lua,"redis");
/* redis.breakpoint */
lua_pushstring(lua,"breakpoint");
lua_pushcfunction(lua,luaRedisBreakpointCommand);
lua_settable(lua, -3);
/* redis.debug */
lua_pushstring(lua,"debug");
lua_pushcfunction(lua,luaRedisDebugCommand);
lua_settable(lua,-3);
/* redis.replicate_commands */
lua_pushstring(lua, "replicate_commands");
lua_pushcfunction(lua, luaRedisReplicateCommandsCommand);
lua_settable(lua, -3);
lua_setglobal(lua,"redis");
/* 注册一个错误处理函数,用于在 lua 脚本执行出错时,打印出错信息。
* 需要注意的是,当错误发生在 C 函数中时,我们需要打印出错的 lua 脚本的信息,
* 这样才能帮助用户调试 lua 脚本。
*/
{
char *errh_func = "local dbg = debug\n"
"debug = nil\n"
"function __redis__err__handler(err)\n"
" local i = dbg.getinfo(2,'nSl')\n"
" if i and i.what == 'C' then\n"
" i = dbg.getinfo(3,'nSl')\n"
" end\n"
" if type(err) ~= 'table' then\n"
" err = {err='ERR ' .. tostring(err)}"
" end"
" if i then\n"
" err['source'] = i.source\n"
" err['line'] = i.currentline\n"
" end"
" return err\n"
"end\n";
luaL_loadbuffer(lua,errh_func,strlen(errh_func),"@err_handler_def");
lua_pcall(lua,0,0,0);
}
/* 创建一个 lua client (没有网络连接),用于在 lua 环境中执行 redis 命令。
* 这个客户端没必要在 scriptingReset() 调用时重新创建。
*/
if (lctx.lua_client == NULL) {
lctx.lua_client = createClient(NULL);
lctx.lua_client->flags |= CLIENT_SCRIPT;
/* We do not want to allow blocking commands inside Lua */
lctx.lua_client->flags |= CLIENT_DENY_BLOCKING;
}
/* Lock the global table from any changes */
lua_pushvalue(lua, LUA_GLOBALSINDEX);
luaSetErrorMetatable(lua);
/* Recursively lock all tables that can be reached from the global table */
luaSetTableProtectionRecursively(lua);
lua_pop(lua, 1);
lctx.lua = lua;
}
通过这部分代码,应该对于 lua 的嵌入式使用有了一个大概的印象。这里可以回答以下的问题:
...