• lua源码分享-gc篇(三)流程之创建对象



    前面两篇主要介绍一些基础,帮助后面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
    • 6
    • 7
    • 8
    • 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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    这个函数只做了三件事:

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

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

    table

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

    local t = {1, 2, 3}
    
    • 1

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    说明:

    • 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); }
    
    • 1
    • 2
    • 3
    • 4

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

    lua function

    创建函数,测试代码:

    local a = 3
    function f()
        local b = a
        print("test function")
    end
    
    • 1
    • 2
    • 3
    • 4
    • 5

    源码:

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    说明:

    • 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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    说明:

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

    THREAD

    创建携程测试代码:

    co = coroutine.create(function (a,b)
        print(111, a, b)
    end)
    
    • 1
    • 2
    • 3

    代码:

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    说明:

    • 放在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] = '\0';  /* ending 0 */
      tb = &G(L)->strt;
      h = lmod(h, tb->size);
      ts->tsv.next = tb->hash[h];  /* chain new entry */
      tb->hash[h] = obj2gco(ts);
      tb->nuse++;
      if (tb->nuse > cast(lu_int32, tb->size) && tb->size <= MAX_INT/2)
        luaS_resize(L, tb->size*2);  /* too crowded */
      return ts;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    说明

    • 字符串有一点特殊的,这里并没有调用luaC_link函数放在gc链表上,而是直接放在global_State中的strt变量中,所以扫描的时候也不会处理字符串类型了,因为不在gc链表中。所以,后面流程中处理的方式也有所区别

    Upvalue

    代码:

    void luaC_linkupval (lua_State *L, UpVal *uv) {
      global_State *g = G(L);
      GCObject *o = obj2gco(uv);
      o->gch.next = g->rootgc;  /* link upvalue into `rootgc' list */
      g->rootgc = o;
      if (isgray(o)) { 
        if (g->gcstate == GCSpropagate) {
          gray2black(o);  /* closed upvalues need barrier */
          luaC_barrier(L, uv, uv->v);
        }
        else {  /* sweep phase: sweep it (turning it into white) */
          makewhite(g, o);
          lua_assert(g->gcstate != GCSfinalize && g->gcstate != GCSpause);
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    说明:

    • 详细见参考2
    • 可以看到这里主要是对节点做颜色操作。因为upvalue肯定是跟Closure有关联的,所以可以在扫描Closure的时候找到他的upvalue。隐藏,没有必要再把这个节点放在gc链表上了

    userdata

    代码:

    Udata *luaS_newudata (lua_State *L, size_t s, Table *e) {
      Udata *u;
      if (s > MAX_SIZET - sizeof(Udata))
        luaM_toobig(L);
      u = cast(Udata *, luaM_malloc(L, s + sizeof(Udata)));
      u->uv.marked = luaC_white(G(L));  /* is not finalized */
      u->uv.tt = LUA_TUSERDATA;
      u->uv.len = s;
      u->uv.metatable = NULL;
      u->uv.env = e;
      /* chain it on udata list (after main thread) */
      u->uv.next = G(L)->mainthread->next;
      G(L)->mainthread->next = obj2gco(u);
      return u;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    说明:

    • 详细见参考2

    GC

    一开始会以为lua会每隔固定的时间(setpause)进行一次gc,但是看代码却根本没有这样的循环。那么什么时候GC?

    什么时候GC

    lua在每次new对象成功之后,都会做一次GC的检查,这一点在New一个GC对象(table等)之前都会做这个检查,源码随处可见,自己找一下。这个检查GC的代码就是luaC_checkGC函数:

    #define luaC_checkGC(L) { \
      condhardstacktests(luaD_reallocstack(L, L->stacksize - EXTRA_STACK - 1)); \
      if (G(L)->totalbytes >= G(L)->GCthreshold) \
        luaC_step(L); }
    
    • 1
    • 2
    • 3
    • 4

    可以看到其中如果超过了某个阈值就会进行GC,这个阈值和collectgarbage息息相关,这里面讲起来也需要一个篇幅,先不打算深入了。

    接下来,开始进入gc的正式流程~

    参考

    [1]第二篇 数据结构篇
    [2]新创建对象

  • 相关阅读:
    vscode中对 python 快速增加header 描述
    vue 的常用事件
    Qt扫盲-QVariant理论使用总结
    有了InheritableThreadLocal为啥还需要TransmittableThreadLocal?
    八年测开经验面试28K公司后,吐血整理出高频面试题和答案
    企业级git工作流程
    Mockito Spies InjectMocks & 回调测试
    最优闭回路问题
    关于近期轻量化部署任务的一个小结
    6、mysql高级语句
  • 原文地址:https://blog.csdn.net/pkxpp/article/details/126072096