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

lua源码剖析(三)

    博客分类:
  • lua
阅读更多
这次简单的补充一下前面类型部分剩下的东西。

首先我们要知道当我们想为lua来编写扩展的时候,有时候可能需要一些全局变量。可是这样会有问题,这是因为这样的话,我们就无法用于多个lua状态(也就是new 多个state).

于是lua提供了三种可以代替全局变量的方法。分别是注册表,环境变量和upvalue。

其中注册表和环境变量都是table。而upvalue也就是我们前面介绍的用来和指定函数关联的一些值。

由于lua统一了从虚拟的栈上存取数据的接口,而这三个值其实并不是在栈上保存,而lua为了统一接口,通过伪索引来存取他们。接下来我们就会通过函数index2adr的代码片断来分析这三个类型。

其实还有一种也是伪索引来存取的,那就是全局状态。也就是state的l_gt域。

ok,我们来看这几种伪索引的表示,每次传递给index2adr的索引就是下面这几个:


#define LUA_REGISTRYINDEX	(-10000)
#define LUA_ENVIRONINDEX	(-10001)
#define LUA_GLOBALSINDEX	(-10002)

///这个就是来存取upvalue。
#define lua_upvalueindex(i)	(LUA_GLOBALSINDEX-(i))


来看代码,这个函数我们前面有分析过,只不过跳过了伪索引这部分,现在我们来看剩下的部分。

其实很简单,就是通过传递进来的index来确定该到哪部分处理。

这里他们几个处理有些不同,这是因为注册表是全局的(不同模块也能共享),环境变量可以是整个lua_state共享,也可以只是这个函数所拥有。而upvalue只能属于某个函数。

看下它们所在的位置,他们的作用域就很一目了然了。

其中注册表包含在global_State中,环境变量 closure和state都有,upvalue只在closure中包含。


static TValue *index2adr (lua_State *L, int idx) {
....................
  else switch (idx) {  /* pseudo-indices */
///注册表读取
    case LUA_REGISTRYINDEX: return registry(L);
///环境变量的存取
    case LUA_ENVIRONINDEX: {
///先得到当前函数
      Closure *func = curr_func(L);
///将当前函数的env设置为整个state的env。这样整个模块都可以共享。
      sethvalue(L, &L->env, func->c.env);
      return &L->env;
    }
///用来取global_State。
    case LUA_GLOBALSINDEX: return gt(L);

///取upvalue
    default: {
///取得当前函数
      Closure *func = curr_func(L);
///转换索引
      idx = LUA_GLOBALSINDEX - idx;
///从upvalue数组中取得对应的值。
      return (idx <= func->c.nupvalues)
                ? &func->c.upvalue[idx-1]
                : cast(TValue *, luaO_nilobject);
    }
  }
}


下面就是取得环境变量和注册表的对应的宏。

#define registry(L)	(&G(L)->l_registry)
#define gt(L)	(&L->l_gt)


我们一个个的来看,首先是注册表。由于注册表是全局的,所以我们需要很好的选择key,尽量避免冲突,而在选择key中,不能使用数字类型的key,这是因为在lua中,数字类型的key是被引用系统所保留的。

来看引用系统,我们编写lua模块时可以看到所有的值,函数,table,都是在栈上保存着,也就是说它们都是由lua来管理,我们要存取只能通过栈来存取。可是lua为了我们能够在c这边保存一个lua的值的指针,提供了luaL_ref这个函数。

引用也就是在c这边保存lua的值对象。

来看引用的实现,可以看到它是传递LUA_REGISTRYINDEX给luaL_ref函数,也就是说引用也是全局的,保存在注册表中的。
#define lua_ref(L,lock) ((lock) ? luaL_ref(L, LUA_REGISTRYINDEX) : \
      (lua_pushstring(L, "unlocked references are obsolete"), lua_error(L), 0))


然后来看它的key的计算。

可以看到当要引用的值是nil时,直接返回LUA_REFNIL这个常量,并不会创建新的引用。

还有一个要注意的就是这里注册表有个FREELIST_REF的key,这个key所保存的值就是我们最后一次unref掉的那个key。我们接下来看luaL_unref的时候会看到。

这里为什么要这么做呢,这是因为在注册表中key是不能重复的,因此这里的key的选择是通过注册表这个table的大小来做key的,而这里每次unref之后我们通过设置t[FREELIST_REF]的值为上一次被unref掉的引用的key。这样当我们再次需要引用的时候,我们就不需要增长table的大小并且也不需要再次计算key,而是直接将上一次被unref掉得key返回就可以了。

而这里上上一次被unref掉得ref的key是被保存在t[ref]中的。我们先来看luaL_unref的实现。

LUALIB_API void luaL_unref (lua_State *L, int t, int ref) {
  if (ref >= 0) {
///取出注册表的table
    t = abs_index(L, t);
///得到t[FREELIST_REF];
    lua_rawgeti(L, t, FREELIST_REF);
///这里可以看到如果再次unref的话t[ref]就保存就的是上上一次的key的值。
    lua_rawseti(L, t, ref);  /* t[ref] = t[FREELIST_REF] */

///将ref压入栈
    lua_pushinteger(L, ref);
///设置t[FREELIST_REF]为ref。
    lua_rawseti(L, t, FREELIST_REF);  /* t[FREELIST_REF] = ref */
  }
}


通过上面可以看到lua这里实现得很巧妙,通过表的t[FREELIST_REF]来保存最新的被unref掉得key,t[ref]来保存上一次被unref掉得key.然后我们就可以通过这个递归来得到所有已经被unref掉得key。接下来的luaL_ref就可以清晰的看到这个操作。也就是说t[FREELIST_REF]相当于一个表头。


来看luaL_ref,这个流程很简单,就是先取出注册表的那个table,然后将得到t[FREELIST_REF]来看是否有已经unref掉得key,如果有则进行一系列的操作(也就是上面所说的,将这个ref从freelist中remove,然后设置t[FREELIST_REF]为上上一次unref掉得值(t[ref])),最后设置t[ref]的值。这样我们就不需要遍历链表什么的。

这里要注意就是调用这个函数之前栈的最顶端保存的就是我们要引用的值。

LUALIB_API int luaL_ref (lua_State *L, int t) {
  int ref;
///取得索引
  t = abs_index(L, t);
  if (lua_isnil(L, -1)) {
    lua_pop(L, 1);  /* remove from stack */
///如果为nil,则直接返回LUA_REFNIL.
    return LUA_REFNIL;  
  }
///得到t[FREELIST_REF].
  lua_rawgeti(L, t, FREELIST_REF);
///设置ref = t[FREELIST_REF] 
  ref = (int)lua_tointeger(L, -1); 
///弹出t[FREELIST_REF] 
  lua_pop(L, 1);  /* remove it from stack */

///如果ref不等于0,则说明有已经被unref掉得key。
  if (ref != 0) {  /* any free element? */
///得到t[ref],这里t[ref]保存就是上上一次被unref掉得那个key。
    lua_rawgeti(L, t, ref);  /* remove it from list */
///设置t[FREELIST_REF] = t[ref],这样当下次再进来,我们依然可以通过freelist来直接返回key。
    lua_rawseti(L, t, FREELIST_REF); 
  }
  else {  /* no free elements */
///这里是通过注册表的大小来得到对应的key
    ref = (int)lua_objlen(L, t);
    ref++;  /* create new reference */
  }

//设置t[ref]=value;
  lua_rawseti(L, t, ref);
  return ref;
}


所以我们可以看到我们如果要使用注册表的话,尽量不要使用数字类型的key,不然的话就很容易和引用系统冲突。

不过在PIL中介绍了一个很好的key的选择,那就是使用代码中静态变量的地址(也就是用light userdata),因为c链接器可以保证key的唯一性。详细的东西可以去看PIL.

然后我们来看LUA_ENVIRONINDEX,环境是可以被整个模块共享的。可以先看PIL中的例子代码:

int luaopen_foo(lua_State *L)
{
     lua_newtable(L);
     lua_replace(L,LUA_ENVIRONIDEX);
     luaL_register(L,<lib name>,<func list>);
..........................................
}


可以看到我们一般都是为当前模块创建一个新的table,然后当register注册的所有函数就都能共享这个env了。

来看代码片断,register最终会调用luaI_openlib:

LUALIB_API void luaI_openlib (lua_State *L, const char *libname,const luaL_Reg *l, int nup) {
 ...........................
///遍历模块内的所有函数。
  for (; l->name; l++) {
    int i;
    for (i=0; i<nup; i++)  /* copy upvalues to the top */
      lua_pushvalue(L, -nup);
///这里将函数压入栈,这个函数我们前面分析过,他最终会把当前state的env赋值给新建的closure,也就是说这里最终模块内的所有函数都会共享当前的state的env。
    lua_pushcclosure(L, l->func, nup);
    lua_setfield(L, -(nup+2), l->name);
  }
  lua_pop(L, nup);  /* remove upvalues */
}


通过我们一开始分析的代码,我们知道当我们要存取环境的时候每次都是将当前调用的函数的env指针赋值给state的env,然后返回state的env(&L->env)。这是因为state是被整个模块共享的,每个函数修改后必须与state的那个同步。

最后我们来看upvalue。这里指的是c函数的upvalue,我们知道在lua中closure分为两个类型,一个是c函数,一个是lua函数,我们现在主要就是来看c函数。

c函数的upvalue和lua的类似,也就是将我们以后函数调用所需要得一些值保存在upvalue中。

这里一般都是通过lua_pushcclosure这个函数来做的。下面先来看个例子代码:


static int counter(lua_state *L);

int newCounter(lua_State *L)
{
    lua_pushinteger(L,0);
    lua_pushcclosure(L,&counter,1);
    return 1;
}


上面的代码很简单,就是先push进去一个整数0,然后再push一个closure,这里closure的第三个参数就是upvalue的个数(这里要注意在lua中的upvalue的个数只有一个字节,因此你太多upvalue会被截断)。


lua_pushcclosure的代码前面已经分析过了,我们这里简单的再介绍一下。

这个函数每次都会新建一个closure,然后将栈上的对应的value拷贝到closure的upvalue中,这里个数就是它的第三个参数来确定的。

而取得upvalue也很简单,就是通过index2adr来计算对应的upvalue中的索引值,最终返回对应的值。


然后我们来看light userdata,这种userdata和前面讲得userdata的区别就是这种userdata的管理是交给c函数这边来管理的。

这个实现很简单,由于它只是一个指针,因此只需要将这个值压入栈就可以了。
LUA_API void lua_pushlightuserdata (lua_State *L, void *p) {
  lua_lock(L);
///设置对应的值。
  setpvalue(L->top, p);
  api_incr_top(L);
  lua_unlock(L);
}


最后我们来看元表。我们知道在lua中每个值都有一个元表,而table和userdata可以有自己独立的元表,其他类型的值共享所属类型的元表。在lua中可以使用setmetatable.而在c中我们是通过luaL_newmetatable来创建一个元表。

元表其实也就是保存了一种类型所能进行的操作。

这里要知道在lua中元表是保存在注册表中的。

因此我们来看luaL_newmetatable的实现。
这里第二个函数就是当前所要注册的元表的名字。这里一般都是类型名字。这个是个key,因此我们一般要小心选择类型名。

LUALIB_API int luaL_newmetatable (lua_State *L, const char *tname) {
///首先从注册表中取得key为tname的元表
  lua_getfield(L, LUA_REGISTRYINDEX, tname);  
///如果存在则失败,返回0
  if (!lua_isnil(L, -1))  /* name already in use? */
    return 0; 
  lua_pop(L, 1);
///创建一个元表
  lua_newtable(L);  /* create metatable */
///压入栈
  lua_pushvalue(L, -1);
///设置注册表中的对应的元表。
  lua_setfield(L, LUA_REGISTRYINDEX, tname);  
  return 1;
}


当我们设置完元表之后我们就可以通过调用luaL_checkudata来检测栈上的userdata的元表是否和指定的元表匹配。
这里第二个参数是userdata的位置,tname是要匹配的元表的名字。

这里我们要知道在lua中,Table和userdata中都包含一个metatable域,这个也就是他们对应的元表,而基本类型的元表是保存在global_State的mt中的。这里mt是一个数组。

这里我们先来看lua_getmetatable,这个函数返回当前值的元表。
这里代码很简单,就是取值,然后判断类型。最终返回设置元表。

LUA_API int lua_getmetatable (lua_State *L, int objindex) {
  const TValue *obj;
  Table *mt = NULL;
  int res;
  lua_lock(L);
///取得对应索引的值
  obj = index2adr(L, objindex);
///开始判断类型。
  switch (ttype(obj)) {
///table类型
    case LUA_TTABLE:
      mt = hvalue(obj)->metatable;
      break;
///userdata类型
    case LUA_TUSERDATA:
      mt = uvalue(obj)->metatable;
      break;
    default:
///这里是基础类型
      mt = G(L)->mt[ttype(obj)];
      break;
  }
  if (mt == NULL)
    res = 0;
  else {
///设置元表到栈的top
    sethvalue(L, L->top, mt);
    api_incr_top(L);
    res = 1;
  }
  lua_unlock(L);
  return res;
}



接下来来看checkudata的实现。他就是取得当前值的元表,然后取得tname对应的元表,最后比较一下。


LUALIB_API void *luaL_checkudata (lua_State *L, int ud, const char *tname) {
  void *p = lua_touserdata(L, ud);
  if (p != NULL) {  /* value is a userdata? */
///首先取得当前值的元表。
    if (lua_getmetatable(L, ud)) { 
///然后取得taname对应的元表。
      lua_getfield(L, LUA_REGISTRYINDEX, tname);  
//比较。
      if (lua_rawequal(L, -1, -2)) {  
        lua_pop(L, 2);  /* remove both metatables */
        return p;
      }
    }
  }
  luaL_typerror(L, ud, tname);  /* else error */
  return NULL;  /* to avoid warnings */
}







2
1
分享到:
评论
4 楼 simohayha 2009-12-22  
lua 写道

看过那篇,实际上也跟着读了一部分(见我的博客http://sunxiunan.com),但是总感觉这种读法有些光读不练(缺乏实际运行状态分析)。

我准备是一边调试一边阅读相应碰到的代码,这也是我接触一个新project时候的做法。这样运行时的状态值以及堆栈信息、相关信息就都一目了然了,整个Lua系统也是活动而不是死的。


恩.一般都是平时写扩展什么比较多了,才能更好的理解源码里的一些东西..或者说直接开gdb,加调试信息也是个好办法..
3 楼 lua 2009-12-22  
simohayha 写道
lua 写道
尽管我也读了一些lua代码,但是看着还是比较费劲。

是否能够动静结合分析会更好一些?


你可以看下这个阅读顺序.

http://www.reddit.com/comments/63hth/ask_reddit_which_oss_codebases_out_there_are_so/c02pxbp

还有就是lua 5.1实现的那篇文档了,以及PIL的第四部分...


看过那篇,实际上也跟着读了一部分(见我的博客http://sunxiunan.com),但是总感觉这种读法有些光读不练(缺乏实际运行状态分析)。

我准备是一边调试一边阅读相应碰到的代码,这也是我接触一个新project时候的做法。这样运行时的状态值以及堆栈信息、相关信息就都一目了然了,整个Lua系统也是活动而不是死的。
2 楼 simohayha 2009-12-22  
lua 写道
尽管我也读了一些lua代码,但是看着还是比较费劲。

是否能够动静结合分析会更好一些?


你可以看下这个阅读顺序.

http://www.reddit.com/comments/63hth/ask_reddit_which_oss_codebases_out_there_are_so/c02pxbp

还有就是lua 5.1实现的那篇文档了,以及PIL的第四部分...
1 楼 lua 2009-12-22  
尽管我也读了一些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源码剖析,Lua虚拟机的机制分析,硕士论文

    Lua源码剖析、Lua虚拟机的机制分析是硕士论文的主题,本文从Lua语言的特性入手,深入分析Lua虚拟机的实现,涵盖编译过程、线程执行、函数调用和垃圾回收过程。然后,本文将Lua虚拟机中的关键技术与Pyt hon虚拟机的...

    Lua源码剖析21

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

    lua源码剖析.doc

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

    Lua源码剖析32

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

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

    cpp-Lua514版本代码注释

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

    CodeLearn_Lua:Lua源码分析

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

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

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

Global site tag (gtag.js) - Google Analytics