C

C和lua互操作实践

Lua, C, Redis

相信了解 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 的嵌入式使用有了一个大概的印象。这里可以回答以下的问题:

...

在Parent Shell中执行内置命令的方法

背景 #

最近在为 大仓项目(Monorepo) 制作一个脚手架,其中构思了一个自动替用户切换工作路径的工具(代码是通过模板初始化的,在结构上基本一致,但是代码文件较深,想要在terminal去和文件交互时,只能使用 cd 命令,费时费力),因此我期望一个小工具,能比较方便的帮我跳转到目标路径。预期的使用效果如下:

这个命令执行后,你可以从当前路径(任意路径)跳转到目标路径,那我就不用记我应该先跳转到根目录再前往目标目录了,少敲击很多次 tab。

PS: 后续计划给这个命令扩展一下历史记录,可以通过筛选匹配历史快速补全命令,提高效率。

chdir没作用? #

这个小工具是通过Go来编写的,我从文档中看到 os.Chdir 这个调用, 因此用它来试试:

// Chdir changes the current working directory to the named directory. 
// If there is an error, it will be of type *PathError.
func Chdir(dir string) error

结果我发现没有效果,通过 os.Getwd() 却能看到当前路径已经被改变了。这个切换的需求还没解决,我却产生了几个新的疑问:

  • cd 命令怎么实现的?
  • chdir 系统调用的使用和原理?

通过执行如下的命令,会发现 cd 命令并不是通过一个独立的可执行文件来实现的,而是内置在 bash 等 shell 程序中,其次通过查阅资料可以发现 大部分Linux发行版都部分符合 POSIX 标准。如果再检索一下shell程序的原理,我们会得出如下的结论:

$ which cd
cd: shell built-in command

shell 程序在执行内建命令时,是通过调用内部的函数的执行。而非内建的命令(如 git / gcc / gdb)都是通过 fork 一个进程来执行;cd 命令是通过 chdir [IEEE Std 1003.1-1988] 系统调用实现的;chdir 在 <uinstd.h> 头文件中定义;

...

访问量 访客数