`
simohayha
  • 浏览: 1403622 次
  • 性别: Icon_minigender_1
  • 来自: 火星
社区版块
存档分类
最新评论

lua源码剖析(二)

    博客分类:
  • lua
阅读更多
这次紧接着上次的,将gc类型的数据分析完毕。


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


先来看闭包:

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

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

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



typedef union Closure {
  CClosure c;
  LClosure l;
} Closure;


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

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

更新 :
isC:如果是c函数这个值为1,为lua的函数则为0.

nupvalues:表示upvalue或者upvals的大小(闭包和函数里面的)。
gclist:链接到全局的gc链表。
env:环境,可以看到它是一个table类型的,他里面保存了一些全局变量等。

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


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

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


typedef struct CClosure {
  ClosureHeader;
  lua_CFunction f;
  TValue upvalue[1];
} CClosure;


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



LUA_API void lua_pushcclosure (lua_State *L, lua_CFunction fn, int n) {
  Closure *cl;
  lua_lock(L);
  luaC_checkGC(L);
  api_checknelems(L, n);
///new一个cclosure
  cl = luaF_newCclosure(L, n, getcurrenv(L));
  cl->c.f = fn;
  L->top -= n;
///开始将参数值放到upvalue中.
  while (n--)
    setobj2n(L, &cl->c.upvalue[n], L->top+n);
  setclvalue(L, L->top, cl);
  lua_assert(iswhite(obj2gco(cl)));
  api_incr_top(L);
  lua_unlock(L);
}



然后来看LClosure 的实现。

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


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

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


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

typedef struct LClosure {
  ClosureHeader;
  struct Proto *p;
  UpVal *upvals[1];
} 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。如果打开则为一个链表。

typedef struct UpVal {
  CommonHeader;
  TValue *v;  /* points to stack or to its own value */
  union {
    TValue value;  /* the value (when closed) */
    struct {  /* double linked list (when open) */
      struct UpVal *prev;
      struct UpVal *next;
    } l;
  } u;
} UpVal;


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


Closure *luaF_newLclosure (lua_State *L, int nelems, Table *e) {
  Closure *c = cast(Closure *, luaM_malloc(L, sizeLclosure(nelems)));
  luaC_link(L, obj2gco(c), LUA_TFUNCTION);
  c->l.isC = 0;
  c->l.env = e;
///更新upvals。
  c->l.nupvalues = cast_byte(nelems);
  while (nelems--) c->l.upvals[nelems] = NULL;
  return c;
}



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

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

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


在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的。

UpVal *luaF_newupval (lua_State *L) {
///new一个upval
  UpVal *uv = luaM_new(L, UpVal);
///链接到全局的gc中
  luaC_link(L, obj2gco(uv), LUA_TUPVAL);
///可以看到这里的upval是close的。
  uv->v = &uv->u.value;
  setnilvalue(uv->v);
  return uv;
}


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

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

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

UpVal *luaF_findupval (lua_State *L, StkId level) {
  global_State *g = G(L);
///得到openupval链表
  GCObject **pp = &L->openupval;
  UpVal *p;
  UpVal *uv;
///开始遍历open upvalue。
  while (*pp != NULL && (p = ngcotouv(*pp))->v >= level) {
    lua_assert(p->v != &p->u.value);
///发现已存在。
    if (p->v == level) {  
      if (isdead(g, obj2gco(p)))  /* is it dead? */
        changewhite(obj2gco(p));  /* ressurect it */
///直接返回
      return p;
    }
    pp = &p->next;
  }
///否则new一个新的upvalue
  uv = luaM_new(L, UpVal);  /* not found: create a new one */
  uv->tt = LUA_TUPVAL;
  uv->marked = luaC_white(g);
///设置值
  uv->v = level;  /* current value lives in the stack */
///首先插入到lua_state的openupval域
  uv->next = *pp;  /* chain it in the proper position */
  *pp = obj2gco(uv);
///然后插入到global_State的uvhead(这个也就是双向链表的头)
  uv->u.l.prev = &g->uvhead;  /* double link it in `uvhead' list */
  uv->u.l.next = g->uvhead.u.l.next;
  uv->u.l.next->u.l.prev = uv;
  g->uvhead.u.l.next = uv;
  lua_assert(uv->u.l.next->u.l.prev == uv && uv->u.l.prev->u.l.next == uv);
  return uv;
}


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



void luaF_close (lua_State *L, StkId level) {
  UpVal *uv;
  global_State *g = G(L);
///开始遍历open upvalue
  while (L->openupval != NULL && (uv = ngcotouv(L->openupval))->v >= level) {
    GCObject *o = obj2gco(uv);
    lua_assert(!isblack(o) && uv->v != &uv->u.value);
    L->openupval = uv->next;  /* remove from `open' list */
    if (isdead(g, o))
      luaF_freeupval(L, uv);  /* free upvalue */
    else {
///unlink掉当前的uv.
      unlinkupval(uv);
      setobj(L, &uv->u.value, uv->v);
      uv->v = &uv->u.value;  /* now current value lives here */
      luaC_linkupval(L, uv);  /* link upvalue into `gcroot' list */
    }
  }
}

static void unlinkupval (UpVal *uv) {
  lua_assert(uv->u.l.next->u.l.prev == uv && uv->u.l.prev->u.l.next == uv);
  uv->u.l.next->u.l.prev = uv->u.l.prev;  /* remove from `uvhead' list */
  uv->u.l.prev->u.l.next = uv->u.l.next;
}

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


typedef union Udata {
  L_Umaxalign dummy;  
  struct {
///gc类型的都会包含这个头,前面已经描述过了。
    CommonHeader;
///元标
    struct Table *metatable;
///环境
    struct Table *env;
///当前user data的大小。
    size_t len;
  } uv;
} Udata;


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

这里跳过了一些代码,跳过的代码以后会分析到。

LUA_API void *lua_newuserdata (lua_State *L, size_t size) {
  Udata *u;
  lua_lock(L);
  luaC_checkGC(L);
///new一个新的user data,然后返回地址
  u = luaS_newudata(L, size, getcurrenv(L));
///将u压入压到栈中。
  setuvalue(L, L->top, u);
///更新栈顶指针
  api_incr_top(L);
  lua_unlock(L);
///返回u+1,也就是去掉头(Udata)然后返回。
  return u + 1;
}


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

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


Udata *luaS_newudata (lua_State *L, size_t s, Table *e) {
  Udata *u;

///首先检测size,userdata是由大小限制的。
  if (s > MAX_SIZET - sizeof(Udata))
    luaM_toobig(L);
///然后malloc一块内存。
  u = cast(Udata *, luaM_malloc(L, s + sizeof(Udata)));
///这里gc相关的东西,以后分析gc时再说。
  u->uv.marked = luaC_white(G(L));  /* is not finalized */
///设置类型
  u->uv.tt = LUA_TUSERDATA;

///设置当前udata大小
  u->uv.len = s;
  u->uv.metatable = NULL;
  u->uv.env = e;
  /* chain it on udata list (after main thread) */
///然后链接到mainthread中
  u->uv.next = G(L)->mainthread->next;
  G(L)->mainthread->next = obj2gco(u);

///然后返回。
  return u;
}


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

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

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

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

struct lua_State {
  CommonHeader;
 
///栈相关的
  StkId top;  /* first free slot in the stack */
  StkId base;  /* base of current function */
  StkId stack_last;  /* last free slot in the stack */
  StkId stack;  /* stack base */
///指向全局的状态。
  global_State *l_G;

///函数相关的
  CallInfo *ci;  /* call info for current function */
  const Instruction *savedpc;  /* `savedpc' of current function */
  CallInfo *end_ci;  /* points after end of ci array*/
  CallInfo *base_ci;  /* array of CallInfo's */
  lu_byte status;
///一些要用到的len,栈大小,c嵌套的数量,等。
  int stacksize;
  int size_ci;  /* size of array `base_ci' */
  unsigned short nCcalls;  /* number of nested C calls */
  unsigned short baseCcalls;  /* nested C calls when resuming coroutine */
  lu_byte hookmask;
  lu_byte allowhook;
  int basehookcount;
  int hookcount;
  lua_Hook hook;

///一些全局(这个状态)用到的东西,比如env等。
  TValue l_gt;  /* table of globals */
  TValue env;  /* temporary place for environments */

///gc相关的东西。
  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) */
};


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

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





2
0
分享到:
评论
1 楼 joshzhu 2009-12-15  
bobo,isC是用来标示它是C函数还是Lua函数的

相关推荐

    Lua源码剖析11

    Lua 源码剖析 —— 解析 Lua 源码的实现机制 Lua 是一款轻量级的脚本语言,广泛应用于游戏开发、软件开发等领域。 Lua 的源码剖析是学习 Lua 语言的必经之路。本文将对 Lua 源码的实现机制进行详细的解析,从 main...

    lua源码剖析

    【lua源码剖析】 Lua是一种轻量级的脚本语言,因其简洁、高效而被广泛应用于游戏开发、嵌入式系统以及服务器配置等场景。源码剖析有助于深入了解其内部机制和设计思想,这对于优化lua应用、扩展lua功能或进行跨语言...

    lua 源码剖析

    《lua 源码剖析》是一本深入探讨lua编程语言...通过对lua源码的学习,开发者不仅能掌握lua语言的精髓,还能提升编程技巧,更好地利用lua解决实际问题。同时,源码阅读也能培养程序员的底层思维,增强系统级编程能力。

    Lua 源码剖析.rar

    《Lua 源码剖析》是由知名IT专家云风精心撰写的关于Lua编程语言源代码解析的著作。这本书深入浅出地探讨了Lua的核心机制,为开发者提供了深入了解和掌握Lua的宝贵资源。以下是对该书内容的详细概述: 1. **Lua简介*...

    Lua源码剖析21

    《Lua源码剖析:词法分析与语法解析》 Lua是一种轻量级的脚本语言,其源码设计精巧且高效。本文将深入探讨Lua的词法分析和语法解析过程,帮助读者理解其核心机制。 词法分析是编译器的第一步,它将输入的文本转换...

    lua源码剖析.doc

    《Lua源码剖析》 Lua是一种轻量级的脚本语言,因其简洁高效而广泛应用于游戏开发、热更新等领域。本文将深入探讨Lua的核心数据结构和虚拟机机制,以帮助理解其内部工作原理。 首先,我们要关注的是Lua中的基本值...

    Lua源码剖析32

    《Lua源码剖析:VM解析》 Lua是一种轻量级的、高效的脚本语言,它的虚拟机(VM)是其核心部分,负责解释并执行字节码。在深入理解Lua虚拟机的工作机制时,我们首先关注的是luaV_execute函数,它是Lua VM执行的起点...

    Lua源码剖析,Lua虚拟机的机制分析,硕士论文

    本文将深入剖析Lua源码,细致分析Lua虚拟机的实现机制,并与Python虚拟机的实现进行对比,探究两者效率的差异,并提出对Lua虚拟机执行框架和垃圾回收(GC)机制的改进方案。 首先,我们必须了解Lua语言的特性。作为...

    Lua源码剖析.doc

    本文将深入解析Lua源码,探讨其核心数据结构和内存管理机制,以及如何与C语言交互。 首先,我们关注Lua中值的表示方式。在Lua中,所有值都由`TValue`结构体表示,它包含一个`Value`联合体和一个`tt`类型标记。`tt`...

    Lua源码剖析

    ### Lua源码剖析:深入理解Lua的内部机制 #### 引言 Lua,作为一种轻量级、高效且可嵌入的脚本语言,在游戏开发、Web应用、自动化脚本等领域有着广泛的应用。其源码的剖析不仅能够帮助我们更深入地理解Lua的工作...

    lua源码导读---云风

    本书《lua源码导读》旨在深入剖析 Lua 的源代码,帮助读者理解其内部实现原理。 #### 源文件划分 Lua 的源代码组织清晰有序,每个模块负责一部分功能,这种模块化的处理方式使得代码易于理解和维护。主要包括以下...

    Lua源码分析

    以下是对Lua源码分析的一些详细知识点。 Lua源码主要可以分为几个部分:虚拟机运转的核心功能、源文件划分、代码风格、Lua核心、代码翻译及预编译字节码、内嵌库、独立解析器及字节码编译器、阅读源代码的次序。...

    lua-5.3.4源码

    本文将针对Lua 5.3.4版本的源码进行深度剖析,旨在帮助开发者更深入地理解其内部机制。 一、Lua语言特性 Lua 5.3.4在语言设计上延续了其一贯的精简风格,主要特性包括: 1. 动态类型:Lua的所有值都有类型,类型...

    lua 源码分析

    《深入剖析:Lua源码分析》 一、引言与背景 Lua,作为一种小巧而强大的脚本语言,凭借其高效性、灵活性以及易嵌入特性,在游戏开发、系统管理、Web应用等多个领域得到了广泛的应用。它由标准C语言编写而成,这不仅...

    Lua语言程序设计合集(8本)

    共八本: Lua程序设计(第二版);LUA脚本语言参考文档;Lua脚本语言中文教程;Lua性能优化技巧;Lua虚拟机指令集介绍;lua源码剖析;Lua源码赏析;使用notepad 运行python和lua的配置

    LUA-5.3.2 源码

    本文将针对LUA-5.3.2的源码进行深入剖析,旨在帮助读者理解其内部机制,提升编程技巧。 首先,让我们从源码结构开始。`lua-5.3.2`目录下包含了所有源代码和头文件,这使得源码易于阅读和理解。主要的源文件有`lapi....

    lua分析分析

    《深入理解Lua源码解析》 Lua是一种轻量级、高效、可扩展的脚本语言,广泛应用于游戏开发、嵌入式系统以及各种应用程序中。本文将从lua.c的main函数出发,逐步剖析Lua的源码,以帮助读者更好地理解和应用Lua。 1. ...

    cocos2dx骨骼动画Armature源码剖析(二)

    本文将深入探讨Armature的源码,特别是Flash数据与XML数据之间的关系。 首先,`<skeleton>`节点包含了关于整个骨骼动画的基本信息。`name`属性标识了Flash文件的名称,`frameRate`定义了动画的帧率,而`version`则...

    cpp-Lua514版本代码注释

    本文将针对Lua 5.1.4版本的源代码进行深入剖析,特别关注C++开发者如何理解和利用这些源码。我们将从C++的角度出发,探讨Lua的内存管理、虚拟机设计、语法解析、运行机制以及与C++的交互等核心知识点。 1. **内存...

    CodeLearn_Lua:Lua源码分析

    "CodeLearn_Lua"项目提供了对Lua源码的深度剖析,旨在帮助开发者更好地理解和掌握Lua的内部工作原理,从而提升编程能力,优化代码性能。 首先,让我们来看看"CodeLearn_Lua"项目的核心内容。源码分析通常包括以下几...

Global site tag (gtag.js) - Google Analytics