Lua5.3 GC源码阅读(2)

上篇已经提到表作为lua中的一种对象,其生命周期是被GC管理的.

下面就从一个表的创建来分析GC模块是如何接管其生命周期的.

先来看一段lua代码

--a.lua
local a = {}
a.foo = "bar"

使用`luac -p -l a.lua` 得到如下结果

main <a.lua:0,0> (3 instructions at 0x20d7a50)
0+ params, 2 slots, 1 upvalue, 1 local, 2 constants, 0 functions
        1       [1]     NEWTABLE        0 0 0
        2       [2]     SETTABLE        0 -1 -2 ; "foo" "bar"
        3       [2]     RETURN          0 1

NEWTABLE对就OPCODE中的OP_NEWTABLE.

根据lopcodes.h中的定义, OP_NEWTABLE使用A,B,C三个寄存器,其中寄存器A用来接收创建的table, 寄存器B和C用于为新创建的table预分配array部分大小和hash部分大小.

在lvm.c中找到代码片段刚好印证了上面的解释.

vmcase(OP_NEWTABLE) {
  int b = GETARG_B(i);
  int c = GETARG_C(i);
  Table *t = luaH_new(L);
  sethvalue(L, ra, t);
  if (b != 0 || c != 0)
    luaH_resize(L, t, luaO_fb2int(b), luaO_fb2int(c));
  checkGC(L, ra + 1);
  vmbreak;
}

从luaH_new的定义可以得到一个结论,所有需要GC管理的对象都会调用luaC_newobj函数来分配内存空间.

//ltable.c
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);
  t->array = NULL;
  t->sizearray = 0;
  setnodevector(L, t, 0);
  return t;
}

在luaC_newobj函数中, 首先调用luaM_newobject来为对象分配内存, 然后为正确管理此对象生命周期做了一些初始化操作.
1. 将对象标记为白色(lua5.3中使用的是三色标记清除垃圾回收算法)
2. 为此对象标记类型(类型的含义与上篇tt_字段的类型含义相同)
3. 将其挂在global_State上的allgc链表上.

由此可以得出一个结论,所有需要GC管理生命周期的对象都在allgc的链表上

//lgc.c
Object *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;
}

在luaM_newobject中就只做了两件事:

1. 调用g->frealloc进行分配内存,如果分配失败的话,执一次fullgc,再次尝试分配
2. 将新增使用内存量, 加入到global_State中的GCdebt字段中.

其中g->frealloc是在调用lua_newstate创建lua_State时指定的, 值得一提的是当lua_Alloc中的参数ptr为null时, osize参数为要new的对象的类型.

//lua.h
LUA_API lua_State *(lua_newstate) (lua_Alloc f, void *ud);
typedef void * (*lua_Alloc) (void *ud, void *ptr, size_t osize, size_t nsize);
//lmem.h
#define luaM_newobject(L,tag,s) luaM_realloc_(L, NULL, tag, (s))
//lmem.c
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;
   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);
   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;
}

OP_NEWTABLE的最后一步就是调用checkGC, 如果有未偿还的债务,就执行一次luaC_step.

//lvm.c
#define checkGC(L,c)  \
  { luaC_condGC(L, L->top = (c),  /* limit of live values */ \
		   Protect(L->top = ci->top));  /* restore top */ \
     luai_threadyield(L); }
//lgc.h
 #define luaC_condGC(L,pre,pos) \
         { if (G(L)->GCdebt > 0) { pre; luaC_step(L); pos;}; \
           condchangemem(L,pre,pos); }

Lua中的GC实现为’增量标记-清除回收器’, 每次调用luaC_step都会执行一次增加回收.

到目前为止, GC可以通过luaC_newobj和luaC_condGC来获得哪些对象需要被其管理生命周期, 并且什么时间触发增量GC操作.

后面开始分析luaC_step函数.



发表评论