加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 大数据 > 正文

lua源码剖析(二)

发布时间:2020-12-14 22:25:01 所属栏目:大数据 来源:网络整理
导读:lua源码剖析(二) 文章分类:综合技术 这次紧接着上次的,将gc类型的数据分析完毕。? 谢谢 老朱同学的指正,这里CClosure和LClosure理解有误.? 先来看闭包:? 可以看到闭包也是会有两种类型,这是因为在lua中,函数不过是一种特殊的闭包而已。? 更新:这里CClosur

lua源码剖析(二)

文章分类:综合技术
这次紧接着上次的,将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,?Table?*e)?{??
  2. ///分配内存??
  3. ??Closure?*c?=?cast(Closure?*,?sizeCclosure(nelems)));??
  4. ///链接到全局的gc链表??
  5. ??luaC_link(L,?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,?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?*,?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的时候会再回到这些类型。?

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读