可可西

从源码剖析Lua数据类型

 

lua类型 lua示例 C类型(宏 ) C子类型(variant tags宏) 及详细说明 C数据结构
nil(空) type(nil-->nil #define LUA_TNIL 0 //空类型

// 判断TValue* o是否为一个nil   即:o->_tt是否为0

#define ttisnil(o) checktag((o), LUA_TNIL)

boolean(布尔) type(true) -->boolean #define LUA_TBOOLEAN 1

不被GC管理

 

// 判断TValue* o是否为一个bool值   即:o->_tt是否为1

#define ttisboolean(o) checktag((o), LUA_TBOOLEAN)

int
number(数值)

type(3.14) -->number

type(100) -->number

// 若想知道number具体类型,可使用函数math.type来获取

math.type(3.14) -->float

math.type(100) -->integer

注:对于其他非number类型,math.type会返回nil

#define LUA_TNUMBER

 

// 判断TValue* o是否为一个number  即:(o->_tt & 0xF)是否为3

#define ttisnumber(o) checktype((o), LUA_TNUMBER)

#define LUA_TNUMFLT (LUA_TNUMBER | (0 << 4))  /* float numbers */ 3  不被GC管理

 

// 判断TValue* o是否为一个浮点值   即:o->_tt是否为3

#define ttisfloat(o) checktag((o), LUA_TNUMFLT)

lua_Number

//即double

#define LUA_TNUMINT (LUA_TNUMBER | (1 << 4))  /* integer numbers */ 19  不被GC管理

 

// 判断TValue* o是否为一个整型值  即:o->_tt是否为19(0x13)

#define ttisinteger(o) checktag((o), LUA_TNUMINT)

lua_Integer

//即__int64

string(字符串)

type("Hello World") -->string

type('good') -->string

#define LUA_TSTRING 4

 

// 判断TValue* o是否为一个string  即:(o->_tt & 0xF)是否为4

#define ttisstring(o) checktype((o), LUA_TSTRING)

#define LUA_TSHRSTR (LUA_TSTRING | (0 << 4))  /* short strings */ 4  被GC管理

 

// 判断TValue* o是否为短串  即:o->_tt是否为68(0x44)

#define ttisshrstring(o) checktag((o), ctb(LUA_TSHRSTR))

TString

#define LUA_TLNGSTR (LUA_TSTRING | (1 << 4))  /* long strings */ 20  被GC管理

 

// 判断TValue* o是否为长串  即:o->_tt是否为84(0x54)

#define ttislngstring(o) checktag((o), ctb(LUA_TLNGSTR))

TString

function(函数)

type(print-->function

#define LUA_TFUNCTION 6

 

// 判断TValue* o是否为一个function

#define ttisclosure(o)  ((rttype(o) & 0x1F) == LUA_TFUNCTION)

#define LUA_TLCL (LUA_TFUNCTION | (0 << 4))  /* Lua closure */ 6   被GC管理

 

// 判断TValue* o是否为LClosure  即:o->_tt是否为70(0x46)

#define ttisLclosure(o) checktag((o), ctb(LUA_TLCL))

 

注:Lua closure即Lua function

LClosure 由 Proto 和 UpVal 组成

Proto描述了lua函数的函数原型

记录着函数原型的字节码、函数引用的常量表、调试信息、参数、栈大小等信息

UpVal保存了对upvalue的引用。它直接用一个TValue 指针引用一个upvalue值变量

当被引用的变量还在数据栈上时,这个指针直接指向栈上的TValue,那么这个upvalue被称为开放的

LClosure

#define LUA_TLCF (LUA_TFUNCTION | (1 << 4))  /* light C function */ 22  不被GC管理

 

// 判断TValue* o是否为lua_CFunction  即:o->_tt是否为22(0x16)

#define ttislcf(o) checktag((o), LUA_TLCF)

 

LUA_TLCF即:当LUA_TCCL不包含 upvalue时,直接用lua_CFunction函数指针,不必构造Closure对象

注:typedef int (*lua_CFunction) (lua_State *L) 

lua_CFunction

#define LUA_TCCL (LUA_TFUNCTION | (2 << 4))  /* C closure */ 38  被GC管理

 

// 判断TValue* o是否为CClosure  即:o->_tt是否为102(0x66)

#define ttisCclosure(o) checktag((o), ctb(LUA_TCCL))

 

注:C closure即regular C function

CClosure 由 lua_CFunction 和 TValue 组成

C 函数可以用闭包的方式嵌入 lua,与LClosure 相比,CClosure天生就是关闭的

因此,直接使用TValue来保存upvalue

CClosure

table(表) type({}) -->table #define LUA_TTABLE 5

// 判断TValue* o是否为Table  即:o->_tt是否为69(0x45)

#define ttistable(o) checktag((o), ctb(LUA_TTABLE))

可以有元表和元方法

Table
userdata(用户数据)

type(io.stdin) -->userdata

注:stdin,stdout,stderr是lua提供三种预定义文件描述

#define LUA_TLIGHTUSERDATA 2

// 判断TValue* o是否为一个light userdata数   即:o->_tt是否为2

#define ttislightuserdata(o) checktag((o), LUA_TLIGHTUSERDATA)

 

即轻量级用户数据(light userdata)

只是一个指针(在c中,调用lua_pushlightuserdata将一个指针压入栈来给lua使用)

没有元表,且无法得知其类型

与数值类型一样,不被GC管理

void*

#define LUA_TUSERDATA 7

// 判断TValue* o是否为一个full userdata数   即:o->_tt是否为71(0x47)

#define ttisfulluserdata(o) checktag((o), ctb(LUA_TUSERDATA))

 

即完全用户数据(full userdata)

通常用来表示C中的结构体,可以有元表和元方法

在c中调用lua_newuserdata创建指定大小的内存区域,被GC管理

void*
thread(线程)

type(coroutine.create(function()  end)) -->thread

#define LUA_TTHREAD 8

// 判断TValue* o是否为一个thread   即:o->_tt是否为72(0x48)

#define ttisthread(o) checktag((o), ctb(LUA_TTHREAD))

 

lua不支持真正的多线程,实际是一个协程

在c中调用lua_newstate来创建lua_State

在c中调用lua_newthread创建一个线程

被GC管理

lua_State
   

#define LUA_TNONE (-1) //无类型

 

基础结构

 

Value与TValue

lua为了方便对所有的类型进行统一管理,把它们都抽象成了一个叫做Value的union结构中   注:sizeof(Value)=8

/*
** Union of all Lua values
*/
typedef union Value {
  GCObject *gc;    /* collectable objects */
  void *p;         /* light userdata */
  int b;           /* booleans */
  lua_CFunction f; /* light C functions */
  lua_Integer i;   /* integer numbers */
  lua_Number n;    /* float numbers */
} Value;

从定义可以看出,主要把这些类型划分为了需要GC的类型和不需要GC的类型

由于Value是union的结构,所以每个Value实例里同时只会有一个字段是有效的

而为了知道具体哪个字段是有效的,也就是具体该Value是什么类型,从而有了TValue这个struct结构,主要在Value基础上wrap了一个_tt字段来标识Value的具体类型   注:sizeof(TValue)=16

#define TValuefields    Value value_; int tt_


typedef struct lua_TValue {
  TValuefields;
} TValue;

 

GCUnion、GCObject、CommonHeader

lua把所有值按是否需要被GC,划分为了一般类型和被GC管理的类型。所有需要被GC的类型,被定义在了GCUnion里

/*
** Union of all collectable objects (only for conversions)
*/
union GCUnion {
  GCObject gc;  /* common header */
  struct TString ts;  /* 字符串 */
  struct Udata u;  /* 用户数据 */
  union Closure cl;  /* 函数 */
  struct Table h;  /**/
  struct Proto p;  /* 函数原型:存放函数字节码等信息 */
  struct lua_State th;  /* 线程 */
};

可以发现String、UserData、Closure、Table、Proto、luaState等类型都是需要被GC的,GCUnion结构和Value类似,也是同时只有一个字段是有效的

所以我们自然而然会想到,是不是类似TValue一样,在外面给包一层type呢,但是lua实现这边并没有这样做

而是让TString、UData这些"子类"都在各自开头定义了一个叫做CommonHeader的宏,这个宏里包含了type和一些其他字

而每个GC类型都需要在在其struct头部定义该宏,从而可以造成一种所有GC类型都继承自一个带有CommonHeader宏的基类的假象

/*
** Common type for all collectable objects
*/
typedef struct GCObject GCObject;


/*
** Common Header for all collectable objects (in macro form, to be
** included in other objects)
*/
#define CommonHeader    GCObject *next; lu_byte tt; lu_byte marked
// 注:tt,即该GC对象的具体类型
// 注:next,指向GCObject的指针,用于GC算法内部实现链表
// 注:marked,用于GC算法内部实现


/*
** Common type has only the common header
*/
struct GCObject {
  CommonHeader;
};

这样组织的好处在于lua可以把所有的GC类型的对象都视作是一个GCObject。

再比如,lua里创建单个gcobject的函数如下

/*
** create a new collectable object (with given type and size) and link
** it to 'allgc' list.
*/
GCObject *luaC_newobj (lua_State *L, int tt, size_t sz) {
  global_State *g = G(L);
  GCObject *o = cast(GCObject *, luaM_newobject(L, novariant(tt), sz));
  o->marked = luaC_white(g);
  o->tt = tt;
  o->next = g->allgc;
  g->allgc = o;
  return o;
}

 

所有的gc类型就都会调用luaC_newobj函数来创建一个GCObject实例,区别只是在于传入的type和内存size不一样而已

该函数会根据实际传入的内存大小来开辟空间,然后填充CommonHeader部分的数据

最后,它还会把该obj挂接到global_state结构里定义的GC列表GCObject* allgc(保存所有gc类型对象的指针)的头部,以供GC模块使用

每个类型只用把创建出来的实例剩余内存部分的数据设置好即可,比如下面的String类型

#define sizelstring(l)  (sizeof(union UTString) + ((l) + 1) * sizeof(char))

/*
** Get the actual string (array of bytes) from a 'TString'.
** (Access to 'extra' ensures that value is really a 'TString'.)
*/
#define getstr(ts)  \
  check_exp(sizeof((ts)->extra), cast(char *, (ts)) + sizeof(UTString))
  
#define gco2ts(o)  \
    check_exp(novariant((o)->tt) == LUA_TSTRING, &((cast_u(o))->ts))
  
/*
** creates a new string object
*/
static TString *createstrobj (lua_State *L, size_t l, int tag, unsigned int h) {
  TString *ts;
  GCObject *o;
  size_t totalsize;  /* total size of TString object */
  // 计算一个string实例实际内存占用大小:其实是UTString结构占用,再加上(charlength+1)个char大小
  totalsize = sizelstring(l);
  // 创建GCObject
  o = luaC_newobj(L, tag, totalsize);
  ts = gco2ts(o);
  // 填充string实例特有字段
  ts->hash = h;
  ts->extra = 0;
  // 取TString关联的char数组
  getstr(ts)[l] = '\0';  /* ending 0 */
  return ts;
}

string在创建完成以后,调用了内部的gco2ts函数,把本来指向GCObject指针强转成了指向TString的指针,然后对TString的额外元数据进行了赋值

 

字符串

考虑到性能和内存等方面,lua把String分成短字符串和长字符串两类来分开处理(注:长度大于40的为长串,反之则为短串;#define LUAI_MAXSHORTLEN 40)

如:短串会先在全局stringtable的hashmap结构表中查询,若查询不到才会创建;而长串不查询,直接创建;两个相同的长串将会是两个副本,占用两份内存。

主要原因是:

①  短串复用度会比长串要高。比如obj["id"] = 12, obj["type"] = 0,类似"id"、"type"这种短串可能会在程序很多处地方使用到,如果开辟多份就有点浪费了;而长串则很少会有重复的

②  长串计算哈希耗时长

 

TString结构体

/*
** Header for string value; string bytes follow the end of this structure
** (aligned according to 'UTString'; see next).
*/
typedef struct TString {
  CommonHeader;
  lu_byte extra;  /* reserved words for short strings; "has hash" for longs */
  lu_byte shrlen;  /* length for short strings */
  unsigned int hash;
  union {
    size_t lnglen;  /* length for long strings */
    struct TString *hnext;  /* linked list for hash table */
  } u;
} TString;

字段含义:

CommonHeader -- GC对象通用的CommonHeader
extra -- 短串:用于实现保留字符串   长串:是否进行过hash的标识,为0时,表示该长串的hash还没计算过,否则表示已经计算过了
shrlen -- 短串:长度     长串:未使用
hash -- 字符串的hash值。短串:该hash值是在创建时就计算出来的    长串:只有真正需要它的hash值时,才会手动调用luaS_hashlongstr函数生成该值,lua内部现在只有在把长串作为table的key时,才会去计算它。
union{lnglen, hnext} -- 短串:由于创建时会被加入到全局stringtable的链表中,所以在该结构中保存了指向下一个TString的指针;  长串:表示该长串的长度。
注:长串和短串没有共用一个字段来表示它们的长度,主要是长串的长度可以很长,而短串最长就为40,一个byte就够用了,这边也体现了lua实现是很抠细节的,反倒是把这两个不相关的字段打包到一个union里来节约内存了。

 

UTString Union

/*
** Ensures that address after this type is always fully aligned.
*/
typedef union UTString {
  L_Umaxalign dummy;  /* ensures maximum alignment for strings */
  TString tsv;
} UTString;

为了实现TString结构的内存对齐,lua又在其上wrap了一层UTString结构

sizeof(L_Umaxalign)为8,保证UTString对象本身会按照8字节进行对齐

 

使用luaS_newlstr创建字符串

/*
** new string (with explicit length) 创建字符串
*/
TString *luaS_newlstr (lua_State *L, const char *str, size_t l) {
  if (l <= LUAI_MAXSHORTLEN)  /* short string? */
    return internshrstr(L, str, l);  // 创建短串
  else {
    TString *ts;
    if (l >= (MAX_SIZE - sizeof(TString))/sizeof(char))  // MAX_SIZE=0x7FFFFFFFFFFFFFFF
      luaM_toobig(L); // 长度太大了,直接报错
    ts = luaS_createlngstrobj(L, l);  // 创建长串
    memcpy(getstr(ts), str, l * sizeof(char)); // 将str的内容拷贝到ts的内存中
    return ts;
  }
}


/*************************************创建短串***************************************/
/*
** checks whether short string exists and reuses it or creates a new one
*/
static TString *internshrstr (lua_State *L, const char *str, size_t l) {
  TString *ts;
  global_State *g = G(L);
  unsigned int h = luaS_hash(str, l, g->seed);  // 计算Hash  为了对长度较长的字符串不逐位hash,luaS_hash函数内部也是根据长度的2次幂计算出了一个步长step,来加速hash的过程
  
  // 查找该Hash是否在全局stringtable对象g->strt中
  TString **list = &g->strt.hash[lmod(h, g->strt.size)];
  lua_assert(str != NULL);  /* otherwise 'memcmp'/'memcpy' are undefined */
  for (ts = *list; ts != NULL; ts = ts->u.hnext) {
    if (l == ts->shrlen &&
        (memcmp(str, getstr(ts), l * sizeof(char)) == 0)) {
      /* found! */
      if (isdead(g, ts))  /* dead (but not collected yet)? */
        changewhite(ts);  /* resurrect it */
      // 如果在stringtable中,直接返回
      return ts;
    }
  }
  // stringtable的元素数量已经大于桶数,那么以两倍的尺寸对stringtable进行resize
  if (g->strt.nuse >= g->strt.size && g->strt.size <= MAX_INT/2) {
    // luaS_resize是实际改变stringtable桶数量的函数,只会在2个地方被调用
    // 一个是这里:桶数量小于了元素数量,说明散列比较拥挤了,会对桶进行两倍的扩容
    // 即:newsize>oldsize。这个时候会先进行扩容,然后进行rehash。扩容跟到里面去调用的就是realloc函数。而rehash的代码也很简洁,就是简单的遍历每个桶,把每个桶里的元素再哈希到正确的桶里去
    // 另一个是在gc时,如果发现桶数量大于4倍的元素数量,说明散列太稀疏了,会对桶数量进行减半操作
    // 即:newsize < oldsize,顺序是倒过来的,需要先根据newsize进行rehash,然后在保证所有元素已经收缩到newsize个数的桶里以后,才能进行shrink操作,这里也是调用的realloc函数来实现
    luaS_resize(L, g->strt.size * 2);
    list = &g->strt.hash[lmod(h, g->strt.size)];  /* recompute with new size */
  }
  // 调用createstrobj函数创建TString。其中包括了内存的分配,CommonHeader的填充,TString特化字段的填充等
  ts = createstrobj(L, l, LUA_TSHRSTR, h);
  memcpy(getstr(ts), str, l * sizeof(char));
  ts->shrlen = cast_byte(l);
  ts->u.hnext = *list;
  // 更新stringtable的链表,以及对stringtable的元素数量加1
  *list = ts;
  g->strt.nuse++;
  return ts;
}


// 短串实现的hashmap结构体
typedef struct stringtable {
  TString **hash; // 基于TString的hashmap,也叫做散列桶。基本结构是一个数组,每个数组里存的是相同hash值的TString的链表
  int nuse;  /* number of elements */  // 当前实际的元素数
  int size;  // 当前的桶大小
} stringtable;


/*************************************创建长串***************************************/
TString *luaS_createlngstrobj (lua_State *L, size_t l) {
  TString *ts = createstrobj(L, l, LUA_TLNGSTR, G(L)->seed);
  ts->u.lnglen = l;  // 设置长串的长度变量
  return ts;
}

 

内存结构如下:

注1:将value_.gc指针强制转换为TString*类型,即可读取TString中数据

注2:(char*)(value_.gc) + sizeof(TString)即为字符串的内容

 

函数

lua函数及C函数,都是一个函数闭包。函数闭包存储了函数本身以及外围作用域的局部变量(upvalue)注:env环境也是upvalue的一种

#define ClosureHeader \
    CommonHeader; lu_byte nupvalues; GCObject *gclist
//包含了CommonHeader的宏,表明Closure是gc类型
//nupvalues为upvalue的个数
//GCObject* gclist:与gc有关,将table加入到gray表中时gclist指向gray表中的下一个元素或者为空


/*
** Upvalues for Lua closures  // Lua函数闭包的Upvalues
*/
struct UpVal {
  TValue *v;  /* points to stack or to its own value */
  lu_mem refcount;  /* reference counter */  // 引用计数
  union {
    struct {  /* (when open) */
      UpVal *next;  /* linked list */
      int touched;  /* mark to avoid cycles with dead threads */
    } open;
    TValue value;  /* the value (when closed) */  // 关闭状态时的value值
  } u;
};

// C函数闭包
typedef struct CClosure {
  ClosureHeader;
  lua_CFunction f;  // 函数指针
  TValue upvalue[1];  /* list of upvalues */  //C函数的闭包天生是关闭的,直接使用TValue来保存upvalue
} CClosure;

// Lua函数闭包
typedef struct LClosure {
  ClosureHeader;
  struct Proto *p;  // 函数原型(Prototype)的结构
  UpVal *upvals[1];  /* list of upvalues */ //函数的upvalue指针列表,记录了该函数引用的所有upvals
} LClosure;


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

 

Proto结构体

每个函数会被编译成一个称之为原型(Proto)的结构

原型主要包含6部分内容:函数基本信息(basic info:含参数数量、局部变量数量等信息)、字节码(bytecodes)、常量(constants)表、upvalue(闭包捕获的非局部变量)表、调试信息(debug info)、子函数原型列表(sub functions)

// Lua虚拟机的指令集为定长(Fixed-width)指令集,每条指令占4个字节(32bits),其中操作码(OpCode)占6bits,操作数(Operand)使用剩余的26bits
/*
** type for virtual-machine instructions;
** must be an unsigned with (at least) 4 bytes (see details in lopcodes.h)
*/
#if LUAI_BITSINT >= 32
typedef unsigned int Instruction;
#else
typedef unsigned long Instruction;
#endif

// 描述函数原型上的一个upvalue
/*
** Description of an upvalue for function prototypes
*/
typedef struct Upvaldesc {
  TString *name;  /* upvalue name (for debug information) */  // upvalue的名称(debug版字节码才有该信息)
  lu_byte instack;  /* whether it is in stack (register) */   // 是否在寄存器中
  lu_byte idx;  /* index of upvalue (in stack or in outer function's list) */  // upvalue在栈或外部函数列表中index
} Upvaldesc;

// 描述函数原型上的一个局部变量(debug版字节码才有该信息)
/*
** Description of a local variable for function prototypes
** (used for debug information)
*/
typedef struct LocVar {
  TString *varname;  // 变量名
  int startpc;  /* first point where variable is active */ // 起始指令索引
  int endpc;    /* first point where variable is dead */  // 终止指令索引
} LocVar;

// 函数原型
/*
** Function Prototypes
*/
typedef struct Proto {
  CommonHeader; // GC类型
  lu_byte numparams;  /* number of fixed parameters */ // 固定参数个数
  lu_byte is_vararg; // 是否为不定参数
  lu_byte maxstacksize;  /* number of registers needed by this function */  // 函数所需的寄存器数目
  int sizeupvalues;  /* size of 'upvalues' */  // Upvaldesc *upvalues个数
  int sizek;  /* size of 'k' */  // 常量TValue *k个数
  int sizecode;  // 指令Instruction *code个数
  int sizelineinfo; // 指令int *lineinfo行号个数 (debug版字节码才有该信息)
  int sizep;  /* size of 'p' */  // 子函数原型个数
  int sizelocvars;  // 局部变量个数
  int linedefined;  /* debug information  */  // 函数起始代码行(debug版字节码才有该信息)
  int lastlinedefined;  /* debug information  */  // 函数结束代码行(debug版字节码才有该信息)
  TValue *k;  /* constants used by the function */  // 常量表
  Instruction *code;  /* opcodes */  // 指令表
  struct Proto **p;  /* functions defined inside the function */  // 子函数原型表
  int *lineinfo;  /* map from opcodes to source lines (debug information) */  // 指令行号表(debug版字节码才有该信息)
  LocVar *locvars;  /* information about local variables (debug information) */  // 局部变量表(debug版字节码才有该信息)
  Upvaldesc *upvalues;  /* upvalue information */  // upvalue表
  struct LClosure *cache;  /* last-created closure with this prototype */  // 最近一次使用该原型创建的closure
  TString  *source;  /* used for debug information */  // 源代码文件名(debug版字节码才有该信息)
  GCObject *gclist;  // 与gc有关,将table加入到gray表中时gclist指向gray表中的下一个元素或者为空
} Proto;

这里的localvars和upvalues是函数原型中包含局部变量和upvalue的原始信息。在运行时,局部变量是存储在Lua栈上,upvalue索引是存储在LClosure的upvals字段中的

 

CClosure结构体

CClosure其实是lua在C侧对闭包的一个模拟。可以通过lua_pushcclosure函数来往栈上加入一个C闭包

// 该函数会先创建一个CClosure结构,然后把提前push到栈顶的n个元素作为upvalue,将其引用存储在CClosure的upvalue数组中
LUA_API void lua_pushcclosure (lua_State *L, lua_CFunction fn, int n) {
  lua_lock(L);
  if (n == 0) {
    setfvalue(L->top, fn);
  }
  else {
    CClosure *cl;
    api_checknelems(L, n);
    api_check(L, n <= MAXUPVAL, "upvalue index too large");
    cl = luaF_newCclosure(L, n);
    cl->f = fn;
    L->top -= n;
    while (n--) {
      setobj2n(L, &cl->upvalue[n], L->top + n);
      /* does not need barrier because closure is white */
    }
    setclCvalue(L, L->top, cl);
  }
  api_incr_top(L);
  luaC_checkGC(L);
  lua_unlock(L);
}

CClosure和LClosure最大区别,在于LClosure是需要去解析lua代码来得到upvalue以及字节码等信息,在执行时需要去根据opcode来执行;

而CClosure是一个直接的C函数,可直接执行,并且upvalue也是在创建前调用者手动push到栈上去的。

 

表(table)

table应该算是lua最灵魂的一个结构了,它有以下特点:

容器功能:与其他语言相似,lua也内置了容器功能,也就是table。而与其他语言不同的是,lua内置容器只有table。

table的内部结构又分为了数组和哈希表(字典)两个部分,根据不同情况来决定使用哪个部分。

面向对象功能:与其他语言不同的时,lua并没有把面向对象的功能以语法的形式包装给开发者。

而是保留了这样一种能力,待开发者去实现自己的面向对象,而这一保留的能力,也是封装在table里的。

table里可以组合一个metatable,这个metatable本身也是一个table,它的字段用来描述原table的行为。

#define TValuefields    Value value_; int tt_


typedef union TKey {
  struct {
    TValuefields; // 主要是为了方便对value_、tt_变量的访问,不用写成tvk.value_、tvk.tt_
    int next;  /* for chaining (offset for next node) */  // 相当于当前Node的下一个Node的索引Offset(在当前Node后面,next值为正;在当前Node前面,next值为负)
  } nk;
  TValue tvk;
} TKey;

typedef struct Node {
  TValue i_val;  // value
  TKey i_key;   // key
} Node;


/*
* WARNING: if you change the order of this enumeration,
* grep "ORDER TM" and "ORDER OP"
*/
typedef enum {
  TM_INDEX,                                           // flags = 00000001
  TM_NEWINDEX,                                         // flags = 00000010
  TM_GC,                                               // flags = 00000100
  TM_MODE,                                          // flags = 00001000
  TM_LEN,                                            // flags = 00010000
  TM_EQ,  /* last tag method with fast access */    // flags = 00100000
  TM_ADD,
  TM_SUB,
  TM_MUL,
  TM_MOD,
  TM_POW,
  TM_DIV,
  TM_IDIV,
  TM_BAND,
  TM_BOR,
  TM_BXOR,
  TM_SHL,
  TM_SHR,
  TM_UNM,
  TM_BNOT,
  TM_LT,
  TM_LE,
  TM_CONCAT,
  TM_CALL,
  TM_N        /* number of elements in the enum */
} TMS;

#define gfasttm(g,et,e) ((et) == NULL ? NULL : \
  ((et)->flags & (1u<<(e))) ? NULL : luaT_gettm(et, e, (g)->tmname[e]))  // 先使用flags来判断,若对应的位1,就立即返回NULL

#define fasttm(l,et,e)    gfasttm(G(l), et, e)


typedef struct Table {
  CommonHeader;  // GC类型
  lu_byte flags;  /* 1<<p means tagmethod(p) is not present */ // 用来快速判断小于等于TM_EQ的tag methods是否存在:1表示不存在,0表示不确定
  lu_byte lsizenode;  /* log2 of size of 'node' array */  // 等于哈希表大小取log2(哈希表大小为2的次幂)
  unsigned int sizearray;  /* size of 'array' array */  // 数组大小(数组大小只会为2的次幂)
  TValue *array;  /* array part */  // 数组头指针(一片连续内存)
  Node *node;  // 哈希表头指针(一片连续内存)
  Node *lastfree;  /* any free position is before this position */ // 哈希表可用尾指针,可用的节点只会小于该lastfree节点
  struct Table *metatable;  // 元表
  GCObject *gclist; // 与gc有关,将table加入到gray表中时gclist指向gray表中的下一个元素或者为空
} Table;

 

array和node指向的是两个连续空间的一维数组,array是普通的数组,成员为Tvalue,node是一个hash表存放key value键值对。

node的key是Tkey类型,Tkey是一个联合,当没有hash冲突时Tkey是一个Tvalue,当有hash冲突时Tkey是一个struct多一个next值,指向下一个有冲突的节点,假设mp指向当前元素,则下一个元素为mp + next。

 

创建表

Table *luaH_new (lua_State *L) {
  GCObject *o = luaC_newobj(L, LUA_TTABLE, sizeof(Table));
  Table *t = gco2t(o);
  t->metatable = NULL;
  t->flags = cast_byte(~0); // 255 二进制为:11111111
  t->array = NULL;
  t->sizearray = 0;
  setnodevector(L, t, 0);
  return t;
}

 

表长度

luaH_getn函数来获取表长度

/*
** Try to find a boundary in table 't'. A 'boundary' is an integer index
** such that t[i] is non-nil and t[i+1] is nil (and 0 if t[1] is nil).
*/
int luaH_getn (Table *t) {
  unsigned int j = t->sizearray;
  if (j > 0 && ttisnil(&t->array[j - 1])) { // 如果sizearray>0,且数组最后一个元素为nil
    /* there is a boundary in the array part: (binary) search for it */
    unsigned int i = 0;
    //二分查找
    while (j - i > 1) {
      unsigned int m = (i+j)/2;
      if (ttisnil(&t->array[m - 1])) j = m;  // 二分时踩到的元素为nil,则将尾部索引j向前移动到m位置
      else i = m; // 否则将头部索引i向后移动到m位置
    }
    return i;
  }
  /* else must find a boundary in hash part */
  else if (isdummy(t))  /* hash part is empty? */ // node hash表是否为空
    return j;  /* that is easy... */  // 为空表明为数组,直接返回j
  else return unbound_search(t, j);  // 为非纯数组时(即:里面含有hash node),就使用unbound_search来计算数组Length
}

从源码上看,table中如果有nil,会导致获取表的长度是不准确的,下面是lua5.3.4下的一些测试

local tb1 = {0, 1, 2, nil, 4, 5, nil} -- 长度#tb1为6
local tb2 = {0, 1, nil, 3, 4, nil}  -- 长度#tb2为2
local tb3 = {nil, 1, nil, 3, 4, nil}  -- 长度#tb3为0
local tb4 = {0, 1, 2, nil, 4, 5} -- 长度#tb4为6
local tb5 = {0, 1, nil, 3, 4}  -- 长度#tb5为5
local tb6 = {nil, 1, nil, 3, 4}  -- 长度#tb6为5

local tb7 = {key1="hello"} -- 长度#tb7为0
local tb8 = {key1="hello", nil} -- 长度#tb8为0
local tb9 = {key1="hello", key2="world"} -- 长度#tb9为0
local tb10 = {key1="hello", 1, nil} -- 长度#tb10为1
local tb11 = {key1="hello", 1, 2} -- 长度#tb11为2
local tb12 = {nil, key1="hello", 1, 2} -- 长度#tb12为3
local tb13 = {key1="hello", 1, nil, 2} -- 长度#tb13为3
local tb14 = {key1="hello", 1, 2, 3, nil} -- 长度#tb14为3
local tb15 = {key1="hello", 1, nil, 2, nil} -- 长度#tb15为1

因此,在table中不要有nil,如果一个元素要删除,直接 remove掉,不要用nil去代替

 

查询

luaH_get函数传入key来查询value,若没有查询到,则返回nil。

如果key是int类型并且小于sizearray,那么直接返回数组对应slot(luaH_getint函数中),否则走hash表查询该key对应的slot。

/*
** main search function
*/
const TValue *luaH_get (Table *t, const TValue *key) {
  // 判断key的类型
  switch (ttype(key)) {
    case LUA_TSHRSTR: return luaH_getshortstr(t, tsvalue(key));// 短串
    case LUA_TNUMINT: return luaH_getint(t, ivalue(key)); // lua_Integer整型
    case LUA_TNIL: return luaO_nilobject;
    case LUA_TNUMFLT: { // lua_Number浮点型
      lua_Integer k;
      if (luaV_tointeger(key, &k, 0)) /* index is int? */ // 将浮点型key转成整型的k
        return luaH_getint(t, k);  /* use specialized version */ // 通过k来取值
      /* else... */
    }  /* FALLTHROUGH */
    default:
      return getgeneric(t, key); // 通用取值函数(效率较低)
  }
}


/*
** search function for short strings
*/
const TValue *luaH_getshortstr (Table *t, TString *key) {
  Node *n = hashstr(t, key);  // 使用key->hash & (2^t->lsizenode - 1)来获取node hash表中索引
  lua_assert(key->tt == LUA_TSHRSTR);
  for (;;) {  /* check whether 'key' is somewhere in the chain */
    const TValue *k = gkey(n);
    if (ttisshrstring(k) && eqshrstr(tsvalue(k), key))// Node n的键为字符串,且与key内容相同
      return gval(n);  /* that's it */  // 返回Node n的值
    else {
      int nx = gnext(n); // 获取Node n的next
      if (nx == 0)
        return luaO_nilobject;  /* not found */
      n += nx; // 获取下一个Node在node hash表中索引值n
    }
  }
}

/*
** search function for integers
*/
const TValue *luaH_getint (Table *t, lua_Integer key) {
  /* (1 <= key && key <= t->sizearray) */
  if (l_castS2U(key) - 1 < t->sizearray) // key值小于t->sizearray,直接从数组中获取值
    return &t->array[key - 1];
  else {
    Node *n = hashint(t, key); // 使用key & (2^t->lsizenode - 1)来获取node hash表中索引
    for (;;) {  /* check whether 'key' is somewhere in the chain */
      if (ttisinteger(gkey(n)) && ivalue(gkey(n)) == key) // Node n的键为整型,且与key数值相等
        return gval(n);  /* that's it */ // 返回Node n的值
      else {
        int nx = gnext(n);  // 获取Node n的next
        if (nx == 0) break;
        n += nx;  // 获取下一个Node在node hash表中索引值n
      }
    }
    return luaO_nilobject;
  }
}

/*
** "Generic" get version. (Not that generic: not valid for integers,
** which may be in array part, nor for floats with integral values.)
*/
static const TValue *getgeneric (Table *t, const TValue *key) {
  Node *n = mainposition(t, key); // 使用mainposition函数来获取key在node hash表中索引
  for (;;) {  /* check whether 'key' is somewhere in the chain */
    if (luaV_rawequalobj(gkey(n), key)) // 判断Node n的键是否与key相等
      return gval(n);  /* that's it */  // 返回Node n的值
    else {
      int nx = gnext(n);  // 获取Node n的next
      if (nx == 0)
        return luaO_nilobject;  /* not found */
      n += nx;   // 获取下一个Node在node hash表中索引值n
    }
  }
}

/*
** returns the 'main' position of an element in a table (that is, the index
** of its hash value)
*/
static Node *mainposition (const Table *t, const TValue *key) {
  switch (ttype(key)) {
    case LUA_TNUMINT:
      return hashint(t, ivalue(key)); // 使用整型key & (2^t->lsizenode - 1)来获取node hash表中索引
    case LUA_TNUMFLT:
      return hashmod(t, l_hashfloat(fltvalue(key))); // ( l_hashfloat(浮点型key) ) % ( (2^t->lsizenode - 1) | 0x00000001 )来获取node hash表中索引
    case LUA_TSHRSTR:
      return hashstr(t, tsvalue(key)); // 使用key->hash & (2^t->lsizenode - 1)来获取node hash表中索引
    case LUA_TLNGSTR:
      return hashpow2(t, luaS_hashlongstr(tsvalue(key))); // 如果长串没有计算过hash,则调用luaS_hashlongstr来计算,然后再使用hash & (2^t->lsizenode - 1)来获取node hash表中索引
    case LUA_TBOOLEAN:
      return hashboolean(t, bvalue(key));  // 使用整型key & (2^t->lsizenode - 1)来获取node hash表中索引
    case LUA_TLIGHTUSERDATA:
      return hashpointer(t, pvalue(key)); // 使用指针(key & 0xffffffff) % ( (2^t->lsizenode - 1) | 0x00000001 )来获取node hash表中索引
    case LUA_TLCF:
      return hashpointer(t, fvalue(key)); // 使用指针(key & 0xffffffff) % ( (2^t->lsizenode - 1) | 0x00000001 )来获取node hash表中索引
    default:
      lua_assert(!ttisdeadkey(key));
      return hashpointer(t, gcvalue(key));
  }
}

#define luaV_rawequalobj(t1,t2)        luaV_equalobj(NULL,t1,t2)

// 判断t1和t2数是否相等
/*
** Main operation for equality of Lua values; return 't1 == t2'.
** L == NULL means raw equality (no metamethods)
*/
int luaV_equalobj (lua_State *L, const TValue *t1, const TValue *t2) {
  const TValue *tm;
  if (ttype(t1) != ttype(t2)) {  /* not the same variant? */ // 类型不一样
    if (ttnov(t1) != ttnov(t2) || ttnov(t1) != LUA_TNUMBER) // t1 t2不是整型、浮点
      return 0;  /* only numbers can be equal with different variants */
    else {  /* two numbers with different variants */
      lua_Integer i1, i2;  /* compare them as integers */
      return (tointeger(t1, &i1) && tointeger(t2, &i2) && i1 == i2); // 如果是浮点转成整型,再进行比较
    }
  }
  /* values have same type and same variant */
  switch (ttype(t1)) {
    case LUA_TNIL: return 1; // 同为空类型,则相等
    case LUA_TNUMINT: return (ivalue(t1) == ivalue(t2)); // 同为整型,比较数值
    case LUA_TNUMFLT: return luai_numeq(fltvalue(t1), fltvalue(t2)); // 同为浮点型,比较数值
    case LUA_TBOOLEAN: return bvalue(t1) == bvalue(t2);  /* true must be 1 !! */  // 同为bool型(true:1  false:0),比较数值
    case LUA_TLIGHTUSERDATA: return pvalue(t1) == pvalue(t2); // 同为指针,比较数值
    case LUA_TLCF: return fvalue(t1) == fvalue(t2); // 同为函数指针,比较数值
    case LUA_TSHRSTR: return eqshrstr(tsvalue(t1), tsvalue(t2));// 同为短串,比较指针
    case LUA_TLNGSTR: return luaS_eqlngstr(tsvalue(t1), tsvalue(t2));// 同为长串,逐内容比较
    case LUA_TUSERDATA: {
      if (uvalue(t1) == uvalue(t2)) return 1;
      else if (L == NULL) return 0;
      tm = fasttm(L, uvalue(t1)->metatable, TM_EQ);
      if (tm == NULL)
        tm = fasttm(L, uvalue(t2)->metatable, TM_EQ);
      break;  /* will try TM */
    }
    case LUA_TTABLE: {
      if (hvalue(t1) == hvalue(t2)) return 1;
      else if (L == NULL) return 0;
      tm = fasttm(L, hvalue(t1)->metatable, TM_EQ);
      if (tm == NULL)
        tm = fasttm(L, hvalue(t2)->metatable, TM_EQ);
      break;  /* will try TM */
    }
    default:
      return gcvalue(t1) == gcvalue(t2);
  }
  if (tm == NULL)  /* no TM? */
    return 0;  /* objects are different */
  luaT_callTM(L, tm, t1, t2, L->top, 1);  /* call TM */  // 通过TM_EQ的tag methods来判断相等性
  return !l_isfalse(L->top);// 获取luaT_callTM返回值
}

 

新增

新增元素核心是通过luaH_newkey函数来新增key

/*
** beware: when using this function you probably need to check a GC
** barrier and invalidate the TM cache.
*/
TValue *luaH_set (lua_State *L, Table *t, const TValue *key) {
  const TValue *p = luaH_get(t, key); // 获取key对应value值
  if (p != luaO_nilobject) // value值不为空
    return cast(TValue *, p); // 返回该value值
  else return luaH_newkey(L, t, key);  // 否则新建一个key,并返回value值
}

/*
** inserts a new key into a hash table; first, check whether key's main
** position is free. If not, check whether colliding node is in its main
** position or not: if it is not, move colliding node to an empty place and
** put new key in its main position; otherwise (colliding node is in its main
** position), new key goes to an empty position.
*/
TValue *luaH_newkey (lua_State *L, Table *t, const TValue *key) {
  Node *mp;
  TValue aux;
  if (ttisnil(key)) luaG_runerror(L, "table index is nil"); // key为nil,直接报错
  else if (ttisfloat(key)) { // key为浮点数
    lua_Integer k;
    if (luaV_tointeger(key, &k, 0)) {  /* does index fit in an integer? */  // 将浮点数key转成整型
      setivalue(&aux, k);
      key = &aux;  /* insert it as an integer */
    }
    else if (luai_numisnan(fltvalue(key))) // 浮点数为NaN,直接报错
      luaG_runerror(L, "table index is NaN");
  }
  mp = mainposition(t, key); // 使用mainposition函数来获取key在node hash表中索引
  if (!ttisnil(gval(mp)) || isdummy(t)) {  /* main position is taken? */ // mp节点的value有值,或者node hash表为空,需要创建新的节点
    Node *othern;
    Node *f = getfreepos(t);  /* get a free place */  // 返回空闲Node
    if (f == NULL) {  /* cannot find a free place? */  // node hash表没有空闲node
      rehash(L, t, key);  /* grow table */  // 扩容表空间
      /* whatever called 'newkey' takes care of TM cache */
      return luaH_set(L, t, key);  /* insert key into grown table */  // 将key插入到扩张后的表中
    }
    lua_assert(!isdummy(t));
    othern = mainposition(t, gkey(mp)); // 获取占用节点在node hash表中索引
    if (othern != mp) {  /* is colliding node out of its main position? */ // 如果占用节点的索引与插入key的不同,说明该节点是被“挤”到该位置来的,那么把该节点挪到freepos去,然后让newkey入住其mainposition
      /* yes; move colliding node into free position */
      while (othern + gnext(othern) != mp)  /* find previous */
        othern += gnext(othern);
      gnext(othern) = cast_int(f - othern);  /* rechain to point to 'f' */
      *f = *mp;  /* copy colliding node into free pos. (mp->next also goes) */
      if (gnext(mp) != 0) {
        gnext(f) += cast_int(mp - f);  /* correct 'next' */
        gnext(mp) = 0;  /* now 'mp' is free */
      }
      setnilvalue(gval(mp));
    }
    else {  /* colliding node is in its own main position */  // 占用的节点和newkey的哈希值相同,那么直接插入到该mainposition的next 即:从mainposition链表头部插入newkey
      /* new node will go into free position */
      if (gnext(mp) != 0)
        gnext(f) = cast_int((mp + gnext(mp)) - f);  /* chain new position */
      else lua_assert(gnext(f) == 0);
      gnext(mp) = cast_int(f - mp);
      mp = f;
    }
  }
  setnodekey(L, &mp->i_key, key); // 将key赋值给mp->i_key
  luaC_barrierback(L, t, key); // 如果表t是black(垃圾),这将其标色为gray,防止被gc掉
  lua_assert(ttisnil(gval(mp)));
  return gval(mp); // 返回mp的value值
}


static Node *getfreepos (Table *t) {
  if (!isdummy(t)) { // node hash表不为空
    while (t->lastfree > t->node) { // 哈希表可用尾指针大于首指针
      t->lastfree--; // 可用尾指针向前移动
      if (ttisnil(gkey(t->lastfree))) // 判断是否被使用
        return t->lastfree; // 没有被使用,则返回该指针
    }
  }
  return NULL;  /* could not find a free place */
}

 

扩展大小

当在node hash表中找不到空闲节点时,就会调用rehash函数来扩展数组和扩展node hash表的大小。

/*
** nums[i] = number of keys 'k' where 2^(i - 1) < k <= 2^i
*/
static void rehash (lua_State *L, Table *t, const TValue *ek) {
  unsigned int asize;  /* optimal size for array part */ // 最终数组的大小(一定为2的次幂)
  unsigned int na;  /* number of keys in the array part */ // 最终归入数组部分的key的个数
  unsigned int nums[MAXABITS + 1]; // MAXABITS=31  它的第i个位置存储的是key在2^(i-1)~2^i区间内的数量
  int i;
  int totaluse; // 总共的key个数
  for (i = 0; i <= MAXABITS; i++) nums[i] = 0;  /* reset counts */
  na = numusearray(t, nums);  /* count keys in array part */ // 遍历当前表的array部分,按其中key的分布来更新nums数组
  totaluse = na;  /* all those keys are integer keys */
  totaluse += numusehash(t, nums, &na);  /* count keys in hash part */ // 遍历当前的hash表部分,如果其中的key为整数,na++并且更新nums数组,对于每个遍历的元素,totaluse++
  /* count extra key */
  na += countint(ek, nums); // 将ek是整型的,更新nums数组,并返回1
  totaluse++;
  // 计算optimal的array部分大小。这个函数根据整型key在2^(i-1)~2^i之间的填充率,来决定最终的array大小。
  // 一旦遇到某个子区间的填充率小于1/2,那么后续的整型key都存储到hash表中去,这一步是为了防止数组过于稀疏而浪费内存
  /* compute new size for array part */
  asize = computesizes(nums, &na);
  // 根据上一步计算出的最终数组和哈希表大小,进行resize操作。如果哈希表的尺寸有变化,会对原来哈希表中的元素进行真正的rehash
  /* resize the table to new computed sizes */
  luaH_resize(L, t, asize, totaluse - na);
}

 

迭代器

在使用测主要是ipairs和pairs两个函数。这两个函数都会在vm内部临时创建出两个变量state和index,用于对lua表进行迭代访问,每次访问的时候,会调用luaH_next函数

int luaH_next (lua_State *L, Table *t, StkId key) {
  // 返回key在表中的索引i
  unsigned int i = findindex(L, t, key);  /* find original element */

  // i < t->sizearray,表明key存放在数组中
  for (; i < t->sizearray; i++) {  /* try first array part */
    if (!ttisnil(&t->array[i])) {  /* a non-nil value? */
      setivalue(key, i + 1);
      setobj2s(L, key+1, &t->array[i]);
      return 1;
    }
  }
  // 否则key在node hash表中
  for (i -= t->sizearray; cast_int(i) < sizenode(t); i++) {  /* hash part */
    if (!ttisnil(gval(gnode(t, i)))) {  /* a non-nil value? */
      setobj2s(L, key, gkey(gnode(t, i)));
      setobj2s(L, key+1, gval(gnode(t, i)));
      return 1;
    }
  }
  return 0;  /* no more elements */
}

 

userdata(用户数据)

Lua和C交互所使用的的自定义数据分为full userdata和light userdata两个子类,这两者的根本区别在于内存生命周期的管理者不同。

full userdata的内存在Lua栈上分配。用户使用userdata时,通过调用lua_newuserdata(lua_State* L, size_t nBytes)分配指定大小的内存块,类似于malloc,但不需要自行调用free(),该内存由Lua的gc机制进行回收。

full userdata可认为是lua中不需要理解/解析的可存储任意数据的原始内存区域。在lua中仅需获取userdata,并调用其函数接口;c/c++则负责理解userdata,并实现其函数接口功能。

light userdata只是通过lua_pushlightuserdata(ua_state* L, void* p)将C对象指针交给Lua的对象持有,light userdata所使用的内存的分配和回收,需要用户自行管理,Lua并不会帮忙回收。

尤其需要注意的是,Lua中light userdata的对象生命周期与绑定C对象的生命周期息息相关,因此C对象释放时,Lua中的light userdata的释放也需要用户关心处理,否则会出现野指针问题。

 

Udata结构体与UUdata Union

/*
** Header for userdata; memory area follows the end of this structure
** (aligned according to 'UUdata'; see next).
*/
typedef struct Udata {
  CommonHeader;
  lu_byte ttuv_;  /* user value's tag */  // Udata存放的类型
  struct Table *metatable; // userdata的元表,和table的元表一样的
  size_t len;  /* number of bytes */ // 使用userdata的时候绑定对象申请的空间大小
  union Value user_;  /* user value */  // Udata存放的value
} Udata;


// 为了实现Udata结构的内存对齐,lua又在其上wrap了一层UUdata结构
/*
** Ensures that address after this type is always fully aligned.
*/
typedef union UUdata {
  // sizeof(L_Umaxalign)为8,保证UUdata对象本身会按照8字节进行对齐
  L_Umaxalign dummy;  /* ensures maximum alignment for 'local' udata */
  Udata uv;
} UUdata;

使用luaS_newudata创建full userdata

Udata *luaS_newudata (lua_State *L, size_t s) {
  Udata *u;
  GCObject *o;
  if (s > MAX_SIZE - sizeof(Udata))
    luaM_toobig(L);
  o = luaC_newobj(L, LUA_TUSERDATA, sizeludata(s));
  u = gco2u(o);
  u->len = s;
  u->metatable = NULL;
  setuservalue(L, u, luaO_nilobject);
  return u;
}

 

内存结构如下:

注:将value_.gc指针强制转换为Udata*类型,即可读取Udata中数据

 

void *(lua_touserdata) (lua_State *L, int idx)函数可以返回索引为idx处的userdata指针

当为light userdata时,返回的是value_.p

当为full userdata时,返回的是full userdata内存块的首地址:value_.gc + sizeof(Udata)

 

thread(线程)

从Lua的使用者的角度看,global_State是不可见的。我们无法用公开的API取到它的指针,也不需要引用它。但分析Lua的实现就不能绕开这个部分。
global_State里面有对主线程的引用,有注册表管理所有全局数据,有全局字符串表,有内存管理函数,
有GC需要的把所有对象串联起来的相关信息,以及一切Lua在工作时需要的工作内存。

typedef struct global_State {
  lua_Alloc frealloc;  /* function to reallocate memory */
  void *ud;         /* auxiliary data to 'frealloc' */
  l_mem totalbytes;  /* number of bytes currently allocated - GCdebt */
  l_mem GCdebt;  /* bytes allocated not yet compensated by the collector */
  lu_mem GCmemtrav;  /* memory traversed by the GC */
  lu_mem GCestimate;  /* an estimate of the non-garbage memory in use */
  stringtable strt;  /* hash table for strings */
  TValue l_registry;
  unsigned int seed;  /* randomized seed for hashes */
  lu_byte currentwhite;
  lu_byte gcstate;  /* state of garbage collector */
  lu_byte gckind;  /* kind of GC running */
  lu_byte gcrunning;  /* true if GC is running */
  GCObject *allgc;  /* list of all collectable objects */
  GCObject **sweepgc;  /* current position of sweep in list */
  GCObject *finobj;  /* list of collectable objects with finalizers */
  GCObject *gray;  /* list of gray objects */
  GCObject *grayagain;  /* list of objects to be traversed atomically */
  GCObject *weak;  /* list of tables with weak values */
  GCObject *ephemeron;  /* list of ephemeron tables (weak keys) */
  GCObject *allweak;  /* list of all-weak tables */
  GCObject *tobefnz;  /* list of userdata to be GC */
  GCObject *fixedgc;  /* list of objects not to be collected */
  struct lua_State *twups;  /* list of threads with open upvalues */
  unsigned int gcfinnum;  /* number of finalizers to call in each GC step */
  int gcpause;  /* size of pause between successive GCs */
  int gcstepmul;  /* GC 'granularity' */
  lua_CFunction panic;  /* to be called in unprotected errors */
  struct lua_State *mainthread;
  const lua_Number *version;  /* pointer to version number */
  TString *memerrmsg;  /* memory-error message */
  TString *tmname[TM_N];  /* array with tag-method names */
  struct Table *mt[LUA_NUMTAGS];  /* metatables for basic types */
  TString *strcache[STRCACHE_N][STRCACHE_M];  /* cache for strings in API */
} global_State;


/*
** 'per thread' state
*/
struct lua_State {
  CommonHeader;
  unsigned short nci;  /* number of items in 'ci' list */
  lu_byte status;
  StkId top;  /* first free slot in the stack */
  global_State *l_G;
  CallInfo *ci;  /* call info for current function */
  const Instruction *oldpc;  /* last pc traced */
  StkId stack_last;  /* last free slot in the stack */
  StkId stack;  /* stack base */
  UpVal *openupval;  /* list of open upvalues in this stack */
  GCObject *gclist;
  struct lua_State *twups;  /* list of threads with open upvalues */
  struct lua_longjmp *errorJmp;  /* current error recover point */
  CallInfo base_ci;  /* CallInfo for first level (C calling Lua) */
  volatile lua_Hook hook;
  ptrdiff_t errfunc;  /* current error handling function (stack index) */
  int stacksize;
  int basehookcount;
  int hookcount;
  unsigned short nny;  /* number of non-yieldable calls in stack */
  unsigned short nCcalls;  /* number of nested C calls */
  l_signalT hookmask;
  lu_byte allowhook;
};

 

创建一个lua_State

lua_newstate中初始化了主线程的数据栈、初始化注册表、给出一个基本的字符串池、初始化元表用的字符串、初始化词法分析用的token串、初始化内存错误信息。

Lua_State是暴露给用户的数据类型。从名字上看,它想表示一个Lua程序的执行状态,在官方文档中,它指代Lua的一个线程。

每个线程拥有独立的数据栈以及函数调用链,还有独立的调试钩子和错误处理设施。所以我们不应当简单的把Lua_State看成一个静态的数据集,它是一组Lua程序的执行状态机。

所有的Lua C API都是围绕这个状态机,改变其状态的:或把数据压入堆栈,或取出,或执行栈顶的函数,或继续上次被中断的执行过程。

同一Lua虚拟机中的所有执行线程,共享了一块全局数据global_State。

LUA_API lua_State *lua_newstate (lua_Alloc f, void *ud) {
  int i;
  lua_State *L;
  global_State *g;
  LG *l = cast(LG *, (*f)(ud, NULL, LUA_TTHREAD, sizeof(LG)));
  if (l == NULL) return NULL;
  L = &l->l.l;
  g = &l->g;
  L->next = NULL;
  L->tt = LUA_TTHREAD;
  g->currentwhite = bitmask(WHITE0BIT);
  L->marked = luaC_white(g);
  preinit_thread(L, g);
  g->frealloc = f;
  g->ud = ud;
  g->mainthread = L;
  g->seed = makeseed(L);
  g->gcrunning = 0;  /* no GC while building state */
  g->GCestimate = 0;
  g->strt.size = g->strt.nuse = 0;
  g->strt.hash = NULL;
  setnilvalue(&g->l_registry);
  g->panic = NULL;
  g->version = NULL;
  g->gcstate = GCSpause;
  g->gckind = KGC_NORMAL;
  g->allgc = g->finobj = g->tobefnz = g->fixedgc = NULL;
  g->sweepgc = NULL;
  g->gray = g->grayagain = NULL;
  g->weak = g->ephemeron = g->allweak = NULL;
  g->twups = NULL;
  g->totalbytes = sizeof(LG);
  g->GCdebt = 0;
  g->gcfinnum = 0;
  g->gcpause = LUAI_GCPAUSE;
  g->gcstepmul = LUAI_GCMUL;
  for (i=0; i < LUA_NUMTAGS; i++) g->mt[i] = NULL;
  if (luaD_rawrunprotected(L, f_luaopen, NULL) != LUA_OK) {
    /* memory allocation error: free partial state */
    close_state(L);
    L = NULL;
  }
  return L;
}

 

把数据栈和调用栈合起来就构成了Lua中的线程。在同一个Lua虚拟机中的不同线程因为共享了global_State而很难做到真正意义上的并发。

它也绝非操作系统意义上的线程,但在行为上很相似。用户可以resume一个线程,线程可以被yield打断。Lua的执行过程就是围绕线程进行的。

 

luaL_newstate中会用一个默认分配函数(c的malloc-realloc-free)来分配内存。如果要完全控制lua的内存分配,也可以为lua_newstate来指定内存分配函数。

如Unlua就为lua state指定了内存分配函数:

void* FLuaContext::LuaAllocator(void *ud, void *ptr, size_t osize, size_t nsize)
{
    if (nsize == 0)
    {
        FMemory::Free(ptr);
        return nullptr;
    }

    void *Buffer = nullptr;
    if (!ptr)
    {
        Buffer = FMemory::Malloc(nsize);
    }
    else
    {
        Buffer = FMemory::Realloc(ptr, nsize);
    }
    return Buffer;
}


lua_State *L = lua_newstate(FLuaContext::LuaAllocator, this);  // this为FLuaContext指针

 

创建一个thread

LUA_API lua_State *lua_newthread (lua_State *L) {
  global_State *g = G(L);
  lua_State *L1;
  lua_lock(L);
  luaC_checkGC(L);
  /* create new thread */
  L1 = &cast(LX *, luaM_newobject(L, LUA_TTHREAD, sizeof(LX)))->l;
  L1->marked = luaC_white(g);
  L1->tt = LUA_TTHREAD;
  /* link it on list 'allgc' */
  L1->next = g->allgc;
  g->allgc = obj2gco(L1);
  /* anchor it on L stack */
  setthvalue(L, L->top, L1);
  api_incr_top(L);
  preinit_thread(L1, g);
  L1->hookmask = L->hookmask;
  L1->basehookcount = L->basehookcount;
  L1->hook = L->hook;
  resethookcount(L1);
  /* initialize L1 extra space */
  memcpy(lua_getextraspace(L1), lua_getextraspace(g->mainthread),
         LUA_EXTRASPACE);
  luai_userstatethread(L, L1);
  stack_init(L1, L);  /* init stack */
  lua_unlock(L);
  return L1;
} 

 

参考

Lua设计与实现--数据类型篇

Lua设计与实现--字符串篇

Lua设计与实现--函数篇

Lua设计与实现--Table篇

[lua source code] object system

温故而知新

lua字符串

lua5.3.4源码 

 

posted on 2020-06-30 23:08  可可西  阅读(2056)  评论(0编辑  收藏  举报

导航