Lua源码分享 Gc篇(三)流程之创建对象
[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] = '