lua 状态机及内存管理
<p>Lua虚拟机之间的工作是线程安装的,因为一切和虚拟机相关的内存都被关联到虚拟机对象中,没有任何共享变量。Lua允许用户自定义内存管理器,在虚拟机创建时传入,使得使用者对整个运行状态可控。虚拟机的核心部分没有任何的System call。</p>
1. 内存管理
一般我们直接使用:
lua_State *(luaL_newstate) (void);
来直接创建一个虚拟机。其实这样创建虚拟机,传入虚拟机的内存管理函数是C标准库的内存管理函数,具体如下:
lua_State *luaL_newstate (void) {
lua_State *L = lua_newstate(l_alloc, NULL);
if (L) lua_atpanic(L, &panic);
return L;
}
luaL_newstate其实是利用lua_newstate实现的,传给lua_newstate的内存管理器这l_alloc,而l_alloc的实现如果:
static void *l_alloc (void *ud, void *ptr, size_t osize, size_t nsize) {
(void)ud; (void)osize; /* not used */
if (nsize == 0) {
free(ptr);
return NULL;
}
else
return realloc(ptr, nsize);
}
l_alloc是利用C标准库的realloc和free来实现的内存管理器。在这里,用户可以用自己的管理器来代替默认的内存管理器。l_alloc功能上类似于C标准库的realloc,但是当时nsize为0时,提供释放内存的功能。这个内存管理接口接受额外的一个指针ud,这可以让内存管理模块工作在不同的堆上。
lua使用一组宏来管理单个对象、数组、可变长数组等不同类别的内存。定义在lmem.h中:
#define luaM_reallocv(L,b,on,n,e)
(((sizeof(n) >= sizeof(size_t) && cast(size_t, (n)) + 1 > MAX_SIZET/(e))
? luaM_toobig(L) : cast_void(0)) ,
luaM_realloc_(L, (b), (on)*(e), (n)*(e)))
/*
** Arrays of chars do not need any test
*/
#define luaM_reallocvchar(L,b,on,n)
cast(char *, luaM_realloc_(L, (b), (on)*sizeof(char), (n)*sizeof(char)))
#define luaM_freemem(L, b, s) luaM_realloc_(L, (b), (s), 0)
#define luaM_free(L, b) luaM_realloc_(L, (b), sizeof(*(b)), 0)
#define luaM_freearray(L, b, n) luaM_realloc_(L, (b), (n)*sizeof(*(b)), 0)
#define luaM_malloc(L,s) luaM_realloc_(L, NULL, 0, (s))
#define luaM_new(L,t) cast(t *, luaM_malloc(L, sizeof(t)))
#define luaM_newvector(L,n,t)
cast(t *, luaM_reallocv(L, NULL, 0, n, sizeof(t)))
#define luaM_newobject(L,tag,s) luaM_realloc_(L, NULL, tag, (s))
#define luaM_growvector(L,v,nelems,size,t,limit,e)
if ((nelems)+1 > (size))
((v)=cast(t *, luaM_growaux_(L,v,&(size),sizeof(t),limit,e)))
#define luaM_reallocvector(L, v,oldn,n,t)
((v)=cast(t *, luaM_reallocv(L, v, oldn, n, sizeof(t))))
这组宏实际调用luaM_realloc_和luaM_growaux_这两个内部API,它们不会被直接调用。实现如下:
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;
}
luaM_realloc_调用保存在global_State中的内存管理器来管理内存。主要工作包括分配新的内存、释放不用的内存、扩展不够用的内存,还有通过realloc试图释放掉申请过大的内存的后半部分(取决于用户提供的内存管理器能不能缩小内存块)。
void *luaM_growaux_ (lua_State *L, void *block, int *size, size_t size_elems, int limit, const char *what) {
void *newblock;
int newsize;
if (*size >= limit/2) { /* cannot double it? */
if (*size >= limit) /* cannot grow even a little? */
luaG_runerror(L, "too many %s (limit is %d)", what, limit);
newsize = limit; /* still have at least one free place */
}
else {
newsize = (*size)*2;
if (newsize < MINSIZEARRAY)
newsize = MINSIZEARRAY; /* minimum size */
}
newblock = luaM_reallocv(L, block, *size, newsize, size_elems);
*size = newsize; /* update only when everything else is OK */
return newblock;
}
luaM_growaux_是用来管理可变长数组的。其主要策略是当数组空间不够时,扩大为原来的两倍。
2. 全局状态机
global_State对于lua使用者是不可见的,无法用公开的API取到它的指针,也不需要引用它。global_State里面有对主线程的引用,有注册表管理所有全局数据,有全局字符串表,有内存管理函数,GC相关的信息以及一切lua工作时需要的工作内存。
创建一个新的lua虚拟机时,第一块申请的内存将用来保存主线程和这个全局状态机。利用一个LG结构,把主线程lua_Statet和global_State分配在一起。具体如下:
/*
** thread state + extra space
*/
typedef struct LX {
lu_byte extra_[LUA_EXTRASPACE];
lua_State l;
} LX;
/*
** Main thread combines a thread state and the global state
*/
typedef struct LG {
LX l;
global_State g;
} LG;
lua_newstate初始化所有global_State中将引用的数据。lua_newstate()利用用户传进来的内存分配器分配主线程和global_State的内存,然后把内存分配器赋给global_State的frealloc来管理虚拟机内在的所有内存。把主线程和global_State关联上以及处理GC等相关的初始化。下面看一下它的具体实现:
lua_State *lua_newstate (lua_Alloc f, void *ud) {
int i;
lua_State *L;
global_State *g;
LG *l = cast(LG *, (*f)(ud, NULL, LUA_TTHREAD, sizeof(LG)));
if (l == NULL) return NULL;
L = &l->l.l;
g = &l->g;
L->next = NULL;
L->tt = LUA_TTHREAD;
g->currentwhite = bitmask(WHITE0BIT);
L->marked = luaC_white(g);
preinit_thread(L, g);
g->frealloc = f;
g->ud = ud;
g->mainthread = L;
g->seed = makeseed(L);
g->gcrunning = 0; /* no GC while building state */
g->GCestimate = 0;
g->strt.size = g->strt.nuse = 0;
g->strt.hash = NULL;
setnilvalue(&g->l_registry);
luaZ_initbuffer(L, &g->buff);
g->panic = NULL;
g->version = NULL;
g->gcstate = GCSpause;
g->gckind = KGC_NORMAL;
g->allgc = g->finobj = g->tobefnz = g->fixedgc = NULL;
g->sweepgc = NULL;
g->gray = g->grayagain = NULL;
g->weak = g->ephemeron = g->allweak = NULL;
g->twups = NULL;
g->totalbytes = sizeof(LG);
g->GCdebt = 0;
g->gcfinnum = 0;
g->gcpause = LUAI_GCPAUSE;
g->gcstepmul = LUAI_GCMUL;
for (i=0; i < LUA_NUMTAGS; i++) g->mt[i] = NULL;
if (luaD_rawrunprotected(L, f_luaopen, NULL) != LUA_OK) {
/* memory allocation error: free partial state */
close_state(L);
L = NULL;
}
return L;
}
下面主要看f_luaopen(),此函数主要初始化了主线程的数据栈、初始化注册表、初始化字符串表和字符串cache、初始化元表用的字符串、初始化词法分析用的token串等等。由于初始化中会分配内存,有可能会引起内存错误。在lua_newstate中把g->version = NULL,在f_luaopen()中把所有的初始化完成后,如果没有错误才给版本号赋值g->version = lua_version(NULL)。我们通过’g->version’ != NULL来检测虚拟机是否正确建立起来。
/*
** open parts of the state that may cause memory-allocation errors.
** ('g->version' != NULL flags that the state was completely build)
*/
static void f_luaopen (lua_State *L, void *ud) {
global_State *g = G(L);
UNUSED(ud);
stack_init(L, L); /* init stack */
init_registry(L, g);
luaS_init(L);
luaT_init(L);
luaX_init(L);
g->gcrunning = 1; /* allow gc */
g->version = lua_version(NULL);
luai_userstateopen(L);
}