joe 发表于 2022-6-20 14:40:22

Lua源码分析 - 栈结构篇 - 数据栈和调用栈(03)

目录

一、Lua栈结构 - 栈存储方式

二、Lua栈结构 - 数据栈结构StkId

三、Lua栈结构 - 调用栈结构CallInfo

四、Lua栈结构 - 初始化和释放操作stack_init&freestack

五、Lua栈结构 - 栈扩容操作lua_checkstack

一、Lua栈结构 - 栈存储方式
Lua栈结构主要是lua_State结构上。Lua的栈结构主要由两部分组成:数据栈和调用栈

数据栈:

主要由一个StkId结构的数组组成。StkId结构为TValue,支持字符串、函数、数字等数据结构。
所有的数据都通过lapi.c文件中的lua_push*函数向栈上压入不同类型的值(数字、字符串、函数等)。
L->stack:指向栈底部。每次将一个数据压入栈上,栈指针(L->top++)都会指向到下一个结构上。
L->stacksize:栈体的大小。在栈结构初始化的时候,会分配一个BASIC_STACK_SIZE=40大小的栈结构。(LUAI_MAXSTACK 32位:15000;64位:1000000)
L->stack_last:指向栈头部,但是会留空EXTRA_STACK=5个BUF,用于元表调用或错误处理的栈操作。
L->top:栈顶指针。压入数据,都通过移动栈顶指针来实现。
调用栈:

主要由一个CallInfo的结构组成。CallInfo是一个双向链表结构。通过双向链表结构来管理每一个Lua的函数调用栈信息。
Lua一共有三种类型的函数:C语言闭包函数(例如pmain)、Lua的C函数库(例如str字符串函数)和LUA语言
每一个函数的调用,都会新生产一个CallInfo的调用栈结构,用于管理函数调用的栈指针信息。当一个函数调用结束后,会返回CallInfo链表的前一个调用栈,直到所有的调用栈结束回到L->base_ci。
调用栈最终都会指向数据栈上,通过一个个调用栈,用于管理不同的函数调用。
每次调用栈调用函数完成后,都会将函数返回的结果在栈上移动位置,将结果集逐个从ci->func位置开始填充,并调整L->top栈顶指针。这样的好处就是调用完一个函数,数据栈上只需要存储最终函数返回的结果集,不会因为复杂的函数嵌套而导致整个栈体结构迅速扩大。
https://img-blog.csdnimg.cn/20200213150402796.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2luaXRwaHA=,size_16,color_FFFFFF,t_70

/* 调用栈:调用栈信息管理(CallInfo 为双向链表结构) */
unsigned short nci;/* number of items in 'ci' list - 存储一共多少个CallInfo */
CallInfo base_ci;/* CallInfo for first level (C calling Lua) - 调用栈的头部指针 */
CallInfo *ci;/* call info for current function - 当前运行函数信息 */

/* 数据栈:栈指针地址管理StkId = TValue 的数组 */
StkId top;/* first free slot in the stack - 线程栈的栈顶指针 */
StkId stack_last;/* last free slot in the stack- 线程栈的最后一个位置 */
StkId stack;/* stack base - 栈的指针,当前执行的位置*/
int stacksize; //栈大小
二、Lua栈结构 - 数据栈结构StkId
Lua的栈结构由一个StkId结构存储。lua中的数据分为值类型和引用类型。引用类型需要引入GC机制管理。

看一下Lua数据栈的StkId结构:

typedef TValue *StkId;/* index to stack elements */

/*
** Union of all Lua values
*/
typedef union Value {
GCObject *gc;    /* collectable objects 存放所有需要垃圾回收的类型的对象 */
void *p;         /* light userdata存放轻量用户数据类型(lightuserdata)*/
int b;         /* booleans   */
lua_CFunction f; /* light C functions 存放一个方法*/
lua_Integer i;   /* integer numbers存放int数字*/
lua_Number n;    /* float numbers 存放float数字 */
} Value;

#define TValuefields        Value value_; int tt_

typedef struct lua_TValue {
TValuefields;
} TValue;
三、Lua栈结构 - 调用栈结构CallInfo
Lua把调用栈和数据栈分离了,调用栈放在一个叫做 CallInfo 的结构中,以数组的形式储存在虚拟机对象里。注意:正在调用的函数一定存在于数据栈上。

ci->func:指向正在调用操作的栈底位置。(也可以认为指向调用函数的位置)
ci->top:指向调用栈的栈顶部分。默认是LUA_MINSTACK=20个栈坑,大部分情况下CallInfo也够用了。如果调用栈过大,则会抛栈溢出的错误。
previous和next是双向链表指针,用于连接各个调用栈。当执行完一个函数,通过previous回滚到上一个调用栈CI
CallInfo结构:

typedef struct CallInfo {
StkId func;/* 当前调用栈的调用指针处 function index in the stack    */
StkId        top;/* 调用栈的栈顶 top for this function */
struct CallInfo *previous, *next;/* dynamic call link */
union {
    struct {/* only for Lua functions */
      StkId base;/* base for this function */
      const Instruction *savedpc;
    } l;
    struct {/* only for C functions */
      lua_KFunction k;/* continuation in case of yields */
      ptrdiff_t old_errfunc;
      lua_KContext ctx;/* context info. in case of yields */
    } c;
} u;
ptrdiff_t extra;
short nresults;/* expected number of results from this function */
unsigned short callstatus;
} CallInfo;
四、Lua栈结构 - 初始化和释放操作stack_init&freestack
Lua栈初始化的时候,会默认分配一个40大小的栈空间,栈尾l->stack,栈顶操作指针l->top,栈头l->stack_last

留空了EXTRA_STACK5个作为空闲buf,用于元表调用或错误处理的栈操作,也就是这些扩展槽位可以让某些操作不用考虑栈空间是否足够。

调用栈初始化的时候,ci->func指向了l->top操作指针(函数开始位置)。lstate.c文件中stack_init函数

/**
* 栈初始化(栈分为数据栈 + 调用栈)
*/
static void stack_init (lua_State *L1, lua_State *L) {
int i; CallInfo *ci;
/* initialize stack array */
L1->stack = luaM_newvector(L, BASIC_STACK_SIZE, TValue); //默认40个
L1->stacksize = BASIC_STACK_SIZE;
for (i = 0; i < BASIC_STACK_SIZE; i++)
    setnilvalue(L1->stack + i);/* erase new stack */
L1->top = L1->stack;
L1->stack_last = L1->stack + L1->stacksize - EXTRA_STACK;//栈顶默认到35,空出5个做buf?

/* initialize first ci */
ci = &L1->base_ci;
ci->next = ci->previous = NULL;
ci->callstatus = 0;
ci->func = L1->top;//指向当前栈顶
setnilvalue(L1->top++);/* 'function' entry for this 'ci' */
ci->top = L1->top + LUA_MINSTACK;//指向到L1->top +20的位置
L1->ci = ci;
}

/**
* 释放栈结构内存
*/
static void freestack (lua_State *L) {
if (L->stack == NULL)
    return;/* stack not completely built yet */
L->ci = &L->base_ci;/* free the entire 'ci' list */
luaE_freeCI(L);
lua_assert(L->nci == 0);
luaM_freearray(L, L->stack, L->stacksize);/* free stack array */
}


五、Lua栈结构 - 栈扩容操作lua_checkstack
Lua会通过lua_checkstack函数去检查栈空间的大小(lapi.c文件中)。初始化分配栈大小的时候是分配了40个StkId,栈头部留5个buf空间,所以栈头部是35个。

对于需要在循环里不断压入元素的操作,应该调用lua_checkstack用于检查每次的栈大小空间。

/**
* 检查lua_State的大小,如果栈小了,则扩容(默认栈大小:栈的默认尺寸是35)
* 说明:只会不断扩容,不会缩小
* 32/64位机器栈最大:1000000
* 16位机器栈最大:15000
*/
LUA_API int lua_checkstack (lua_State *L, int n) {
int res;
CallInfo *ci = L->ci;
lua_lock(L);
api_check(L, n >= 0, "negative 'n'");
if (L->stack_last - L->top > n)/* stack large enough? */
    res = 1;/* yes; check is OK */
else {/* no; need to grow stack */
    int inuse = cast_int(L->top - L->stack) + EXTRA_STACK;
    if (inuse > LUAI_MAXSTACK - n)/* can grow without overflow? */
      res = 0;/* no */
    else/* try to grow stack尝试去扩容 */
      res = (luaD_rawrunprotected(L, &growstack, &n) == LUA_OK);
}
if (res && ci->top < L->top + n)
    ci->top = L->top + n;/* adjust frame top */
lua_unlock(L);
return res;
}

/**
* 针对lua_State进行扩容
*/
static void growstack (lua_State *L, void *ud) {
int size = *(int *)ud;
luaD_growstack(L, size);
}

/**
* 对lua_State进行扩容
*/
void luaD_growstack (lua_State *L, int n) {
int size = L->stacksize;
if (size > LUAI_MAXSTACK)/* error after extra size? */
    luaD_throw(L, LUA_ERRERR);
else {
    int needed = cast_int(L->top - L->stack) + n + EXTRA_STACK;
    int newsize = 2 * size;
    if (newsize > LUAI_MAXSTACK) newsize = LUAI_MAXSTACK;
    if (newsize < needed) newsize = needed;
    if (newsize > LUAI_MAXSTACK) {/* stack overflow? */
      luaD_reallocstack(L, ERRORSTACKSIZE);
      luaG_runerror(L, "stack overflow");
    }
    else
      luaD_reallocstack(L, newsize);
}
}

/**
* 重新分配一块statck内容,并且进行拷贝
*/
void luaD_reallocstack (lua_State *L, int newsize) {
TValue *oldstack = L->stack;
int lim = L->stacksize;
lua_assert(newsize <= LUAI_MAXSTACK || newsize == ERRORSTACKSIZE);
lua_assert(L->stack_last - L->stack == L->stacksize - EXTRA_STACK);
luaM_reallocvector(L, L->stack, L->stacksize, newsize, TValue);
for (; lim < newsize; lim++)
    setnilvalue(L->stack + lim); /* erase new segment */
L->stacksize = newsize;
L->stack_last = L->stack + newsize - EXTRA_STACK;
correctstack(L, oldstack);
}

/**
* 拷贝到新的栈结构上
*/
static void correctstack (lua_State *L, TValue *oldstack) {
CallInfo *ci;
UpVal *up;
L->top = (L->top - oldstack) + L->stack;
for (up = L->openupval; up != NULL; up = up->u.open.next)
    up->v = (up->v - oldstack) + L->stack;
for (ci = L->ci; ci != NULL; ci = ci->previous) {
    ci->top = (ci->top - oldstack) + L->stack;
    ci->func = (ci->func - oldstack) + L->stack;
    if (isLua(ci))
      ci->u.l.base = (ci->u.l.base - oldstack) + L->stack;
}
}

————————————————
版权声明:本文为CSDN博主「老码农zhuli」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/initphp/article/details/97273744

页: [1]
查看完整版本: Lua源码分析 - 栈结构篇 - 数据栈和调用栈(03)