[TOC]
这是这个gc系列的第二篇,这一篇主要讲GC用到的数据结构,有助于理解gc的,所以放在前面
1. 栈
栈就是我们平时写代码接触最多的lua_State。在实现上是用一个数组实现的。每个成员类型是TValue,看下文详细介绍。这里不打算详细介绍栈的结构和内容,只会介绍和gc相关的一些内容,主要是帮助我们更好的理解lua的gc。
定义
下面是lua_State的定义,会看到栈的身影,top和base指针。
struct lua_State {
CommonHeader;
lu_byte status;
StkId top; /* first free slot in the stack */
StkId base; /* base of current function */
global_State *l_G;
CallInfo *ci; /* call info for current function */
const Instruction *savedpc; /* `savedpc' of current function */
StkId stack_last; /* last free slot in the stack */
StkId stack; /* stack base */
CallInfo *end_ci; /* points after end of ci array*/
CallInfo *base_ci; /* array of CallInfo's */
int stacksize;
int size_ci; /* size of array `base_ci' */
unsigned short nCcalls; /* number of nested C calls */
lu_byte hookmask;
lu_byte allowhook;
int basehookcount;
int hookcount;
lua_Hook hook;
TValue l_gt; /* table of globals */
TValue env; /* temporary place for environments */
GCObject *openupval; /* list of open upvalues in this stack */
GCObject *gclist;
struct lua_longjmp *errorJmp; /* current error recover point */
ptrdiff_t errfunc; /* current error handling function (stack index) */
};
简单的图
至于base/top和stack/stack_last、以及base_ci/end_ci之间的关系和区别就不打算详细介绍了。主要是lua的指令操作的实现,以及函数调用的时候也要用到栈,只是他们在这个数组的不同区间。
2.栈元素TValue
这个类型是给栈用的,前面说过栈其实是一个TValue的数组。
定义
typedef union {
GCObject *gc;
void *p;
lua_Number n;
int b;
} Value;
#define TValuefields Value value; int tt
typedef struct lua_TValue {
TValuefields;
} TValue;
说明
- TValue,是Value加了一个类型
类型定义:
/*
** basic types
*/
#define LUA_TNONE (-1)
#define LUA_TNIL 0
#define LUA_TBOOLEAN 1
#define LUA_TLIGHTUSERDATA 2
#define LUA_TNUMBER 3
#define LUA_TSTRING 4
#define LUA_TTABLE 5
#define LUA_TFUNCTION 6
#define LUA_TUSERDATA 7
#define LUA_TTHREAD 8
- 可以看到存放真正值的是这是一个union结构
- 用过lua的都知道,lua是一种动态类型语言,所有值都是first-class的。所以代码层就是这个Value
- 简单介绍一下union中成员的含义
成员 | 含义 |
---|---|
GCObject *gc | 所有的需要gc的对象都是用的这个成员,所以本系列文章只关注这个成员就好了 |
void *p | 存放lightuserdata |
lua_Number n | 数值类型,这里也可以看出来lua里面用到的整形浮点型都是用这个存储的,就是double类型 |
int b | bool类型 |
看代码:
// bool类型的宏
#define setbvalue(obj,x) \
{ TValue *i_o=(obj); i_o->value.b=(x); i_o->tt=LUA_TBOOLEAN; }
// table类型的宏
#define sethvalue(L,obj,x) \
{ TValue *i_o=(obj); \
i_o->value.gc=cast(GCObject *, (x)); i_o->tt=LUA_TTABLE; \
checkliveness(G(L),i_o); }
3.GC对象
gc对象就是指lua里面需要被回收的对象,类型是在LUA_TSTRING(4)到LUA_TTHREAD(8)之间(准确来说还有扩展的类型)。开始看的时候,难免会有疑问,lua里面的所有对象不都是放在栈里面的吗?这个gc对象是个什么的存在?
定义
union GCObject {
GCheader gch;
union TString ts;
union Udata u;
union Closure cl;
struct Table h;
struct Proto p;
struct UpVal uv;
struct lua_State th; /* thread */
};
// head
#define CommonHeader GCObject *next; lu_byte tt; lu_byte marked
说明
- 这是一个union结构体
- 这里必须提醒注意下这个GCheader,可以看到前面一个GCheader gch的定义,是跟类型无关的。后面在解答上面疑问的时候,一并说明一下。这个CommonHeader实现了一个链表结构(next),也指明了这个对象的类型(tt),以及颜色(marked)。
- 会看到除了前面的基本类型之外,多了几个可以回收的类型
/*
** Extra tags for non-values
*/
#define LUA_TPROTO (LAST_TAG+1)
#define LUA_TUPVAL (LAST_TAG+2)
#define LUA_TDEADKEY (LAST_TAG+3)
4.gc链表
这个链表是记录了lua里面所有的可回收对象,另外注意这是一个单向链表。正因为是单向链表,为了效率,才不会去整个遍历一遍,才会再增加扫描的链表等,这些后面篇章详细介绍。
定义
这个链表的指针是放在global_State中rootgc中的。
/*
** `global state', shared by all threads of this state
*/
typedef struct global_State {
stringtable strt; /* hash table for strings */
lua_Alloc frealloc; /* function to reallocate memory */
void *ud; /* auxiliary data to `frealloc' */
lu_byte currentwhite;
lu_byte gcstate; /* state of garbage collector */
int sweepstrgc; /* position of sweep in `strt' */
GCObject *rootgc; /* list of all collectable objects */
GCObject **sweepgc; /* position of sweep in `rootgc' */
GCObject *gray; /* list of gray objects */
GCObject *grayagain; /* list of objects to be traversed atomically */
GCObject *weak; /* list of weak tables (to be cleared) */
GCObject *tmudata; /* last element of list of userdata to be GC */
Mbuffer buff; /* temporary buffer for string concatentation */
lu_mem GCthreshold;
lu_mem totalbytes; /* number of bytes currently allocated */
lu_mem estimate; /* an estimate of number of bytes actually in use */
lu_mem gcdept; /* how much GC is `behind schedule' */
int gcpause; /* size of pause between successive GCs */
int gcstepmul; /* GC `granularity' */
lua_CFunction panic; /* to be called in unprotected errors */
TValue l_registry;
struct lua_State *mainthread;
UpVal uvhead; /* head of double-linked list of all open upvalues */
struct Table *mt[NUM_TAGS]; /* metatables for basic types */
TString *tmname[TM_N]; /* array with tag-method names */
} global_State;
这个结构放了所有关于gc的内容(对着后面的注释看一下):
- currentwhite:这个就是第一篇中提到的gc流程中的当前白色,如果清理阶段某个对象是otherwhite,那么他就会被清理掉
- gcstate:控制gc流程的,后面流程中说的状态就是记录在这里
- rootgc:前面刚提到过,所有可回收的gc对象单向链表
- gray:为了gc的效率增加的一个gc链表
- grayagain:为了实现增量式gc,过程中处理中断问题的一个链表
- GCthreshold,totalbytes,estimate,gcdept,gcpause:这几个单次gc相关的控制或者状态量,直接关系到lua提供的接口collectgarbage
- 另外一些是全局的一些变量的定义,metatable等。这些跟gc扫描不会遍历整个gc链表有关系。
5.栈和gc链表的关系
栈没有细说,但是他和gc链表的关系必须详细说明一下。如下图所示
答疑解惑
就着问题,说一下栈和gc链表之间的关系。
- 1.GCObject的存在
这里就需要了解gc链表和栈中元素的关系。lua的栈是一个数组,里面真正存放了lua里面的所有对象。gc链表存放了lua所有的可回收对象,而事实上gc链表存放的只是所有可回收对象的指针,真正的对象还是以TValue(GCObject* gc成员)的形式放在lua栈中的。当然,对象真正的内容是在堆上(需要自己回收)。而栈和gc链表中存在的只是真实对象的指针,不同类型的结构不一样,所以以这种方式才能存在一起管理
- 2.怎么做到的?
前面提到了CommonHead,它在栈和gc链表关系中起了关键的作用。有相同的头部,所以可以通过强制转换在TValue和GCObject直接为了当时需要进行切换.看源码更清晰了,能够转换为GCObject的结构体都是必须包含这个头部的,需要GC的结构都要添加这个头部,如下所所示:
//
struct lua_State {
CommonHeader;
//
typedef struct Table {
CommonHeader;
//
typedef struct UpVal {
CommonHeader;
//
typedef struct Proto {
CommonHeader;
//
typedef union Udata {
L_Umaxalign dummy; /* ensures maximum alignment for `local' udata */
struct {
CommonHeader;
typedef union TString {
L_Umaxalign dummy; /* ensures maximum alignment for strings */
struct {
CommonHeader;
总结
- 这里并没有把所有的结构体都解释一遍,userdata,upvalue相关的都是比较细节的东西,可以单独看相关的内容,在全局理解的情况下根据自身特性去看也会很容易明白