lua源码剖析(二)

来源:互联网 发布:网络剪刀 编辑:程序博客网 时间:2024/05/18 02:20

lua源码剖析(二)

    博客分类: 
  • lua
LuaCC++C#虚拟机 
这次紧接着上次的,将gc类型的数据分析完毕。 


谢谢老朱同学的指正,这里CClosure和LClosure理解有误. 


先来看闭包: 

可以看到闭包也是会有两种类型,这是因为在lua中,函数不过是一种特殊的闭包而已。 

更新:这里CClosure表示是c函数,也就是和lua外部交互传递进来的c函数以及内部所使用的c函数. 

LClosure表示lua的函数,这些函数是由lua虚拟机进行管理的..
 


Java代码  收藏代码
  1. typedef union Closure {  
  2.   CClosure c;  
  3.   LClosure l;  
  4. } Closure;  


接下来来看这个两个结构。 

在看着两个结构之前,先来看宏ClosureHeader,这个也就是每个闭包(函数的头).它包括了一些全局的东西: 

更新 : 
isC:如果是c函数这个值为1,为lua的函数则为0.
 
nupvalues:表示upvalue或者upvals的大小(闭包和函数里面的)。 
gclist:链接到全局的gc链表。 
env:环境,可以看到它是一个table类型的,他里面保存了一些全局变量等。 

Java代码  收藏代码
  1. #define ClosureHeader \  
  2.     CommonHeader; lu_byte isC; lu_byte nupvalues; GCObject *gclist; \  
  3.     struct Table *env  


ok接下来先来看 CClosure的实现.他很简单,就是保存了一个函数原型,以及一个参数列表 

更新: 
lua_CFunction f: 这个表示所要执行的c函数的原型. 
TValue upvalue[1]:这个表示函数运行所需要的一些参数(比如string 的match函数,它所需要的几个参数都会保存在upvalue里面
 

Java代码  收藏代码
  1. typedef struct CClosure {  
  2.   ClosureHeader;  
  3.   lua_CFunction f;  
  4.   TValue upvalue[1];  
  5. } CClosure;  


更新: 
这里我们只简要的介绍CClosure ,主要精力我们还是放在LClosure上.我来简要介绍下CClosure 的操作.一般当我们将CClosure 压栈,然后还有一些对应的调用函数f所需要的一些参数,此时我们会将参数都放到upvalue中,然后栈中只保存cclosure本身,这样当我们调用函数的时候(有一个全局的指针指向当前的调用函数),能够直接得到所需参数,然后调用函数.
 


Java代码  收藏代码
  1. LUA_API void lua_pushcclosure (lua_State *L, lua_CFunction fn, int n) {  
  2.   Closure *cl;  
  3.   lua_lock(L);  
  4.   luaC_checkGC(L);  
  5.   api_checknelems(L, n);  
  6. ///new一个cclosure  
  7.   cl = luaF_newCclosure(L, n, getcurrenv(L));  
  8.   cl->c.f = fn;  
  9.   L->top -= n;  
  10. ///开始将参数值放到upvalue中.  
  11.   while (n--)  
  12.     setobj2n(L, &cl->c.upvalue[n], L->top+n);  
  13.   setclvalue(L, L->top, cl);  
  14.   lua_assert(iswhite(obj2gco(cl)));  
  15.   api_incr_top(L);  
  16.   lua_unlock(L);  
  17. }  



然后来看LClosure 的实现。 

在lua中闭包和函数是原型是一样的,只不过函数的upvalue为空罢了,而闭包upvalue包含了它所需要的局部变量值. 


这里我们要知道在lua中闭包的实现。Lua 用一种称为upvalue 的结构来实现闭包。对任何外层局部变量的存取间接地通过upvalue来进行,也就是说当函数创建的时候会有一个局部变量表upvals(下面会介绍到).然后当闭包创建完毕,它就会复制upvals的值到upvalue。详细的描述可以看the implementation of lua 5.0(云风的blog上有提供下载). 

struct Proto *p:这个指针包含了很多的属性,比如变量,比如嵌套函数等等。 
UpVal *upvals[1]:这个数组保存了指向外部的变量也就是我们闭包所需要的局部变量。 


下面会详细分析这个东西。 

Java代码  收藏代码
  1. typedef struct LClosure {  
  2.   ClosureHeader;  
  3.   struct Proto *p;  
  4.   UpVal *upvals[1];  
  5. } LClosure;  


这里我摘录一段the implementation of lua 5.0里面的描述: 

引用
通过为每个变量至少创建一个upvalue 并按所需情况进行重复利用,保证了未决状态(是否超过生存期)的局部变量(pending vars)能够在闭包间正确地 
共享。为了保证这种唯一性,Lua 为整个运行栈保存了一个链接着所有正打开着 
的upvalue(那些当前正指向栈内局部变量的upvalue)的链表(图4 中未决状态 
的局部变量的链表)。当Lua 创建一个新的闭包时,它开始遍历所有的外层局部 
变量,对于其中的每一个,若在上述upvalue 链表中找到它,就重用此upvalue, 
否则,Lua 将创建一个新的upvalue 并加入链表中。注意,一般情况下这种遍历 
过程在探查了少数几个节点后就结束了,因为对于每个被内层函数用到的外层局 
部变量来说,该链表至少包含一个与其对应的入口(upvalue)。一旦某个关闭的 
upvalue 不再被任何闭包所引用,那么它的存储空间就立刻被回收。

下面是示意图: 




这里的未决状态(是否超过生存期)的局部变量指的就是我们下面的UpVal,其中: 
TValue *v:指向栈内的自己的位置或者自己(这里根据是否这个uvalue被关闭)。 
union u:这里可以看到如果是被关闭则直接保存value。如果打开则为一个链表。 

Java代码  收藏代码
  1. typedef struct UpVal {  
  2.   CommonHeader;  
  3.   TValue *v;  /* points to stack or to its own value */  
  4.   union {  
  5.     TValue value;  /* the value (when closed) */  
  6.     struct {  /* double linked list (when open) */  
  7.       struct UpVal *prev;  
  8.       struct UpVal *next;  
  9.     } l;  
  10.   } u;  
  11. } UpVal;  


然后来看luaF_newLclosure的实现,它与cclosure类似。 

Java代码  收藏代码
  1. Closure *luaF_newLclosure (lua_State *L, int nelems, Table *e) {  
  2.   Closure *c = cast(Closure *, luaM_malloc(L, sizeLclosure(nelems)));  
  3.   luaC_link(L, obj2gco(c), LUA_TFUNCTION);  
  4.   c->l.isC = 0;  
  5.   c->l.env = e;  
  6. ///更新upvals。  
  7.   c->l.nupvalues = cast_byte(nelems);  
  8.   while (nelems--) c->l.upvals[nelems] = NULL;  
  9.   return c;  
  10. }  



ok,接下来我们就通过一些函数来更详细的理解闭包的实现。 

先分析CClosure。我们来看luaF_newCclosure的实现,这个函数创建一个CClosure,也就是创建一个所需要执行的c函数. 

这个函数实现比较简单,就是malloc一个Closure,然后链接到全局gc,最后初始化Closure 。 
Java代码  收藏代码
  1. Closure *luaF_newCclosure (lua_State *L, int nelems, Table *e) {  
  2. ///分配内存  
  3.   Closure *c = cast(Closure *, luaM_malloc(L, sizeCclosure(nelems)));  
  4. ///链接到全局的gc链表  
  5.   luaC_link(L, obj2gco(c), LUA_TFUNCTION);  
  6. ///开始初始化。  
  7.   c->c.isC = 1;  
  8.   c->c.env = e;  
  9.   c->c.nupvalues = cast_byte(nelems);  
  10.   return c;  
  11. }  


在lua_State中它里面包含有GCObject 类型的域叫openupval,这个域也就是当前的栈上的所有open的uvalue。可以看到这里是gcobject类型的,这里我们就知道为什么gcobvject中为什么还要包含struct UpVal uv了。而在global_State中的UpVal uvhead则是整个lua虚拟机里面所有栈的upvalue链表的头。 

然后我们来看lua中如何new一个upval。 

它很简单就是malloc一个UpVal然后链接到gc链表里面。这边要注意,每次new的upval都是close的。 

Java代码  收藏代码
  1. UpVal *luaF_newupval (lua_State *L) {  
  2. ///new一个upval  
  3.   UpVal *uv = luaM_new(L, UpVal);  
  4. ///链接到全局的gc中  
  5.   luaC_link(L, obj2gco(uv), LUA_TUPVAL);  
  6. ///可以看到这里的upval是close的。  
  7.   uv->v = &uv->u.value;  
  8.   setnilvalue(uv->v);  
  9.   return uv;  
  10. }  


接下来我们来看闭包如何来查找到对应的upval,所有的实现就在函数luaF_findupval中。我们接下来来看这个函数的实现。 
这个函数的流程是这样的。 

1 首先遍历lua_state的openupval,也就是当前栈的upval,然后如果能找到对应的值,则直接返回这个upval。 

2 否则新建一个upval(这里注意new的是open的),然后链接到openupval以及uvhead中。而且每次新的upval的插入都是插入到链表头的。而且这里插入了两次。这里为什么要有两个链表,那是因为有可能会有多个栈,而uvhead就是用来管理多个栈的upvalue的(也就是多个openupval)。 

Java代码  收藏代码
  1. UpVal *luaF_findupval (lua_State *L, StkId level) {  
  2.   global_State *g = G(L);  
  3. ///得到openupval链表  
  4.   GCObject **pp = &L->openupval;  
  5.   UpVal *p;  
  6.   UpVal *uv;  
  7. ///开始遍历open upvalue。  
  8.   while (*pp != NULL && (p = ngcotouv(*pp))->v >= level) {  
  9.     lua_assert(p->v != &p->u.value);  
  10. ///发现已存在。  
  11.     if (p->v == level) {    
  12.       if (isdead(g, obj2gco(p)))  /* is it dead? */  
  13.         changewhite(obj2gco(p));  /* ressurect it */  
  14. ///直接返回  
  15.       return p;  
  16.     }  
  17.     pp = &p->next;  
  18.   }  
  19. ///否则new一个新的upvalue  
  20.   uv = luaM_new(L, UpVal);  /* not found: create a new one */  
  21.   uv->tt = LUA_TUPVAL;  
  22.   uv->marked = luaC_white(g);  
  23. ///设置值  
  24.   uv->v = level;  /* current value lives in the stack */  
  25. ///首先插入到lua_state的openupval域  
  26.   uv->next = *pp;  /* chain it in the proper position */  
  27.   *pp = obj2gco(uv);  
  28. ///然后插入到global_State的uvhead(这个也就是双向链表的头)  
  29.   uv->u.l.prev = &g->uvhead;  /* double link it in `uvhead' list */  
  30.   uv->u.l.next = g->uvhead.u.l.next;  
  31.   uv->u.l.next->u.l.prev = uv;  
  32.   g->uvhead.u.l.next = uv;  
  33.   lua_assert(uv->u.l.next->u.l.prev == uv && uv->u.l.prev->u.l.next == uv);  
  34.   return uv;  
  35. }  


更新: 
上面可以看到我们new的upvalue是open的,那么什么时候我们关闭这个upvalue呢,当函数关闭的时候,我们就会unlink掉upvalue,从全局的open upvalue表中:
 

Java代码  收藏代码
  1. void luaF_close (lua_State *L, StkId level) {  
  2.   UpVal *uv;  
  3.   global_State *g = G(L);  
  4. ///开始遍历open upvalue  
  5.   while (L->openupval != NULL && (uv = ngcotouv(L->openupval))->v >= level) {  
  6.     GCObject *o = obj2gco(uv);  
  7.     lua_assert(!isblack(o) && uv->v != &uv->u.value);  
  8.     L->openupval = uv->next;  /* remove from `open' list */  
  9.     if (isdead(g, o))  
  10.       luaF_freeupval(L, uv);  /* free upvalue */  
  11.     else {  
  12. ///unlink掉当前的uv.  
  13.       unlinkupval(uv);  
  14.       setobj(L, &uv->u.value, uv->v);  
  15.       uv->v = &uv->u.value;  /* now current value lives here */  
  16.       luaC_linkupval(L, uv);  /* link upvalue into `gcroot' list */  
  17.     }  
  18.   }  
  19. }  
  20.   
  21. static void unlinkupval (UpVal *uv) {  
  22.   lua_assert(uv->u.l.next->u.l.prev == uv && uv->u.l.prev->u.l.next == uv);  
  23.   uv->u.l.next->u.l.prev = uv->u.l.prev;  /* remove from `uvhead' list */  
  24.   uv->u.l.prev->u.l.next = uv->u.l.next;  
  25. }  

接下来来看user data。这里首先我们要知道,在lua中,创建一个userdata,其实也就是分配一块内存紧跟在Udata的后面。后面我们分析代码的时候就会看到。也就是说Udata相当于一个头。 

Java代码  收藏代码
  1. typedef union Udata {  
  2.   L_Umaxalign dummy;    
  3.   struct {  
  4. ///gc类型的都会包含这个头,前面已经描述过了。  
  5.     CommonHeader;  
  6. ///元标  
  7.     struct Table *metatable;  
  8. ///环境  
  9.     struct Table *env;  
  10. ///当前user data的大小。  
  11.     size_t len;  
  12.   } uv;  
  13. } Udata;  


ok,接下来我们来看代码,我们知道调用lua_newuserdata能够根据指定大小分配一块内存,并将对应的userdata压入栈。 

这里跳过了一些代码,跳过的代码以后会分析到。 
Java代码  收藏代码
  1. LUA_API void *lua_newuserdata (lua_State *L, size_t size) {  
  2.   Udata *u;  
  3.   lua_lock(L);  
  4.   luaC_checkGC(L);  
  5. ///new一个新的user data,然后返回地址  
  6.   u = luaS_newudata(L, size, getcurrenv(L));  
  7. ///将u压入压到栈中。  
  8.   setuvalue(L, L->top, u);  
  9. ///更新栈顶指针  
  10.   api_incr_top(L);  
  11.   lua_unlock(L);  
  12. ///返回u+1,也就是去掉头(Udata)然后返回。  
  13.   return u + 1;  
  14. }  


我们可以看到具体的实现都包含在luaS_newudata中,这个函数也满简单的,malloc一个size+sizeof(Udata)的内存,然后初始化udata。 

我们还要知道在全局状态,也就是global_State中包含一个struct lua_State *mainthread,这个主要是用来管理userdata的。它也就是表示当前的栈,因此下面我们会将新建的udata链接到它上面。 

Java代码  收藏代码
  1. Udata *luaS_newudata (lua_State *L, size_t s, Table *e) {  
  2.   Udata *u;  
  3.   
  4. ///首先检测size,userdata是由大小限制的。  
  5.   if (s > MAX_SIZET - sizeof(Udata))  
  6.     luaM_toobig(L);  
  7. ///然后malloc一块内存。  
  8.   u = cast(Udata *, luaM_malloc(L, s + sizeof(Udata)));  
  9. ///这里gc相关的东西,以后分析gc时再说。  
  10.   u->uv.marked = luaC_white(G(L));  /* is not finalized */  
  11. ///设置类型  
  12.   u->uv.tt = LUA_TUSERDATA;  
  13.   
  14. ///设置当前udata大小  
  15.   u->uv.len = s;  
  16.   u->uv.metatable = NULL;  
  17.   u->uv.env = e;  
  18.   /* chain it on udata list (after main thread) */  
  19. ///然后链接到mainthread中  
  20.   u->uv.next = G(L)->mainthread->next;  
  21.   G(L)->mainthread->next = obj2gco(u);  
  22.   
  23. ///然后返回。  
  24.   return u;  
  25. }  


还剩下两个gc类型,一个是proto(函数包含的一些东西)一个是lua_State(也就是协程). 

我们来简单看一下lua_state,顾名思义,它就代表了状态,一个lua栈(或者叫做线程也可以),每次c与lua交互都会新建一个lua_state,然后才能互相通过交互。可以看到在new state的时候它的tt就是LUA_TTHREAD。 

并且每个协程也都有自己独立的栈。 

我们就来看下我们前面已经触及到的一些lua-state的域: 

Java代码  收藏代码
  1. struct lua_State {  
  2.   CommonHeader;  
  3.    
  4. ///栈相关的  
  5.   StkId top;  /* first free slot in the stack */  
  6.   StkId base;  /* base of current function */  
  7.   StkId stack_last;  /* last free slot in the stack */  
  8.   StkId stack;  /* stack base */  
  9. ///指向全局的状态。  
  10.   global_State *l_G;  
  11.   
  12. ///函数相关的  
  13.   CallInfo *ci;  /* call info for current function */  
  14.   const Instruction *savedpc;  /* `savedpc' of current function */  
  15.   CallInfo *end_ci;  /* points after end of ci array*/  
  16.   CallInfo *base_ci;  /* array of CallInfo's */  
  17.   lu_byte status;  
  18. ///一些要用到的len,栈大小,c嵌套的数量,等。  
  19.   int stacksize;  
  20.   int size_ci;  /* size of array `base_ci' */  
  21.   unsigned short nCcalls;  /* number of nested C calls */  
  22.   unsigned short baseCcalls;  /* nested C calls when resuming coroutine */  
  23.   lu_byte hookmask;  
  24.   lu_byte allowhook;  
  25.   int basehookcount;  
  26.   int hookcount;  
  27.   lua_Hook hook;  
  28.   
  29. ///一些全局(这个状态)用到的东西,比如env等。  
  30.   TValue l_gt;  /* table of globals */  
  31.   TValue env;  /* temporary place for environments */  
  32.   
  33. ///gc相关的东西。  
  34.   GCObject *openupval;  /* list of open upvalues in this stack */  
  35.   GCObject *gclist;  
  36.   
  37. ///错误处理相关。  
  38.   struct lua_longjmp *errorJmp;  /* current error recover point */  
  39.   ptrdiff_t errfunc;  /* current error handling function (stack index) */  
  40. };  


而global_State主要就是包含了gc相关的东西。 

现在基本类型的分析就告一段落了,等到后面分析parse以及gc的时候会再回到这些类型。 
0 0