lua 状态机及内存管理

5.7k 词
<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);
}