本文分析主要是基于Lua5.3.1版本。
lua的源码比较简洁,看起来比较费劲。
先要搞懂一个lua脚本如何被编译成为字节码,然后被放到Lua虚拟机里面执行,整个过程很复杂。首先需要先搞懂lua中如何定义数据。
1. LUA的类型抽象
lua中的类型叫做Value。
Value的定义如下:
/*
** Union of all Lua values
*/
typedef union Value {
struct GCObject *gc; /* collectable objects */
void *p; /* light userdata */
lua_CFunction f; /* light C functions */
lua_Integer i; /* integer numbers */
lua_Number n; /* float numbers */
} Value;
复制代码
可以看出:
Value其实是一个Union类型。
也就是说一个Value,要不是一个GCObject
,要不是一个light userdata
/ light C functions
/integer numbers
/float numbers
。
然后再把这个Union给封装一层, 加上一个type标识这个value是什么类型,这个新的类型就是TValue.
/*
** Tagged Values. This is the basic representation of values in Lua:
** an actual value plus a tag with its type.
*/
#define TValuefields Value value_; lu_byte tt_
typedef struct TValue {
TValuefields;
} TValue;
复制代码
用图来解释下如下:
对于
void *p; /* light userdata */
lua_CFunction f; /* light C functions */
lua_Integer i; /* integer numbers */
lua_Number n; /* float numbers */
复制代码
这几种类型从注释上看,还是比较好理解的,但是对于GCObject
,这个就是lua的一种抽象类型了,代表这种类型的对象是可以被垃圾回收的。
2. lua可垃圾回收对象类型
从上面的图可以看出,TValue类型中有一种可被垃圾回收的对象类型—GCObject
.
2.1 GCObject对象定义
lua本身是一个动态语言,所有的对象其实都是分配在堆内存上的,通过一个GCList链表进行管理。所以Lua在创建对象的时候都是使用GCObject类型做抽象,也就是创建的所有对象其实都是一种GCObject对象。
通过名字不难看出,GCObject就是一种可以被垃圾回收的Object。简单的理解就是在Lua VM中,它们都是被垃圾回收链表管理的对象。
先看下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
/*
** Common type has only the common header
*/
struct GCObject {
CommonHeader;
};
复制代码
可以看出GCObject都有一个CommnHeader,其中有3个成员:
- next指针, 证明lua中所有的变量都是使用一个链表串起来的;
- tt 当前这个Object类型
- marked 是标记这个用户的回收状态,后续再分析LUA的垃圾回收的时候会介绍到;
那么LUA内部,支持哪些可以垃圾回收的类型呢?
从代码分析,可以看出一共是5种内部类型,分别如下:
- TString
- Udata
- Proto
- Closure
- Table
如下是几种数据类型的定义说明.
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;
复制代码
Udata定义:
/*
** 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 */
struct Table *metatable;
size_t len; /* number of bytes */
union Value user_; /* user value */
} Udata;
复制代码
Proto定义:
/*
** Function Prototypes
*/
typedef struct Proto {
CommonHeader;
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' */
int sizek; /* size of 'k' */
int sizecode;
int sizelineinfo;
int sizep; /* size of 'p' */
int sizelocvars;
int linedefined;
int lastlinedefined;
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) */
LocVar *locvars; /* information about local variables (debug information) */
Upvaldesc *upvalues; /* upvalue information */
struct LClosure *cache; /* last-created closure with this prototype */
TString *source; /* used for debug information */
GCObject *gclist;
} Proto;
复制代码
Closure分为两种,Lua闭包和C闭包,定义如下:
/*
** Closures
*/
#define ClosureHeader \
CommonHeader; lu_byte nupvalues; GCObject *gclist
typedef struct CClosure {
ClosureHeader;
lua_CFunction f;
TValue upvalue[1]; /* list of upvalues */
} CClosure;
typedef struct LClosure {
ClosureHeader;
struct Proto *p; //lua函数的原型指针
UpVal *upvals[1]; /* list of upvalues */
} LClosure;
复制代码
Table定义:
typedef struct Table {
CommonHeader;
lu_byte flags; /* 1<<p means tagmethod(p) is not present */
lu_byte lsizenode; /* log2 of size of 'node' array */
unsigned int sizearray; /* size of 'array' array */
TValue *array; /* array part */
Node *node;
Node *lastfree; /* any free position is before this position */
struct Table *metatable;
GCObject *gclist;
} Table;
复制代码
从面向对象的角度分析的话,可以认为这5种内部基础类型,都继承自GCObject
。
2.2 Lua中的GCObject对象创建过程
2.2.1 创建一个GCObject对象
创建一个内部对象,使用同一个的接口函数luaC_newobj (lua_State *L, int tt, size_t sz)
参数说明:
- lua_Stata *L 是当前的lua上下文
- tt 需要创建的对象的类型
- sz 需要创建的对象的大小
返回值说明:
- GCObject 创建好的抽象对象的的指针
从入参其实不难理解,这个函数有点像面向对象里面的工厂方法,传入一个需要的对象类型,就返回一个创建好的父类指针,但是这里其实只是做了申请内存的操作。
每个具体的对象都有自己的创建函数,每个创建函数又都会调用luaC_newobj
函数来申请内存。
下面是luaC_newobj
函数代码实现:
/*
** 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); //标记这个对象是white
o->tt = tt; //类型
o->next = g->allgc; //新创建的对象放到gclist的最前面
g->allgc = o;
return o;
}
#define luaM_newobject(L,tag,s) luaM_realloc_(L, NULL, tag, (s))
/*
** generic allocation routine.
*/
void *luaM_realloc_ (lua_State *L, void *block, size_t osize, size_t nsize) {
void *newblock;
global_State *g = G(L);
size_t realosize = (block) ? osize : 0; //新建的话,block都是NULL
lua_assert((realosize == 0) == (block == NULL));
#if defined(HARDMEMTESTS)
if (nsize > realosize && g->gcrunning)
luaC_fullgc(L, 1); /* force a GC whenever possible */
#endif
newblock = (*g->frealloc)(g->ud, block, osize, nsize); //调用的就是l_alloc
if (newblock == NULL && nsize > 0) {
lua_assert(nsize > realosize); /* cannot fail when shrinking a block */
if (g->version) { /* is state fully built? */
luaC_fullgc(L, 1); /* try to free some memory... */
newblock = (*g->frealloc)(g->ud, block, osize, nsize); /* try again */
}
if (newblock == NULL)
luaD_throw(L, LUA_ERRMEM);
}
lua_assert((nsize == 0) == (newblock == NULL));
g->GCdebt = (g->GCdebt + nsize) - realosize;
return newblock;
}
复制代码
上面的函数夹杂了很多GC的处理逻辑。本文对其中垃圾回收的原理不做详细介绍。但是可以很清晰的看到,创建一个对象,最终都会使用 (*g->frealloc)(g->ud, block, osize, nsize);
这个函数。而这个函数其实就是realloc
,申请堆内存。
2.2.2 抽象对象到具体对象的转换
通过统一的接口luaC_newobj
函数,创建的内部对象都是GCObjcet
类型。如果要正常使用,还需要做一次转换,LUA对于这种抽象结构转换为具体对象结构的行为,封装了一套宏函数:
/* macros to convert a GCObject into a specific value */
#define gco2ts(o) \
check_exp(novariant((o)->tt) == LUA_TSTRING, &((cast_u(o))->ts))
#define gco2u(o) check_exp((o)->tt == LUA_TUSERDATA, &((cast_u(o))->u))
#define gco2lcl(o) check_exp((o)->tt == LUA_TLCL, &((cast_u(o))->cl.l))
#define gco2ccl(o) check_exp((o)->tt == LUA_TCCL, &((cast_u(o))->cl.c))
#define gco2cl(o) \
check_exp(novariant((o)->tt) == LUA_TFUNCTION, &((cast_u(o))->cl))
#define gco2t(o) check_exp((o)->tt == LUA_TTABLE, &((cast_u(o))->h))
#define gco2p(o) check_exp((o)->tt == LUA_TPROTO, &((cast_u(o))->p))
#define gco2th(o) check_exp((o)->tt == LUA_TTHREAD, &((cast_u(o))->th))
复制代码
3 总结
一张图总结下: