zoukankan      html  css  js  c++  java
  • lua堆栈

    lua堆栈

    来源 https://blog.csdn.net/suhuaiqiang_janlay/article/details/56702381

    来源 https://blog.csdn.net/suhuaiqiang_janlay/article/details/63683036

    一、Lua脚本语言

    1. 概述

    Lua是一种脚本编程语言,与一般脚本语言不同,被称为是嵌入式的脚本语言。Lua最著名的应用是在暴雪公司的网络游戏魔兽世界中。

    Lua语言可以独立进行编程,但这不是其主要的使用方式。Lua最典型的用法,是作为一个库,嵌入到其他大型语言(称为宿主语言)的应用程序之中,为应用程序提供参数配置或逻辑描述等功能,带来前所未有的灵活性。

     

    Lua常见的宿主语言有:C/C++、Java、.NET,甚至脚本语言如PHP、Ruby。


    2. Lua与相似解决方案的比较

     

    Lua体积很小,往往使用静态链接嵌入到程序内部,在发布应用时不需要附带任何的运行时支持。

    3. 宿主语言中嵌入Lua的工作流程

    (1)宿主语言建立Lua解释器对象

    (2)将宿主语言实现的Lua扩展,如函数等,注册到Lua解释器中,供其使用。

    (3)读入Lua源程序或预先编译好的Lua程序。

    (4)执行读入的Lua程序。

    二、Lua虚拟机的初始化

    Lua工作的核心是Lua虚拟机,宿主语言在加载和执行Lua脚本时,做的第一件事情就是创建并初始化Lua虚拟机。


    1. 创建Lua虚拟机

    lua_State *lua_newstate(lua_Alloc f, void *ud) API可以为我们创建一个新的独立的Lua虚拟机。

    参数指定了虚拟机中的内存分配策略,例如我们已经在自己的代码中实现了内存池,这时候只需要写一个符合lua_Alloc原型的适配器,然后指定为Lua的内存分配器就可以了,使得内存分配更加灵活。当然,如果不想自定义内存分配策略,也可以使用luaL_newstate,这样Lua会帮你定义默认的内存分配策略。

    我们可以先来看一下luaL_newstate的源码:

    // luaconf.h
    /*
    @@ LUA_EXTRASPACE defines the size of a raw memory area associated with
    ** a Lua state with very fast access.
    ** CHANGE it if you need a different size.
    */
    #define LUA_EXTRASPACE        (sizeof(void *))
     
    // lstate.c
    /*
    ** thread state + extra space
    */
    typedef struct LX {
      lu_byte extra_[LUA_EXTRASPACE];
      lua_State l;
    } LX;
     
    /*
    ** Main thread combines a thread state and the global state
    */
    typedef struct LG {
      LX l;
      global_State g;
    } LG;
     
    // lstate.c
    LUA_API lua_State *lua_newstate (lua_Alloc f, void *ud) {
      int i;
      lua_State *L;
      global_State *g;
      LG *l = cast(LG *, (*f)(ud, NULL, LUA_TTHREAD, sizeof(LG)));
      if (l == NULL) return NULL;
      L = &l->l.l;
      g = &l->g;
      ......
      return L;
    }

    可见,通过luaL_newstate 创建Lua虚拟机时,第一块申请的内存将用来存储global_State(全局状态机)和lua_State(主线程)实例。为了避免内存碎片的产生,同时减少内存分配和释放的次数,Lua采用了一个小技巧:利用一个LG结构,把分配lua_State和global_State的行为关联在一起。这个LG结构是在C文件内部定义,而不存在公开的H文件中,仅供该C代码文件使用,因此这种依赖数据结构内存布局的用法负作用不大。

     

    2. 关于global_State和lua_State

    在一个独立的Lua虚拟机中,global_State是一个全局的结构, 而lua_State可以有多个。

    global_State

    global_State结构,我们可以称之为Lua全局状态机。从Lua的使用者角度来看,global_State结构是完全感知不到的:我们无法用Lua公开的API获取到它的指针、句柄或引用,而且实际上我们也并不需要引用到它。但是对于Lua的实现来说,global_State是十分重要的部分。

    它管理着Lua中全局唯一的信息,主要是以下功能:


    (1)内存分配策略及其参数

    在调用lua_newstate的时候配置它们. 也可以通过lua_getallocf和lua_setallocf随时获取和修改它

    (2)字符串的hashtable

    全局的字符串哈希表,即保存那些短字符串,使得整个虚拟机中短字符串只有一份实例。具体参见 Lua字符串处理

    (3)垃圾回收相关的信息,内存使用统计量

    (4)panic, 当无保护调用发生时, 会调用该函数, 默认是null, 可以通过lua_atpanic配置.(用于异常处理)

    (5)注册表, 注册表是一个全局唯一的table

    (6)记录lua中元方法名称 和 基本类型的元表

    [注意, lua中table和userdata每个实例可以拥有自己的独特的元表--记录在table和userdata的mt字段, 其他类型是每个类型共享一个元表--就是记录在这里].

    (7)upvalue链表

    (8)主lua_State, 一个lua虚拟机中, 可以有多个lua_State, lua_newstate会创建出一个lua_State(称为主线程), 并邦定到global_state的主lua_State上

    lua_State

    线程,这里线程的概念区别于操作系统的线程,实际上也是Lua中定义的一种状态机。lua_State主要是管理一个lua虚拟机的执行环境, 一个lua虚拟机可以有多个执行环境。


    (1)要注意的是, 和nil, string, table一样,lua_State也是lua中的一种基本类型, lua中的表示是TValue {value = lua_State, tt = LUA_TTHREAD}

    (2)lua_State的成员和功能

    a. 栈的管理, 包括管理整个栈和当前函数使用的栈的情况

    b. CallInfo的管理, 包括管理整个CallInfo数组和当前函数的CallInfo

    c. hook相关的, 包括hookmask, hookcount, hook函数等

    d. 全局表l_gt, 注意这个变量的命名, 很好的表现了它其实只是在本lua_State范围内是全局唯一的的, 和注册表不同, 注册表是lua虚拟机范围内是全局唯一的
    e. gc的一些管理和当前栈中upvalue的管理 
    f.  错误处理的支持
    (3)从lua_State的成员可以看出来, lua_State最主要的功能就是函数调用以及和c的通信.

    3. lua_newstate函数的流程

    (1)新建一个global_state和一个lua_State

    (2)初始化, 包括给g_s创建注册表, g_s中各个类型的元表的默认值全部置为0

    (3)给l_s创建全局表, 预分配l_s的CallInfo和stack空间

    (4)其中涉及到了内存分配统统使用lua_newstate传进来的内存分配器分配

    1:lua_push* 压栈API

    lua_push*这些API是把C语言里面的值封装成Lua类型的值压入栈中的,对于那些需要垃圾回收的元素,在压入栈时,都会在Lua(也就是Lua虚拟机中)生成一个副本。比如lua_pushstring(lua_State *L, const char *s)会向中栈压入由s指向的以''结尾的字符串,在C中调用这个函数后,我们可以任意释放或修改由s指向的字符串,也不会出现问题,原因就是在执行lua_pushstring过程中Lua会生成一个内部副本。实质上,Lua不会持有指向外部字符串的指针,也不会持有指向任何其他外部对象的指针(除了C函数,因为C函数总是静态的)。

    总之,一旦C中值被压入栈中,Lua就会生成相应的结构(实质就是Lua中实现的相应数据类型)并管理(比如自动垃圾回收)这个值,从此不会再依赖于原来的C值。

    2:lua栈大小

    lua 只保证在从 Lua 进入 C 的边界上提供额外的 LUA_MINSTACK 个 slot 。这个值默认为 20 ,一般是够用的。正因为一般够用,反而容易被编写 C 扩展的同学忽视。尤其是在 C 扩展的代码里有 C 层次上的递归时,非常容易在边界情况下栈溢出。因为 Lua 的 stack 实际上又经常留出超过 LUA_MINSTACK 的空间,这种 bug 不易察觉。记住:如果你在 C 扩展中做复杂的事情,一定要记得在使用 lua stack 前,用 luaL_checkstack 留够你需要的空间。


    OK,这篇文章主要是借lua_newstate讲述global_State和lua_State的结构与作用,希望对大家了解Lua工作环境有一点帮助。

    下一篇将讲述Lua栈相关的内容,更新中。。。

    参考文献:

    http://www.cnblogs.com/ringofthec/archive/2010/11/09/lua_State.html

    http://blog.csdn.net/maximuszhou/article/details/46277695

    一、Lua栈

    1. 什么是lua栈


    lua的栈类似于以下的定义, 它是在创建lua_State的时候创建的:  TValue stack[max_stack_len]  // 欲知内情可以查 lstate.c 的stack_init函数

    存入栈的数据类型包括数值, 字符串, 指针, talbe, 闭包等, 下面是一个栈的例子:

     


    2. TValue结构

    压入的类型有数值, 字符串, 表和闭包[在c中看来是不同类型的值], 但是最后都是统一用TValue这种数据结构来保存的:), 下面用图简单的说明一下这种数据结构:      

     

      p -- 可以存一个指针, 实际上是lua中的light userdata结构
      n -- 所有的数值存在这里, 不过是int , 还是float

      b -- Boolean值存在这里, 注意, lua_pushinteger不是存在这里, 而是存在n中, b只存布尔
      gc -- 其他诸如table, thread, closure, string需要内存管理垃圾回收的类型都存在这里
      gc是一个指针, 它可以指向的类型由联合体GCObject定义, 从图中可以看出, 有string, userdata, closure, table, proto, upvalue, thread


       从上面的图可以的得出如下结论:
       1. lua中, number, boolean, nil, light userdata四种类型的值是直接存在栈上元素里的, 和垃圾回收无关.

       2. lua中, string, table, closure, userdata, thread存在栈上元素里的只是指针, 他们都会在生命周期结束后被垃圾回收.

    lua value 和 c value的对应关系


                 c          lua
             nil           无    {value=0, tt = t_nil}
          boolean       int  非0, 0    {value=非0/0, tt = t_boolean}
          number       int/float等   1.5    {value=1.5, tt = t_number}
       lightuserdata    void*, int*, 各种*  point    {value=point, tt = t_lightuserdata}
          string          char  str[]    {value=gco, tt = t_string}   gco=TString obj
          table            无    {value=gco, tt = t_table}  gco=Table obj
          userdata            无    {value=gco, tt = t_udata} gco=Udata obj
          closure            无    {value=gco, tt = t_function} gco=Closure obj

    二、通过Lua栈实现和C++的通讯

    1. Lua和C通讯的约定

        lua和c通信时有这样的约定: 所有的lua中的值由lua来管理, c++中产生的值lua不知道, 类似表达了这样一种意思: "如果你(c/c++)想要什么, 你告诉我(lua), 我来产生, 然后放到栈上, 你只能通过api来操作这个值, 我只管我的世界", 这个很重要, 因为:
         "如果你想要什么, 你告诉我, 我来产生"就可以保证, 凡是lua中的变量, lua要负责这些变量的生命周期和垃圾回收, 所以, 必须由lua来创建这些值(在创建时就加入了生命周期管理要用到的簿记信息)
         "然后放到栈上, 你只能通过api来操作这个值", lua api给c提供了一套完备的操作界面, 这个就相当于约定的通信协议, 如果lua客户使用这个操作界面, 那么lua本身不会出现任何"意料之外"的错误.
         "我只管我的世界"这句话体现了lua和c/c++作为两个不同系统的分界, c/c++中的值, lua是不知道的, lua只负责它的世界。

    2. 栈的索引规则

    栈底到栈顶索引呈+1递增的规律,同时索引有正数索引和负数索引两种表示方式:

    1. 正数索引,不需要知道栈的大小,我们就能知道栈底在哪,栈底的索引永远是1

    即:栈底是1,然后一直到栈顶逐渐+1
    2. 负数索引,不需要知道栈的大小,我们就能知道栈顶在哪,栈顶的索引永远是-1

    即:栈顶是-1,然后一直到栈底逐渐-1

    3. Lua和C++通讯实例

    假设在一个lua文件中有如下定义:

    -- hello.lua 文件
    myName = "beauty girl"

    想要在C++中获取到myName的值,可以lua_getglobal 来获取:

    /* 取得table变量,在栈顶 */  
    lua_getglobal(pL, "myName ");  

    lua_getglobal的处理过程如下:(请注意红色数字,代表通信顺序)

     

    1) C++想获取Lua的myName字符串的值,所以它把myName放到Lua堆栈(栈顶),以便Lua能看到
    2) Lua从堆栈(栈顶)中获取myName,此时栈顶再次变为空
    3) Lua拿着这个myName去Lua全局表查找myName对应的字符串
    4) 全局表返回一个字符串”beauty girl”
    5) Lua把取得的“beauty girl”字符串放到堆栈(栈顶)
    6) C++可以从Lua堆栈中取得“beauty girl”

    现在,我们给helloLua.lua文件添加一个table全局变量:

    -- helloLua.lua文件  
    myName = "beauty girl"  
    helloTable = {name = "mutou", IQ = 125}  

    我们看到,多了一个helloTable的变量,它和数组十分相似,又和HashMap有点类似,总之它很强大。
    获取helloTable变量的方式和以前是一样的:

    /* 取得table变量,在栈顶 */  
    lua_getglobal(pL, "helloTable");  

    这样,helloTable变量就被存放到栈顶。
    可我们并不是要取table变量,因为C++中是无法识别Lua的table类型的,所以我们要取得table中具体的值,也就是name和IQ的值。
     
    有一个和lua_getglobal类似的函数,叫做lua_gettable,顾名思义,它是用来取得table相关的数据的。
    lua_gettable函数会从栈顶取得一个值,然后根据这个值去table中寻找对应的值,最后把找到的值放到栈顶。
    lua_pushstring()函数可以把C++中的字符串存放到Lua的栈里;
    然后再用lua_gettable()取执行前面所说的步骤,lua_gettable的第二个参数是指定的table变量在栈中的索引。

    为了方便理解,我们画个图来表示:

     

    这是初始状态,堆栈里还没有任何东西,那么,现在要先把helloTable变量放到栈顶:

    /* 取得table变量,在栈顶 */  
    lua_getglobal(pL, "helloTable");  

    然后就变成了这样:

     

    接着,我们要取得table的name对应的值,那么,先要做的就是把”name”字符串入栈:

    /* 将C++的字符串放到Lua的栈中,此时,栈顶变为“name”, helloTable对象变为栈底 */  
    lua_pushstring(pL, "name");  

    然后变成这样:

     

    注意了,我把栈的索引也加上了,因为我们即将要使用,这次我们用负数索引。
    由于”name”的入栈,现在helloTable变量已经不在栈顶了。
    接着,我们调用要做最重要的一步了,取得name在table中对应的值:

    /* 
    从table对象寻找“name”对应的值(table对象现在在索引为-2的栈中,也就是当前的栈底), 
    取得对应值之后,将值放回栈顶 
    */ 
    lua_gettable(pL, -2); 

    此时,栈变成这样:

     

    lua_gettable倒底做了什么事情?
    首先,我们来解释一下lua_gettable的第二个参数,-2是什么意思,-2就是刚刚helloTable变量在栈中的索引。
    然后,Lua会去取得栈顶的值(之前的栈顶是”name”),然后拿着这个值去helloTable变量中寻找对应的值,当然就找到”mutou”了。
    最后,Lua会把找到的值入栈,于是”mutou”就到了栈顶了。

    最后,简单写了个Lua和C++相互调用的实例,代码地址:Lua和C++交互示例代码

    ============= End

  • 相关阅读:
    PostgreSQL中的partition-wise join
    Partition-wise join
    外观模式 门面模式 Facade 结构型 设计模式(十三)
    桥接模式 桥梁模式 bridge 结构型 设计模式(十二)
    组合模式 合成模式 COMPOSITE 结构型 设计模式(十一)
    创建型设计模式对比总结 设计模式(八)
    原型模式 prototype 创建型 设计模式(七)
    单例模式 创建型 设计模式(六)
    建造者模式 生成器模式 创建型 设计模式(五)
    抽象工厂模式 创建型 设计模式(四)
  • 原文地址:https://www.cnblogs.com/lsgxeva/p/11158168.html
Copyright © 2011-2022 走看看