前言
- 这是一篇关于
lua源码分析的博客,每个阶段会有不同的理解,所以这篇博客会更新自己的认知。
正文
- 首先我们看的是
lua.c的入口,也就是我们使用lua.exe执行lua文件的地方,从这里可以知道lua执行lua文本文件的过程。 - 刚开始使用
luaL_newstatenew了一个lua_State结构体
数据类型
lua使用lua_TValue表示lua中所有的类型,具体如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18// lobject.h
typedef struct lua_TValue {
TValuefields;
} TValue;
/*
** Union of all Lua values
*/
typedef union Value {
GCObject *gc; /* collectable objects */
void *p; /* light userdata */
int b; /* booleans */
lua_CFunction f; /* light C functions */
lua_Integer i; /* integer numbers */
lua_Number n; /* float numbers */
} Value;- 为什么可以表示所有类型呢,如果是类似于需要垃圾回收的,就可以使用
GCObject *指针表示,比如TString,Table,而其他的就表示如不需要lua进行垃圾回收,但需要释放内存的,需要靠自己释放内存;
- 为什么可以表示所有类型呢,如果是类似于需要垃圾回收的,就可以使用
lua使用GCUnion表示使用需要垃圾回收的对象,具体如下:1
2
3
4
5
6
7
8
9
10
11
12
13//lstate.h
/*
** Union of all collectable objects (only for conversions)
*/
union GCUnion {
GCObject gc; /* common header */
struct TString ts;
struct Udata u;
union Closure cl;
struct Table h;
struct Proto p;
struct lua_State th; /* thread */
};- 这里面每个类型都定义了一个
gcobject,也就是公共头,这样可以用上面的GCObject表示,GCObject其实就是用来内存对齐的,这是使用联合体的好处。
- 这里面每个类型都定义了一个
字符串的实现
luaS_newlstr判断完是否长短字符串1
2
3
4
5
6
7
8
9
10
11
12
13
14
15/*
** new string (with explicit length)
*/
TString *luaS_newlstr (lua_State *L, const char *str, size_t l) {
if (l <= LUAI_MAXSHORTLEN) /* short string? */
return internshrstr(L, str, l);
else {
TString *ts;
if (l >= (MAX_SIZE - sizeof(TString))/sizeof(char))
luaM_toobig(L);
ts = luaS_createlngstrobj(L, l);
memcpy(getstr(ts), str, l * sizeof(char));
return ts;
}
}- 这个是带长度的字符串
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21/*
** Create or reuse a zero-terminated string, first checking in the
** cache (using the string address as a key). The cache can contain
** only zero-terminated strings, so it is safe to use 'strcmp' to
** check hits.
*/
TString *luaS_new (lua_State *L, const char *str) {
unsigned int i = point2uint(str) % STRCACHE_N; /* hash */
int j;
TString **p = G(L)->strcache[i];
for (j = 0; j < STRCACHE_M; j++) {
if (strcmp(str, getstr(p[j])) == 0) /* hit? */
return p[j]; /* that is it */
}
/* normal route */
for (j = STRCACHE_M - 1; j > 0; j--)
p[j] = p[j - 1]; /* move out last element */
/* new element is first in the list */
p[0] = luaS_newlstr(L, str, strlen(str));
return p[0];
}- 创建或重新使用一个带
\0终止的字符串,首次检查缓存,缓存只包含0终止的字符串,所以它是安全的,去使用strcmp去检查缓存是否存在。其实就是从strcache里面中找,如果找不到,就把p[0]缓存为新创建的字符串。
短字符串(长度小于等于40)
短字符串走
internshrstr(L, str, l)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30/*
** checks whether short string exists and reuses it or creates a new one
*/
static TString *internshrstr (lua_State *L, const char *str, size_t l) {
TString *ts;
global_State *g = G(L);
unsigned int h = luaS_hash(str, l, g->seed);
TString **list = &g->strt.hash[lmod(h, g->strt.size)];
lua_assert(str != NULL); /* otherwise 'memcmp'/'memcpy' are undefined */
for (ts = *list; ts != NULL; ts = ts->u.hnext) {
if (l == ts->shrlen &&
(memcmp(str, getstr(ts), l * sizeof(char)) == 0)) {
/* found! */
if (isdead(g, ts)) /* dead (but not collected yet)? */
changewhite(ts); /* resurrect it */
return ts;
}
}
if (g->strt.nuse >= g->strt.size && g->strt.size <= MAX_INT/2) {
luaS_resize(L, g->strt.size * 2);
list = &g->strt.hash[lmod(h, g->strt.size)]; /* recompute with new size */
}
ts = createstrobj(L, l, LUA_TSHRSTR, h);
memcpy(getstr(ts), str, l * sizeof(char));
ts->shrlen = cast_byte(l);
ts->u.hnext = *list;
*list = ts;
g->strt.nuse++;
return ts;
}luaS_hash(str, l, g->seed);这里面是产生hash的接口,其实是根据g->seed种子去做的hash值,1
2
3
4
5
6
7unsigned int luaS_hash (const char *str, size_t l, unsigned int seed) {
unsigned int h = seed ^ cast(unsigned int, l);
size_t step = (l >> LUAI_HASHLIMIT) + 1;
for (; l >= step; l -= step)
h ^= ((h<<5) + (h>>2) + cast_byte(str[l - 1]));
return h;
}- 种子去异或长度,由异或的运算可知,相同为0,不同为1;然后
step右移5位,这样循环的次数会变少,关于hash算法的谈论,可以看云风大神关于lua hash函数的一点谈论,这里就不深入研究了,hash函数的设计其实就是为了减少少的碰撞,不至于吃更多的内存。
- 种子去异或长度,由异或的运算可知,相同为0,不同为1;然后
继续回到
hash后面的讲解,取到g->strt.hash其实lmod其实就是为了防止不超过当前strt.size长度的大小,然后开始顺着列表开始遍历,如果产生碰撞,其实就是产生一条链子,顺着这条链表查找下去如果有申请的字符串,就使用,而且先检查长度,这样会减少strcmp的比较效率,如果找到了,判断一下字符串是否在可回收阶段,返回查找到的字符串;如果没找到,肯定是需要申请内存去放TStirng,这样肯定先检查需不需要扩容,具体如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29/*
** resizes the string table
*/
void luaS_resize (lua_State *L, int newsize) {
int i;
stringtable *tb = &G(L)->strt;
if (newsize > tb->size) { /* grow table if needed */
luaM_reallocvector(L, tb->hash, tb->size, newsize, TString *);
for (i = tb->size; i < newsize; i++)
tb->hash[i] = NULL;
}
for (i = 0; i < tb->size; i++) { /* rehash */
TString *p = tb->hash[i];
tb->hash[i] = NULL;
while (p) { /* for each node in the list */
TString *hnext = p->u.hnext; /* save next */
unsigned int h = lmod(p->hash, newsize); /* new position */
p->u.hnext = tb->hash[h]; /* chain it */
tb->hash[h] = p;
p = hnext;
}
}
if (newsize < tb->size) { /* shrink table if needed */
/* vanishing slice should be empty */
lua_assert(tb->hash[newsize] == NULL && tb->hash[tb->size - 1] == NULL);
luaM_reallocvector(L, tb->hash, tb->size, newsize, TString *);
}
tb->size = newsize;
}- 调整大小,这个接口支持扩容和缩小,分配一个
newsize * (TString *)的大小,然后把newsize-oldsize的部分初始化,赋值为空指针,再把旧的元素重新与新大小与一遍,装进新的里面。
- 调整大小,这个接口支持扩容和缩小,分配一个
回到
createstrobj,这里采用了一个巧妙的方法,申请了一个TString的内存大小,再加上字符串的内存大小,转换为字符串的内存的时候就直接使用cast(char*, (ts)) + sizeof (UTString),且使用ts->next = g->allgc头插法,去方便gc遍历
长字符串(长度小于等于40)
- 长字符串没有放进
g->strt里面,而且hash值就是g->seed,简单粗暴,就是创建了一个TString对象,而且长字符串好像不需要查找原来是否有创建好的字符串,也就是说有一个就new一个。
- 长字符串没有放进
table的实现
看下
table结构体的定义,如下1
2
3
4
5
6
7
8
9
10
11typedef struct Table {
CommonHeader;
lu_byte flags; /* 1<<p means tagmethod(p) is not present */
lu_byte lsizenode; /* log2 of size of 'node' array */
unsigned int sizearray; /* size of 'array' array */
TValue *array; /* array part */
Node *node;
Node *lastfree; /* any free position is before this position */
struct Table *metatable;
GCObject *gclist;
} Table;首先如何分配一个
Table *luaH_new (lua_State *L)去分配一个table所需要的内存,如下1
2
3
4
5
6
7
8
9
10Table *luaH_new (lua_State *L) {
GCObject *o = luaC_newobj(L, LUA_TTABLE, sizeof(Table));
Table *t = gco2t(o);
t->metatable = NULL;
t->flags = cast_byte(~0);
t->array = NULL;
t->sizearray = 0;
setnodevector(L, t, 0);
return t;
}- 这里
setnodevector(L,t,0)会把hash相关的变量初始化,初始化第一个node,把key和value赋值为nil。
- 这里
看一下这个
table赋值的接口,如下,这个是使用key为int的时候去插入value的。1
2
3
4
5
6
7
8
9
10
11
12void luaH_setint (lua_State *L, Table *t, lua_Integer key, TValue *value) {
const TValue *p = luaH_getint(t, key);
TValue *cell;
if (p != luaO_nilobject)
cell = cast(TValue *, p);
else {
TValue k;
setivalue(&k, key);
cell = luaH_newkey(L, t, &k);
}
setobj2t(L, cell, value);
}- 这里首先使用
luaH_getint(t,key)去找这个元素,会判断这个值在数组部分还是在hash部分,如果找到了,直接覆盖原来的值,如果没找到,这个时候就需要new一个key,且返回key对应的value,这个key为啥要在hash部分呢,因为数组部分查找竟然没有,那肯定只能是在hash部分。
- 这里首先使用
从
luaH_newkey(L, t, &k),这里是hash部分的通用接口,如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50TValue *luaH_newkey (lua_State *L, Table *t, const TValue *key) {
Node *mp;
TValue aux;
if (ttisnil(key)) luaG_runerror(L, "table index is nil");
else if (ttisfloat(key)) {
lua_Integer k;
if (luaV_tointeger(key, &k, 0)) { /* does index fit in an integer? */
setivalue(&aux, k);
key = &aux; /* insert it as an integer */
}
else if (luai_numisnan(fltvalue(key)))
luaG_runerror(L, "table index is NaN");
}
mp = mainposition(t, key);
if (!ttisnil(gval(mp)) || isdummy(t)) { /* main position is taken? */
Node *othern;
Node *f = getfreepos(t); /* get a free place */
if (f == NULL) { /* cannot find a free place? */
rehash(L, t, key); /* grow table */
/* whatever called 'newkey' takes care of TM cache */
return luaH_set(L, t, key); /* insert key into grown table */
}
lua_assert(!isdummy(t));
othern = mainposition(t, gkey(mp));
if (othern != mp) { /* is colliding node out of its main position? */
/* yes; move colliding node into free position */
while (othern + gnext(othern) != mp) /* find previous */
othern += gnext(othern);
gnext(othern) = cast_int(f - othern); /* rechain to point to 'f' */
*f = *mp; /* copy colliding node into free pos. (mp->next also goes) */
if (gnext(mp) != 0) {
gnext(f) += cast_int(mp - f); /* correct 'next' */
gnext(mp) = 0; /* now 'mp' is free */
}
setnilvalue(gval(mp));
}
else { /* colliding node is in its own main position */
/* new node will go into free position */
if (gnext(mp) != 0)
gnext(f) = cast_int((mp + gnext(mp)) - f); /* chain new position */
else lua_assert(gnext(f) == 0);
gnext(mp) = cast_int(f - mp);
mp = f;
}
}
setnodekey(L, &mp->i_key, key);
luaC_barrierback(L, t, key);
lua_assert(ttisnil(gval(mp)));
return gval(mp);
}首先,先翻译这个解释把,插入一个新
key到hash table,首先检查key的mp位置是否是空闲的,如果不是,检查碰撞的节点它本来的位置是否在这个mp,如果不是,移动这个碰撞的节点放入空的位置,新key放入它自己的mp;除此之外,碰撞的节点在它自己的mp,新key直接放入空的位置。分析一下
mainposition方法,如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25/*
** returns the 'main' position of an element in a table (that is, the index
** of its hash value)
*/
static Node *mainposition (const Table *t, const TValue *key) {
switch (ttype(key)) {
case LUA_TNUMINT:
return hashint(t, ivalue(key));
case LUA_TNUMFLT:
return hashmod(t, l_hashfloat(fltvalue(key)));
case LUA_TSHRSTR:
return hashstr(t, tsvalue(key));
case LUA_TLNGSTR:
return hashpow2(t, luaS_hashlongstr(tsvalue(key)));
case LUA_TBOOLEAN:
return hashboolean(t, bvalue(key));
case LUA_TLIGHTUSERDATA:
return hashpointer(t, pvalue(key));
case LUA_TLCF:
return hashpointer(t, fvalue(key));
default:
lua_assert(!ttisdeadkey(key));
return hashpointer(t, gcvalue(key));
}
}- 函数的作用就是取
table的key对应node的位置,如果是int类型,hash出来就是int&lsizenode的值
- 函数的作用就是取
如果是
mp位置不为nil,或者没有元素,首先获取一个空闲位置freemp,如果没有的话,就需要调整表的大小,也就是rehash(L, t, key)重新hash,如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21/*
** nums[i] = number of keys 'k' where 2^(i - 1) < k <= 2^i
*/
static void rehash (lua_State *L, Table *t, const TValue *ek) {
unsigned int asize; /* optimal size for array part */
unsigned int na; /* number of keys in the array part */
unsigned int nums[MAXABITS + 1];
int i;
int totaluse;
for (i = 0; i <= MAXABITS; i++) nums[i] = 0; /* reset counts */
na = numusearray(t, nums); /* count keys in array part */
totaluse = na; /* all those keys are integer keys */
totaluse += numusehash(t, nums, &na); /* count keys in hash part */
/* count extra key */
na += countint(ek, nums);
totaluse++;
/* compute new size for array part */
asize = computesizes(nums, &na);
/* resize the table to new computed sizes */
luaH_resize(L, t, asize, totaluse - na);
}nums[32],然后初始化nums[i] = 0,na表示数组里面有多少keys,具体代码如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29/*
** Count keys in array part of table 't': Fill 'nums[i]' with
** number of keys that will go into corresponding slice and return
** total number of non-nil keys.
*/
static unsigned int numusearray (const Table *t, unsigned int *nums) {
int lg;
unsigned int ttlg; /* 2^lg */
unsigned int ause = 0; /* summation of 'nums' */
unsigned int i = 1; /* count to traverse all array keys */
/* traverse each slice */
for (lg = 0, ttlg = 1; lg <= MAXABITS; lg++, ttlg *= 2) {
unsigned int lc = 0; /* counter */
unsigned int lim = ttlg;
if (lim > t->sizearray) {
lim = t->sizearray; /* adjust upper limit */
if (i > lim)
break; /* no more elements to count */
}
/* count elements in range (2^(lg - 1), 2^lg] */
for (; i <= lim; i++) {
if (!ttisnil(&t->array[i-1]))
lc++;
}
nums[lg] += lc;
ause += lc;
}
return ause;
}- 各变量的含义,其实变量
limit,就是防止超出数组大小,count elements in range (2^(lg - 1), 2^lg],i就是保存下遍历到多少了,这样就得到数组部分位图和总共使用的数量。
- 各变量的含义,其实变量
hash部分的统计元素有点巧妙,如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44static int numusehash (const Table *t, unsigned int *nums, unsigned int *pna) {
int totaluse = 0; /* total number of elements */
int ause = 0; /* elements added to 'nums' (can go to array part) */
int i = sizenode(t);
while (i--) {
Node *n = &t->node[i];
if (!ttisnil(gval(n))) {
ause += countint(gkey(n), nums);
totaluse++;
}
}
*pna += ause;
return totaluse;
}
static int countint (const TValue *key, unsigned int *nums) {
unsigned int k = arrayindex(key);
if (k != 0) { /* is 'key' an appropriate array index? */
nums[luaO_ceillog2(k)]++; /* count as such */
return 1;
}
else
return 0;
}
/*
** Computes ceil(log2(x))
*/
int luaO_ceillog2 (unsigned int x) {
static const lu_byte log_2[256] = { /* log_2[i] = ceil(log2(i - 1)) */
0,1,2,2,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8
};
int l = 0;
x--;
while (x >= 256) { l += 8; x >>= 8; }
return l + log_2[x];
}- 首先只统计值不是
nil的元素的key,重点看一下luaO_ceillog2,这样循环会减少遍历,类似于空间换时间;其实log_2[i]表示的是2^log_2[i] >= i也就是log_2[i] = ceil(log2(i - 1)),如果大于256说明一定是在2^8方以上,
- 首先只统计值不是
在加上新增加的
key。computesizes(nums, &na)就是重新计算大小了,如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40/*
** Compute the optimal size for the array part of table 't'. 'nums' is a
** "count array" where 'nums[i]' is the number of integers in the table
** between 2^(i - 1) + 1 and 2^i. 'pna' enters with the total number of
** integer keys in the table and leaves with the number of keys that
** will go to the array part; return the optimal size.
*/
static unsigned int computesizes (unsigned int nums[], unsigned int *pna) {
int i;
unsigned int twotoi; /* 2^i (candidate for optimal size) */
unsigned int a = 0; /* number of elements smaller than 2^i */
unsigned int na = 0; /* number of elements to go to array part */
unsigned int optimal = 0; /* optimal size for array part */
/* loop while keys can fill more than half of total size */
for (i = 0, twotoi = 1;
twotoi > 0 && *pna > twotoi / 2;
i++, twotoi *= 2) {
if (nums[i] > 0) {
a += nums[i];
if (a > twotoi/2) { /* more than half elements present? */
optimal = twotoi; /* optimal size (till now) */
na = a; /* all elements up to 'optimal' will go to array part */
}
}
}
lua_assert((optimal == 0 || optimal / 2 < na) && na <= optimal);
*pna = na;
return optimal;
}
static int countint (const TValue *key, unsigned int *nums) {
unsigned int k = arrayindex(key);
if (k != 0) { /* is 'key' an appropriate array index? */
nums[luaO_ceillog2(k)]++; /* count as such */
return 1;
}
else
return 0;
}
接下来重点分析下解决放入元素的问题,直接在代码里面写注释比较好
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81TValue *luaH_newkey (lua_State *L, Table *t, const TValue *key) {
Node *mp;
TValue aux;
if (ttisnil(key)) luaG_runerror(L, "table index is nil");
else if (ttisfloat(key)) {
lua_Integer k;
if (luaV_tointeger(key, &k, 0)) { /* does index fit in an integer? */
setivalue(&aux, k);
key = &aux; /* insert it as an integer */
}
else if (luai_numisnan(fltvalue(key)))
luaG_runerror(L, "table index is NaN");
}
/*
mp是根据key根据hash得到node的地址,
*/
mp = mainposition(t, key);
/*
如果发现node里的value不是nilobj,或者是第一个元素的话,也就是当为hash碰撞了
*/
if (!ttisnil(gval(mp)) || isdummy(t)) { /* main position is taken? */
/*
开始找空闲位置,空闲位置是从最后一个node[i]的位置,开始往左边找
*/
Node *othern;
Node *f = getfreepos(t); /* get a free place */
if (f == NULL) { /* cannot find a free place? */
/*
没有空闲位置就直接扩大hash表的容量
*/
rehash(L, t, key); /* grow table */
/* whatever called 'newkey' takes care of TM cache */
return luaH_set(L, t, key); /* insert key into grown table */
}
lua_assert(!isdummy(t));
/*
取一下在这个位置node[i]的key,然后找一下它本来在的位置
情况1:原本位置不在这
情况2:位置本来是在这里
*/
othern = mainposition(t, gkey(mp));
if (othern != mp) { /* is colliding node out of its main position? */
/* yes; move colliding node into free position */
/*
当本来位置不在这里的时候,从他本来的位置开始找 othern->ikey.next ->...->mp
一定会串起来
othern->ikey.next = 到空闲位置f的距离
空闲位置f = mp
f->next = mp - f
好处应该是尽量hash一次到这个位置
*/
while (othern + gnext(othern) != mp) /* find previous */
othern += gnext(othern);
gnext(othern) = cast_int(f - othern); /* rechain to point to 'f' */
*f = *mp; /* copy colliding node into free pos. (mp->next also goes) */
if (gnext(mp) != 0) {
gnext(f) += cast_int(mp - f); /* correct 'next' */
gnext(mp) = 0; /* now 'mp' is free */
}
setnilvalue(gval(mp));
}
else { /* colliding node is in its own main position */
/* new node will go into free position */
/*
它的key根据hash就在原本的位置了
mp = f f也就是新放入key的元素了
*/
if (gnext(mp) != 0)
gnext(f) = cast_int((mp + gnext(mp)) - f); /* chain new position */
else lua_assert(gnext(f) == 0);
gnext(mp) = cast_int(f - mp);
mp = f;
}
}
setnodekey(L, &mp->i_key, key);
luaC_barrierback(L, t, key);
lua_assert(ttisnil(gval(mp)));
return gval(mp);
}
luaL_newstate()讲解
lua异常处理机制
lua异常处理机制是通过setjmp和longjmp实现的,当longjmp(jmp_buf b, 2)程序会回到setjmp重新执行,且返回值是longjmp的第二个参数;lua就是通过lua_longjmp结构体的status来判断虚拟机是否成功调用函数
lua垃圾回收机制
lua采用增量式标记-清除算法,