nginx的worker进程如何创建lua虚拟机的?

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/yangguangmeng/article/details/83153608

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));

扫描二维码关注公众号,回复: 5302166 查看本文章

            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之间的传参规则)。

猜你喜欢

转载自blog.csdn.net/yangguangmeng/article/details/83153608