Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1511310
  • 博文数量: 129
  • 博客积分: 1449
  • 博客等级: 上尉
  • 技术积分: 3048
  • 用 户 组: 普通用户
  • 注册时间: 2012-07-24 18:36
文章分类

全部博文(129)

文章存档

2015年(3)

2014年(20)

2013年(65)

2012年(41)

分类: C/C++

2013-01-23 14:03:09

xxg@xxg-desktop:~/1-wire/hellolua/src$ ./luatest
--> Hello LUA, this is my first lua program for yingying!
lua: average = 30
C:crc8-->argument 1, Len = 12, Data = 313233343536, return 0xEC
C:crc8-->argument 1, Len = 16, Data = 3132333435363738, return 0x07
lua: crc8 = EC, 07
C: lua var ret: ScreenWidth = 500, appName = Firefox2, str1 = 313233343536, str2 = 3132333435363738

 --- func = add, arg = ii>i
C: arg_len = 2, ret_len = 1, i
lua stack: 1 = 45, 
C: lua func ret = 45

 --- func = strtest, arg = ii>ss
C: arg_len = 2, ret_len = 2, ss
lua: ret =str1-10-20, str2-10-20
lua stack: 2 = str1-10-20, str2-10-20, 
C: lua func ret = str1-10-20, str2-10-20

 --- func = strtest, arg = ss>ss
C: get string = strtest
C: get string = ctolua
C: arg_len = 2, ret_len = 2, ss
lua: ret =str1-strtest-ctolua, str2-strtest-ctolua
lua stack: 2 = str1-strtest-ctolua, str2-strtest-ctolua, 
C: lua func ret = str1-strtest-ctolua, str2-strtest-ctolua
lua stack: 4 = true, 10, nil, hello, 
lua stack: 5 = true, 10, nil, hello, true, 
lua stack: 5 = true, 10, nil, true, hello, 
lua stack: 4 = true, 10, hello, true, 
lua stack: 3 = true, 10, hello, 
lua stack: 2 = true, hello, 
lua stack: 0 = 





(1)lua 和 C之间的交互的基本知识:

lua 和 C 之间的数据交互通过堆栈进行,栈中的数据通过索引值进行定位,(栈就像是一个容器一样,放进去的东西都要有标号)
其中栈顶是-1,栈底是1,也就是第1个入栈的在栈底;即正数表示相对于栈底的位置(位移),负数表示相对于栈顶的位置(位移);

(2)计算和清空栈中元素的操作:
1、函数lua_gettop(): 返回栈顶元素的索引。 
因为索引是从 1 开始编号的, 所以这个结果等于堆栈上的元素个数(返回 0 表示堆栈为空).

2. lua_settop(): 用于把堆栈的栈顶索引设置为指定的数值
函数原型:void lua_settop(lua_State *L , int index);
比如说,一个栈原来有8个元素,调用函数设置index为7,就是把堆栈的元素数设置为7,
也就是删掉一个元素,而且是栈顶元素;这个是用的正数,也就是相对于栈底元素设置的;
如果是相对于栈顶元素,则要求用负值;也就是说如果设置索引为-2(index = -2),也相当于删除掉栈顶元素;
为了说明方便,干脆设置一个宏:
#define lua_pop(L,n) lua_settop(L,-(n)-1)
这里的n是相对于栈顶的第几个元素,主要是为了理解;后面的lua_settop(L,-(n)-1) 用的就是相对于栈顶位移的负数表示;

3、lua_pushvalue(): 压入堆栈上指定索引的一个拷贝到栈顶
函数原型:void lua_pushvalue (lua_State *L, int index);
英文原意:Pushes a copy of the element at the given valid index onto the stack.
下面是一个例子,栈的初始状态为10 20 30 40 50 *(从栈底到栈顶,“*”标识为栈顶)有:
lua_pushvalue(L, 3) --> 10 20 30 40 50 30*

4. lua_remove(): 删除给定索引的元素,并将这一索引之上的元素来填补空缺;
void lua_remove(lua_State *L, int index);
下面是一个例子,栈的初始状态为10 20 30 40 50 *(从栈底到栈顶,“*”标识为栈顶,有:
lua_remove(L, -3) --> 10 20 40 50*
下面是一个例子,栈的初始状态为10 20 30 40 50 *(从栈底到栈顶,“*”标识为栈顶,有:
lua_settop(L, -3) --> 10 20 30 *
lua_settop(L, 6) --> 10 20 30 nil nil nil *

5、lua_replace: 将栈顶元素压入指定位置而不移动任何元素(因此指定位置的元素的值被替换)。
void lua_replace (lua_State* L, int index);
下面是一个例子,栈的初始状态为10 20 30 40 50 *(从栈底到栈顶,“*”标识为栈顶,有:
lua_replace(L, 2) --> 10 50 30 40 * //把50替换到索引的位置,同时去掉栈顶元素

void lua_newtable (lua_State *L);
创建一个空 table ,并将之压入堆栈。 它等价于 lua_createtable(L, 0, 0) 。

int luaL_newmetatable (lua_State *L, const char *tname);
如果注册表中已经有Key为tname的数据则返回0. 否则创建一个新表作为userdata的metatable,并在注册表中注册它然后返回1.
不过两种情况都会把注册表中tname相关的值压入堆栈。

void *luaL_checkudata (lua_State *L, int narg, const char *tname);
Checks whether the function argument narg is a userdata of the type tname (see luaL_newmetatable).

void lua_settable (lua_State *L, int index);
作一个等价于 t[k] = v 的操作, 这里 t 是一个给定有效索引 index 处的值, v 指栈顶的值, 而 k 是栈顶之下的那个值。
这个函数会把键和值都从堆栈中弹出。 和在 Lua 中一样,这个函数可能触发 "newindex" 事件的元方法 (参见 §2.8)。

void lua_pushcfunction (lua_State *L, lua_CFunction f);
将一个 C 函数压入堆栈。 这个函数接收一个 C 函数指针,并将一个类型为 function 的 Lua 值 压入堆栈。当这个栈定的值被调用时,将触发对应的 C 函数。
注册到 Lua 中的任何函数都必须遵循正确的协议来接收参数和返回值 (参见 lua_CFunction)。

lua_pushcfunction 是作为一个宏定义出现的:
#define lua_pushcfunction(L,f) lua_pushcclosure(L,f,0)

int lua_setmetatable (lua_State *L, int index);
把一个 table 弹出堆栈,并将其设为给定索引处的值的 metatable 。

void lua_pushcclosure (lua_State *L, lua_CFunction fn, int n);
把一个新的 C closure 压入堆栈。当创建了一个 C 函数后,你可以给它关联一些值,这样就是在创建一个 C closure (参见 §3.4);
接下来无论函数何时被调用,这些值都可以被这个函数访问到。 为了将一些值关联到一个 C 函数上, 首先这些值需要先被压入堆栈
(如果有多个值,第一个先压)。 接下来调用 lua_pushcclosure 来创建出 closure 并把这个 C 函数压到堆栈上。
参数 n 告之函数有多少个值需要关联到函数上。 lua_pushcclosure 也会把这些值从栈上弹出。

void *lua_newuserdata (lua_State *L, size_t size);
这个函数分配分配一块指定大小的内存块, 把内存块地址作为一个完整的 userdata 压入堆栈,并返回这个地址。
userdata 代表 Lua 中的 C 值。 完整的 userdata 代表一块内存。 它是一个对象(就像 table 那样的对象): 你必须创建它,
它有着自己的元表,而且它在被回收时,可以被监测到。 一个完整的 userdata 只和它自己相等(在等于的原生作用下)。
当 Lua 通过 gc 元方法回收一个完整的 userdata 时, Lua 调用这个元方法并把 userdata 标记为已终止。
等到这个 userdata 再次被收集的时候,Lua 会释放掉相关的内存。

void *lua_touserdata (lua_State *L, int index);
如果给定索引处的值是一个完整的 userdata ,函数返回内存块的地址。 如果值是一个 light userdata ,那么就返回它表示的指针。否则,返回 NULL 。

(3)C中加载lua时一些函数的用法
void lua_getglobal (lua_State *L, const char *name); 把全局的name的值压到栈顶。
lua_is***(lua_State *L, int index); 检查变量是不是某个类型,index指示变量的顺序,栈顶为-1。
lua_to***(lua_State *L, int index); 获取栈中的变量,然后转换为某个指定的类型,并返回。
lua_close(); 销毁所有在指定的Lua State上的所有对象,同时释放所有该State使用的动态分配的空间。

--- 相关栈函数汇总: 
压入元素到栈里
void lua_pushnil (lua_State *L);
void lua_pushboolean (lua_State *L, int bool);
void lua_pushnumber (lua_State *L, double n);
void lua_pushlstring (lua_State *L, const char *s, size_t length);
把指针 s 指向的长度为 len 的字符串压栈。 Lua 对这个字符串做一次内存拷贝(或是复用一个拷贝), 
因此 s 处的内存在函数返回后,可以释放掉或是重用于其它用途。 字符串内可以保存有零字符。
void lua_pushstring (lua_State *L, const char *s);
把指针 s 指向的以零结尾的字符串压栈。 Lua 对这个字符串做一次内存拷贝(或是复用一个拷贝), 因此 s 处的内存在函数返回后,
可以释放掉或是重用于其它用途。 字符串中不能包含有零字符;第一个碰到的零字符会认为是字符串的结束。
void lua_pushcfunction (lua_State *L, lua_CFunction fn);

查询栈里的元素
lua_isnil (lua_State *L, int index);
lua_isboolean (lua_State *L, int index);
int lua_isnumber (lua_State *L, int index);
int lua_isstring (lua_State *L, int index);
int lua_isfunction (lua_State *L, int index);
int lua_istable (lua_State *L, int index);
int lua_isuserdata (lua_State *L, int index);
lua_islightuserdata (lua_State *L, int index);
lua_isthread (lua_State *L, int index);

转换栈里的元素
int lua_toboolean (lua_State *L, int index);
double lua_tonumber (lua_State *L, int index);
const char *lua_tostring (lua_State *L, int index);
const char *lua_tolstring (lua_State *L, int idx, size_t *len);
size_t lua_strlen (lua_State *L, int index);
lua_CFunction lua_tocfunction (lua_State *L, int idx);
void *lua_touserdata (lua_State *L, int idx);
lua_State *lua_tothread (lua_State *L, int idx);

Lua栈的维护
int lua_gettop (lua_State *L); 取得栈顶元素的索引,即栈中元素的个数
void lua_settop (lua_State *L, int index); 设置栈顶索引,即设置栈中元素的个数,如果index<0,则从栈顶往下数,下同
void lua_pushvalue (lua_State *L, int index); 把栈中指定索引的元素复制一份到栈顶
void lua_remove (lua_State *L, int index); 删除指定索引的元素
void lua_insert (lua_State *L, int index); 移动栈顶元素到指定索引的位置,栈中数目没有改变
void lua_replace (lua_State *L, int index); 从栈顶弹出元素值并将其设置到指定索引位置,栈中的数目减一
int lua_checkstack (lua_State *L, int extra); 确保堆栈上至少有 extra 个空位。如果不能把堆栈扩展到相应的尺寸,函数返回 false 。这个函数永远不会缩小堆栈。

int lua_pop(L,n); 从栈顶弹出n个元素,它是一个lua_settop的包装:#define lua_pop(L,n) lua_settop(L, -(n)-1)





1. 理解lua的栈到底是什么?

    lua的栈类似于以下的定义, 它是在创建lua_State的时候创建的:

             TValue stack[max_stack_len]  // 欲知内情可以查 lstate.c 的stack_init函数

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

          lua栈

   执行下面的代码就可以让你的lua栈上呈现图中的情况

    lua_pushcclosure(L, func, 0) // 创建并压入一个闭包

    lua_createtable(L, 0, 0)        // 新建并压入一个表

    lua_pushnumber(L, 343)      // 压入一个数字

    lua_pushstring(L, “mystr”)   // 压入一个字符串

 

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

    lua

 

    TValue结构对应于lua中的所有数据类型, 是一个{值, 类型} 结构, 这就lua中动态类型的实现, 它把值和类型绑在一起, 用tt记录value的类型, value是一个联合结构, 由Value定义, 可以看到这个联合有四个域, 先说明简单的

        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存在栈上元素里的只是指针, 他们都会在生命周期结束后被垃圾回收. 

 

2. 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只负责它的世界 

 

3. 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中是对应的, 也提供一些c中没有的类型. 其中有一些药特别的说明一下:

        nil值, c中没有对应, 但是可以通过lua_pushnil向lua中压入一个nil值

        注意: lua_push*族函数都有"创建一个类型的值并压入"的语义, 因为lua中所有的变量都是lua中创建并保存的, 对于那些和c中有对应关系的lua类型, lua会通过api传来的附加参数, 创建出对应类型的lua变量放在栈顶, 对于c中没有对应类型的lua类型, lua直接创建出对应变量放在栈顶.

       例如:    lua_pushstring(L, “string”) lua根据"string"创建一个 TString obj, 绑定到新分配的栈顶元素上

                  lua_pushcclosure(L,func, 0) lua根据func创建一个 Closure obj, 绑定到新分配的栈顶元素上

                  lua_pushnumber(L,5) lua直接修改新分配的栈顶元素, 将5赋值到对应的域

                  lua_createtable(L,0, 0)lua创建一个Tabke obj, 绑定到新分配的栈顶元素上 

       总之, 这是一个 c value –> lua value的流向, 不管是想把一个简单的5放入lua的世界, 还是创建一个table, 都会导致      1. 栈顶新分配元素    2. 绑定或赋值

       还是为了重复一句话, 一个c value入栈就是进入了lua的世界, lua会生成一个对应的结构并管理起来, 从此就不再依赖这个c value

        lua value –> c value时, 是通过 lua_to* 族api实现, 很简单, 取出对应的c中的域的值就行了, 只能转化那些c中有对应值的lua value, 比如table就不能to c value, 所以api中夜没有提供 lua_totable这样的接口.




这里, 简单的记录一下lua中闭包的知识和C闭包调用

前提知识: 在lua api小记2中已经分析了lua中值的结构, 是一个 TValue{value, tt}组合, 如果有疑问, 可以去看一下

 

一些重要的数据结构

 

    lua中有两种闭包, c闭包和lua闭包

    两种闭包的公共部分:

       #define ClosureHeader CommonHeader;     lu_byte isC;        lua_byte nupvalues; GCObject* gclist;       struct Table env

     /*是否是C闭包*/     /*upval的个数*/                                   /* 闭包的env, set/getenv就是操纵的它 */

 

    C闭包的结构

        struct CClosure{

             ClosureHeader;

             lua_CFunction f;

             TValue upvalue[1];

        }

      结构比较简单, f是一个满足 int lua_func(lua_State*) 类型的c函数

      upvalue是创建C闭包时压入的upvalue, 类型是TValue, 可以得知, upvalue可以是任意的lua类型

 

    Lua闭包结构

       struct LClosure{

           ClosureHeader;

           strcut Proto* p;

           UpVal* upvals[1];

       }

      Proto的结构比较复杂, 这里先不做分析

 

   统一的闭包结构, 一个联合体, 说明一个闭包要么是C闭包, 要么是lua闭包, 这个是用isC表识出来的.

       union Closure{

            CClosure c;

            LClosure  l;

       }

 

纠结的闭包

       为什么大家叫闭包, 不叫它函数, 它看起来就是函数啊? 为什么要发明一个"闭包"这么一个听起来蛋疼的词呢? 我也纠结在这里好久了, 大概快一年半了吧~~~=.=我比较笨~~~随着看源码, 现在想通了, 拿出一些的自己在研究过程中的心得[尽量的通俗易懂]:

       1. c 语言中的函数的定义: 对功能的抽象块, 这个大家没什么异议吧.

       2. lua对函数做了扩展:

              a. 可以把几个值和函数绑定在一起, 这些值被称为upvalue.

              ps:  可能有人觉得c++的函数对象也可以把几个值和函数绑定起来啊, 是这样的, 但是这个问题就像是"在汇编中也可以实现面向对象呀"一样, lua从语言层面对upvalue提供了支持, 就像c++/java从语言层面提供了对类, 对象的支持一样, 当然大大的解放了我们程序员的工作量, 而且配上lua动态类型, 更是让人轻松了不少.

              b. 每个函数可以和一个env(环境)绑定.

              ps:  如果说上面的upvalue还能在c++中coding出来, 那么env 上下文环境这种动态语言中特有的东西c++就没有明显的对应结构了吧? 可能有人觉得lua是c写的, 通过coding也可以实现, 好吧=.= , "能做和做"是两码事, 就想你能步行从北京到上海, 不表明你就必须要这么做. env是非常重要和有用的东西, 它可以轻松创造出一个受限的环境, 就是传说中的"沙盒", 我说的更通俗一点就是"一个动态名字空间机制". 这个先暂时不分析.

 

       好了, 现在我们看到

            c       函数    { 功能抽象 }

            lua    闭包     {功能抽象, upvalue, env}

            重点: 闭包 == {功能抽象, upvalue, env}

  

       看到这里, 大家都明白了, 如果把lua中的{功能抽象, upvalue, env}也称为函数, 不但容易引起大家的误解以为它就是和c函数一样, 而且它确实不能很好的表达出lua函数的丰富内涵, 闭包, "闭" 是指的它是一个object, 一个看得见摸得着的东西, 不可分割的整体(first class); "包" 指的是它包含了功能抽象, upvalue, env. 这里一个很有趣的事实就是, {功能抽象, upvalue, env}是很多动态语言的一个实现特征, 比如lua, javascript都有实现这样的结构, 它是先被实现出来, 然后冠以"闭包"这样一个名称. 所以, 你单单想去理解闭包这个词的话, 基本是没有办法理解的, 去网上查闭包, 没用, 你能查到的就是几个用闭包举出的例子, 看完以后保证你的感觉是"这玩意挺神秘的, 但是还是不懂什么是闭包", 为什么不懂?  因为它指的是一种实现结构特征, 是为了实现动态语言中的函数first class和上下文概念而创造出来的.

       宁可多说几句, 只要对加深理解有好处就行, 有这样两个个句子"我骑车去买点水果" "我用来闭包{功能抽象, upvalue, env}实现动态语言中的函数first class和上下文概念" , 闭包和"骑车"都是你达到目地的一种手段, 为了买水果你才想了"骑车"这样一个主意, 并不是为了骑车而去买水果. 只把把眼睛盯在骑车上是不对的, 它只是手段.

 

 

向lua中注册c函数的过程是通过lua_pushcclosure(L, f, n)函数实现的

 

       流程:  1. 创建一个 sizeof(CClosure) + (n - 1) * sizeof(TValue)大小的内存, 这段内存是 CClosure + TValue[n], 并做gc簿记[这点太重要了, 为什么lua要控制自己世界中的所有变量, 就是因为它要做gc簿记来管理内存],  isC= 1 标示其是一个C闭包.

             2. c->f = f绑定c函数.    ---------  闭包.功能抽象 = f

             3. env = 当前闭包的env[这说明了被创建的闭包继承了创建它的闭包的环境].  ----------- 闭包.env = env

             4. 把栈上的n个元素赋值到c->upvalue[]数组中, 顺序是越先入栈的值放在upvalue数组的越开始位置, c->nupvalues指定改闭包upvalue的个数.  ---------- 闭包.upvalue = upvalue

             5. 弹出栈上n个元素, 并压入新建的Closure到栈顶.

       整个流程是比较简单的, 分配内存, 填写属性, 链入gc监控, 绑定c函数, 绑定upvalue, 绑定env一个C闭包就ok了, 请结合上面给的闭包的解释, 很清楚了.

 

现在来解析这个C闭包被调用的过程[注意, 这里只涉及C闭包的调用]

 

       lua 闭包调用信息结构:

            struct CallInfo {
                 StkId base;  /* base for this function */     ---- 闭包调用的栈基
                 StkId func;  /* function index in the stack */  ---- 要调用的闭包在栈上的位置
                 StkId    top;  /* top for this function */     ---- 闭包的栈使用限制, 就是lua_push*的时候得看着点, push太多就超了, 可以lua_checkstack来扩
                 const Instruction *savedpc;      ---- 如果在本闭包中再次调用别的闭包, 那么该值就保存下一条指令以便在返回时继续执行
                 int nresults;  /* expected number of results from this function */   ---- 闭包要返回的值个数
                 int tailcalls;  /* number of tail calls lost under this entry */   ---- 尾递归用, 暂时不管
            }

        从注释就可以看出来, 这个结构是比较简单的, 它的作用就是维护一个函数调用的有关信息, 其实和c函数调用的栈帧是一样的, 重要的信息base –> ebp, func –> 要调用的函数的栈index, savedpc –> eip, top, nresults和tailcalls没有明显的对应.

        在lua初始化的时候, 分配了一个CallInfo数组, 并用L->base_ci指向该数组第一个元素, 用L->end_ci指向该数组最后一个指针, 用L->size_ci记录数组当前的大小, L->ci记录的是当前被调用的闭包的调用信息.

 

        下面讲解一个c闭包的调用的过程:

        情景: c 函数 int lua_test(lua_State* L){

                          int a = lua_tonumber(L, 1);

                          int b = lua_tonumber(L, 2);

                          a = a + b;

                          lua_pushnumber(L, a);

                 }

                 已经注册到了lua 中, 形成了一个C闭包, 起名为"test", 下面去调用它

                 luaL_dostring(L, "c = test(3, 4)")

 

         1. 首先, 我们把它翻译成对应的c api

             lua3

                                1. 最初的堆栈

 

               lua_getglobal(L, “test”)

               lua_pushnumber(L, 3)

               lua_pushnumber(L, 4)      

             lua2

                              2. 压入了函数和参数的堆栈

 

               lua_call(L, 2, 1)

             lua5

                               3. 调用lua_test开始时的堆栈

 

             lua4

                               4. 调用结束的堆栈

 

               lua_setglobal(L, “c”)     

             lua3

                               5. 取出调用结果的堆栈

 

        我们重点想要知道的是lua_call函数的过程

            1. lua的一致性在这里再一次的让人震撼, 不管是dostring, 还是dofile, 都会形成一个闭包, 也就是说, 闭包是lua中用来组织结构的基本构件, 这个特点使得lua中的结构具有一致性, 是一种简明而强大的概念.

            2. 根据1, a = test(3, 4)其实是被组织成为一个闭包放在lua栈顶[方便期间, 给这个lua闭包起名为bb], 也就说dostring真正调用的是bb闭包, 然后bb闭包执行时才调用的是test

 

         [保存当前信息到当前函数的CallInfo中]

            3. 在调用test的时刻, L->ci记载着bb闭包的调用信息, 所以, 先把下一个要执行的指令放在L->ci->savedpc中, 以供从test返回后继续执行.

            4. 取栈上的test C闭包 cl, 用 cl->isC == 1断定它的确是一个C闭包

 

         [进入一个新的CallInfo, 布置堆栈]

            5. 从L中新分配一个CallInfo ci来记录test的调用信息, 并把它的值设置到L->ci, 这表明一个新的函数调用开始了, 这里还要指定test在栈中的位置, L->base = ci->base = ci->func+1, 注意, 这几个赋值很重要, 导致的堆栈状态由图2转化到图3, 从图中可以看出, L->base指向了第一个参数, ci->base也指向了第一个参数, 所以在test中, 我们调用lua_gettop函数返回的值就是2, 因为在调用它的时候, 它的栈帧上只有2个元素, 实现了lua向c语言中传参数.

 

        [调用实际的函数]

            6. 安排好堆栈, 下面就是根据L->ci->func指向的栈上的闭包(及test的C闭包), 找到对应的cl->c->f, 并调用, 就进入了c函数lua_test

 

        [获取返回值调整堆栈, 返回原来的CallInfo]

            7. 根据lua_test的返回值, 把test闭包和参数弹出栈, 并把返回值压入并调整L->top

            8. 恢复 L->base, L->ci 和 L->savedpc, 继续执行.

        总结: 调用一个新的闭包时 1. 保存当前信息到当前函数的CallInfo中 2. 进入一个新的CallInfo, 布置堆栈  3. 调用实际的函数  4. 获取返回值调整堆栈, 返回原来的CallInfo

 

 

1. 创建lua虚拟机

lua_State *lua_newstate (lua_Alloc f, void *ud)

创建一个新的独立的lua虚拟机. 参数指定了内存分配策略及其参数, 注意, 让用户可以定制内存分配策略是十分有用的, 比如在游戏服务器端使用lua, 我做过一次统记lua在运行的时候会大量的分配大小小于128字节的内存块, 在这样的环境下, 使用lua原生的分配器就不太适合了, 还好在服务器端, 我们往往已经实现了memory pool, 这时只需要写一个符合 lua_Alloc 原型的适配器, 然后指定为lua的内存分配器就可以了, 很灵活.

从lua的设计层面来说, lua只是内存分配器的用户, 它只使用一个简单的接口来分配内存, 而不去实现如何分配, 毕竟内存分配不在lua的功能范围内, 这样使的lua变的更加紧凑, 它只是专注于实现lua本身, 而不需要去关注内存分配策略这样的和lua本身无关的东西. 其实学习lua源代码不光是为了更好的掌握lua, 也是为了学习lua中的体现出来的一些编程思想, lua是一个高度的一致性的, 优雅的软件作品

失败返回null, 多是因为内存分配失败了

该函数会创建栈, 从该函数学习到的东西: 

 1. 当你制作一个功能时, 最好是理清该功能的核心概念和需求, 然后去实现他们, 功能要模块化, 核心概念之间应该是概念一致的, 联系紧密的[谈何容易, 只能是尽可能的, 随时提醒自己要有这样的想法].

 2. 不要因为功能的实现问题而将一个非该功能核心概念的东西加进来, 反之应该把这些东西抽象化作为用户可配置的形式.[在实现时很容易发生"要用到某个功能了, 就是实现它"这样的情况, 这样并不好]就比如lua, 它的核心概念就是lua虚拟机, 而内存分配只是在实现lua虚拟机的过程中的要用到的一种东西, 但它本身不在lua的核心概念里面, 所以把它暴露出来, 让用户自己去定制.

   再说下去就是: 除了系统最核心的功能, 其他的东西能用插件的形式暴露给用户, 使其可配置可扩展.

 

关于这个函数, 还要做更多的解释, 比如我们看到的lua的绝大多数api的第一个参数都是lua_State* L, 而这个L就是lua_newstate制造出来的, 那么在分析源码的时候, 当然要去看看lua_newstate到底是干了些什么, lua_State的结构又是什么, 要了解这些内容, 需要知道lua的内部组织结构, 下面是一张很概括但能反映其结构的图

 

JSZUY$VR25$M{O}OQV59XYW

 

可以看出来, 在一个独立的lua虚拟机里, global_State是一个全局的结构, 而lua_State可以有多个

值得说明的是, 当调用lua_newstate的时候, 主要的工作就是1. 创建和初始化global_State 2. 创建一个lua_State, 下面来详细的讲解global_State的内容和作用.

 

global_State

一个lua虚拟机中只有一个, 它管理着lua中全局唯一的信息, 主要是以下功能

1. 内存分配策略及其参数, 在调用lua_newstate的时候配置它们. 也可以通过lua_getallocf和lua_setallocf随时获取和修改它

2. 字符串的hashtable, lua中所有的字符串都会在该hashtable中注册.

3. gc相关的信息. 内存使用统计量.

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上.

global_State主要是管理lua虚拟机的全局环境.

 

lua_State

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的通信.

lua_State主要是管理一个lua虚拟机的执行环境, 一个lua虚拟机可以有多个执行环境.

 

lua_newstate函数的流程

经过上面的分析, 可以看出newstate = [new 一个 global_state] + [new 一个 lua_State], 现在看一下它的流程, 很简单

1. 新建一个global_state和一个lua_State.

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

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

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

 

 

2. 创建新lua执行环境

lua_State *luaE_newthread (lua_State *L)

创建一个新的lua_State, 预分配CallInfo和stack空间, 并共享l_gt表, 注意, 虽然每个lua_State都有自己的l_gt, 但是这里是却将新建的lua_State的l_gt都指向主lua_State的l_gt.

注意, lua_State是lua运行的基础[CallInfo]和与c通信的基础[stack], 在新的lua_State上操作不会影响到原来的lua_State:), 这个是协程实现的基础. 这里顺便提一下协程, 这里先引一段lua创始人的话:" 我们不信任基于抢占式内存共享的多线程技术. 在 HOPL 论文中, 我们写道: "我们仍然认为, 如果在连 a=a+1 都没有确定结果的语言中, 无人可以写出正确的程序." 我们可以通过去掉抢占式这一点, 或是不共享内存, 就可以回避这个问题."协程的基础就是"去掉抢占式, 但共享内存", 这里的共享是在lua虚拟机的层面上的, 而不是通常意义上的share memory, 这里的共享内存直接就指的是不同线程[lua_State]之间, 共享lua_State.l_gt全局表, 全局表可以作为不同协程之间的通信环境, 当然也可以用lua_xmove函数, 协程的事先说到这里.

 

一个和多lua_State相关的函数是: 在同一个lua虚拟机里传递不同lua_State的值

void lua_xmove (lua_State *from, lua_State *to, int n)
把from栈上的前n个值弹出, 并压入到to栈中.


阅读(2980) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~