Lua源码分享 Gc篇(三)流程之创建对象

3.4k 词

[TOC]

前面两篇主要介绍一些基础,帮助后面gc流程理解的。像是饭前的开胃菜一般,让你后面容易吃的更多。接下来几篇都是gc流程相关

这里列出几个问题,你可以直接跳过。当然,有所了解的按照自己的理解去回答一下,或许会有一些新的想法。如果你都会回答的很清晰了,那么接下来的系列几乎可以不怎么看了。

(1)对象的颜色变化过程? (2)新创建对象和gc流程是怎么关联的? (3)什么时候gc? (4)增量式gc体现在哪?

gc的流程,按照程序状态分为:

/*
** Possible states of the Garbage Collector
*/
#define GCSpause    0
#define GCSpropagate    1
#define GCSsweepstring    2
#define GCSsweep    3
#define GCSfinalize    4
  • 1.初始化阶段
  • 2.扫描阶段
  • 3.回收阶段(字符串)
  • 4.回收阶段
  • 5.结束阶段

接下来,主要讲一下新创建对象是怎么和gc关联的。因为gc所需要的对象都是在某个地方创建出来的,并且在创建的时候就会和第二篇提到的数据结构关联上了

新创建对象

测试代码:

// main 函数
    int nCount = 0;
    while(true){
        int bEven = nCount %2 == 0;
        if(!bEven)
        {
            Sleep(1000);
            ++nCount;
            continue;
        }
        ++nCount;
        // do something
        LuaTest();
    }
// LuaTest函数
g_luaReg->DoScript("Test.lua");

// Test.lua

luaC_link函数

这个是新生成对象和gc绑定关系的关键函数,也就是把新创建的对象放在gc链表上[1]:

void luaC_link (lua_State *L, GCObject *o, lu_byte tt) {
  global_State *g = G(L);
  o->gch.next = g->rootgc;
  g->rootgc = o;
  o->gch.marked = luaC_white(g);
  o->gch.tt = tt;
}

这个函数只做了三件事:

  • 把新创建的对象放在gc链表的开头,因为是单向链表[1]
  • 把新创建的对象标记为当前白色(currentwhite)
  • 设置对象的类型

下面分别介绍一下各需要gc的类型的创建对象部分

table

Test.lua中新建一个table测试代码:

local t = {1, 2, 3}

ps:这里给table放了三个元素是为了让自己调试的好找到是创建的这个table。因为虽然Test.lua中只有一行代码,创建了一个table t。但是在虚拟机启动和加载文件的时候,会创建很多其他的table。

源码:

Table *luaH_new (lua_State *L, int narray, int nhash) {
  Table *t = luaM_new(L, Table);
  luaC_link(L, obj2gco(t), LUA_TTABLE);
  t->metatable = NULL;
  t->flags = cast_byte(~0);
  /* temporary values (kept only if some malloc fails) */
  t->array = NULL;
  t->sizearray = 0;
  t->lsizenode = 0;
  t->node = cast(Node *, dummynode);
  setarrayvector(L, t, narray);
  setnodevector(L, t, nhash);
  return t;
}

说明:

  • new一个table的地方有很多,但是最终都是调用到这个函数,new出来的是一个堆上的一块内存
  • 对象类型设置为LUA_TABLE
  • 放到gc链表上
  • 再把这个指针封装的TValue放到虚拟机的栈上。sethvalue等系列函数,就是把new出来的对象封装到TValue中,然后找到一个栈的元素,把这个value.gc赋值为这个新的对象,看下面代码。有些地方是放在栈顶,那么就需要有栈的操作,比如top++(incr_top(L))
    #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); }
    

    可以看到,需要GC的对象都会放在TValue的gc这个字段,在数据结构篇可以看到有解释[1]。

lua function

创建函数,测试代码:

local a = 3
function f()
    local b = a
    print("test function")
end

源码:

Closure *luaF_newLclosure (lua_State *L, int nelems, Table *e) {
  Closure *c = cast(Closure *, luaM_malloc(L, sizeLclosure(nelems)));
  luaC_link(L, obj2gco(c), LUA_TFUNCTION);
  c->l.isC = 0;
  c->l.env = e;
  c->l.nupvalues = cast_byte(nelems);
  while (nelems--) c->l.upvals[nelems] = NULL;
  return c;
}

说明:

  • luaC_link放在gc链表上
  • 把对象类型为LUA_TFUNCTION

PROTO

目前自己的理解这是一个以文件为单位的chunk,所以每个文件都是以一个个独立的Proto类型在代码里存在的。这一块没有仔细去研究,所以主要是自己理解,以做抛砖引玉之用!

代码:

Proto *luaF_newproto (lua_State *L) {
  Proto *f = luaM_new(L, Proto);
  luaC_link(L, obj2gco(f), LUA_TPROTO);
// 省略...
  return f;
}

说明:

  • luaC_link放到gc链表上
  • f_parser函数,发现新建Proto之后,会新建一个lua函数(luaF_newLclosure)。所以,Proto是和Closure绑定的,也很好理解,函数是需要和某个文件有关联的

THREAD

创建携程测试代码:

co = coroutine.create(function (a,b)
    print(111, a, b)
end)

代码:

LUA_API lua_State *lua_newthread (lua_State *L) {
  lua_State *L1;
  lua_lock(L);
  luaC_checkGC(L);
  L1 = luaE_newthread(L);
  setthvalue(L, L->top, L1);
  api_incr_top(L);
  lua_unlock(L);
  luai_userstatethread(L, L1);
  return L1;
}

说明:

  • 放在gc链表的luaC_link是在luaE_newthread中调用的

String

代码:

static TString *newlstr (lua_State *L, const char *str, size_t l,
                                       unsigned int h) {
  TString *ts;
  stringtable *tb;
  if (l+1 > (MAX_SIZET - sizeof(TString))/sizeof(char))
    luaM_toobig(L);
  ts = cast(TString *, luaM_malloc(L, (l+1)*sizeof(char)+sizeof(TString)));
  ts->tsv.len = l;
  ts->tsv.hash = h;
  ts->tsv.marked = luaC_white(G(L));
  ts->tsv.tt = LUA_TSTRING;
  ts->tsv.reserved = 0;
  memcpy(ts+1, str, l*sizeof(char));
  ((char *)(ts+1))[l] = '