从源码剖析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 3
// 判断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 source code] object system