lua原始碼分析-gc篇(二)資料結構
定義
簡單的圖
2.棧元素TValue
定義
說明
3.GC物件
定義
說明
4.gc連結串列
定義
5.棧和gc連結串列的關係
答疑解惑
總結
這是這個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相關的都是比較細節的東西,可以單獨看相關的內容,在全域性理解的情況下根據自身特性去看也會很容易明白