【Lua进阶系列】lua_Stack

                          【Lua进阶系列】lua_Stack

    

    大家好,我是Lampard~~

    欢迎来到Lua进阶系列的博客

    前文再续,书接上一回。今天和大家讲解一下lua_Stack。

    还记得之前我们曾经写过一篇【lua基础系列】之C/C++与lua的交互方式 ,这篇文章简要提到了lua和c之间的交互过程以及TValue的数据结构,大家没看过的可以先点进去瞅一眼打个底。

    示意图如下:

    

   

(一)lua_Stack究竟由什么组成?

   c与lua之间交互离不开lua堆栈,那么lua堆栈究竟是什么东西,由什么组成?

   对于这个问题,我感觉没有比看源代码更有说服力的答案了(以下是lua5.4最新版代码)。

   直接定位到lua_State定义的文件“lstate.h”中,我们发现了以下结构体还有一堆英文注释,我们现在来一个个分析着看。

struct lua_State {
  CommonHeader;
  unsigned short nci;  /* 存储一共多少个CallInfo number of items in 'ci' list */
  lu_byte status;
  StkId top;  /* 指向栈的顶部,压入数据,都通过移动栈顶指针来实现。 first free slot in the stack */
  global_State *l_G;
  CallInfo *ci;  /* 当前运行函数信息 call info for current function */
  const Instruction *oldpc;  /* last pc traced */
  StkId stack_last;  /* 指向栈的底部,但是会预留空间作宝物处理 last free slot in the stack */
  StkId stack;  /* 指向栈的底部 stack base */
  UpVal *openupval;  /* list of open upvalues in this stack */
  GCObject *gclist;
  struct lua_State *twups;  /* list of threads with open upvalues */
  struct lua_longjmp *errorJmp;  /* current error recover point */
  CallInfo base_ci;  /* 调用栈的头部指针 CallInfo for first level (C calling Lua) */
  volatile lua_Hook hook;
  ptrdiff_t errfunc;  /* current error handling function (stack index) */
  int stacksize;    /* 栈的大小 */
  int basehookcount;
  int hookcount;
  unsigned short nny;  /* number of non-yieldable calls in stack */
  unsigned short nCcalls;  /* number of nested C calls */
  l_signalT hookmask;
  lu_byte allowhook;
};

(1) CommonHeader -- GC的通用头

#define CommonHeader	GCObject *next; lu_byte tt; lu_byte marked

CommonHeader 是使用引用计数机制进行垃圾回收的通用头。如果可以正确的遵从gc的使用规则,也就是说你可以正确无误的使用智能指针,那么理论上bai说,就不可能存在内存泄漏。

 

(2) nci -- 记录调用栈item个数的变量

nci是16位unsigned short类型的一个变量,用于记录有多少个item在调用栈(ci)中

 

(3) status -- 表示当前这个lua_Stack线程的状态

注意这里的线程类型不要与操作系统线程混淆,Lua的线程类型是Lua虚拟机实现一种数据类型,简单来说也就是代表这个lua_Stack的状态。

我们看看lua线程的所有状态(存放在“lua.h”文件中):

LUA_OK -- 正常运行,LUA_YIELD  -- 挂起, LUA_ERRRUN -- 运行时错误,LUA_ERRSYNTAX -- 编译错误 ,LUA_ERRMEM -- 内存分配错误,LUA_ERRGCMM  -- GC内存回收错误,LUA_ERRERR --在运行错误处理函数时发生的错误

/* thread status */
#define LUA_OK		0
#define LUA_YIELD	1
#define LUA_ERRRUN	2
#define LUA_ERRSYNTAX	3
#define LUA_ERRMEM	4
#define LUA_ERRGCMM	5
#define LUA_ERRERR	6

 

 (4) l_G -- 全局状态机,维护全局字符串表、内存管理函数、gc等信息

  在5.4之前l_G并不是global_State全局状态机类型,它是一个把lua_Stack和global_State关联起来的一个结构体变量,不过很明显5.4之后lua底层直接把这个global_State暴露出来了,不过变量名还没有改(还是l_G)。

  我们不是在讲lua_State吗?为啥又来一个global_State呢?

  简而言之lua_state是是暴露给用户的数据类型(线程)用户通过它来调用C_API,global_State维护全局字符串表、内存管理函数、gc等信息。

关于具体的global_State我们可以看一下我写的这篇博客:【Lua进阶系列】全局状态机global_State

  两者大体上的区别如下:

执行状态机 -- lua_state(暴露给用户调用)
        lua_state 是暴露给用户的数据类型,既表示一个 lua 程序的执行状态,也指代 lua 的一个线程(在官方文档中)。
        每个线程拥有独立的数据栈以及函数调用栈,还有独立的调试钩子和错误处理设置。
        lua_state是一个lua 线程的执行状态。所有的lua C API 都是围绕这个状态机:
        lua_State是围绕程序如何执行来设计的,数据栈和调用栈都在其中。

全局状态机 -- 同一虚拟机中的所有执行线程(实际的虚拟机,一个全局状态机的数据多个lua_Stack共享)
        global_state  里面有对主线程的引用,有注册表管理所有全局数据,有全局字符串表
        有内存管理函数,有GC 需要的把所有对象串联起来的相关信息,以及一切 lua 在工作时需要的工作内存。
        通过 lua_newstate 创建一个新的 lua 虚拟机时,第一块申请的内存将用来保存主线程和这个全局状态机。

结构示意图:

 

 (5) StkId -- 数据栈

StkId top;  /* first free slot in the stack */
StkId stack_last;  /* last free slot in the stack */
StkId stack;  /* stack base */

前文提及在ua和C/C++是通过这个lua_State进行交互的,而lua_State就是利用StkId这个数据栈对数据进行暂存的。

下面我们看看这个StkId的代码定义:

我们可以看出,StkId其实是TValue的数组,那么TValue又是什么结构呢?

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;


#define TValuefields	Value value_; int tt_


typedef struct lua_TValue {
  TValuefields;
} TValue;

通过上述代码关系我们可以看出,实际存储数据的数据结构是Value,而TValue是为了区分联合中存放的数据类型(使用tt字段),再额外绑定一个类型字段。

lua 中的数据可以这样分为两类:值类型和引用类型。值类型可以被任意复制,而引用类型共享一份数据。

Value存放了gc和几个属性值,属性值分别对应了lua的值类型数据,而gc则管理lua中的引用数据的生命周期。

    从下面的图可以的得出如下结论:

        1. lua中, number, boolean, nil, light userdata四种类型的值是直接存在栈上元素里的, 和垃圾回收无关.

        2. lua中, string, table, closure, userdata, thread存在栈上元素里的只是指针, 他们都会在生命周期结束后被垃圾回收.

        3.lua_state 的数据栈,就是一个 TValue 的数组。代码中用 StkId 类型来指代对 TValue 的引用。

 

 (6) CallInfo  -- 调用栈

CallInfo base_ci;  /* CallInfo for first level (C calling Lua) */
CallInfo *ci;  /* call info for current function */
  • 主要由一个CallInfo的结构组成。CallInfo是一个双向链表结构。通过双向链表结构来管理每一个Lua的函数调用栈信息。
  • Lua一共有三种类型的函数:C语言闭包函数(例如pmain)、Lua的C函数库(例如str字符串函数)和LUA语言
  • 每一个函数的调用,都会新生产一个CallInfo的调用栈结构,用于管理函数调用的栈指针信息。当一个函数调用结束后,会返回CallInfo链表的前一个调用栈,直到所有的调用栈结束回到L->base_ci
  • 调用栈最终都会指向数据栈上,通过一个个调用栈,用于管理不同的函数调用。
typedef struct CallInfo {
  StkId func;  /* ci->func:指向正在调用操作的栈底位置。 function index in the stack */
  StkId	top;  /* 指向调用栈的栈顶部分 top for this function */
  struct CallInfo *previous, *next; /* previous和next是双向链表指针,用于连接各个调用栈。当执行完一个函数,通过previous回滚到上一个调用栈
	CI 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;

这里可以举一个栗子,就是函数A调用函数B,函数b也调用函数C,那么此时base_ci的next就是函数A的callinfo,ci就是函数c的callinfo

实际上,遍历 L 中的 base_ci域指向的 CallInfo双向链表可以获得完整的 lua 调用栈。而每一级的 CallInfo 中,都可以进一步的通过 func 域取得所在函数的更详细信息。
当 func 为一个 lua 函数时,根据它的函数原型可以获得源文件名、行号等诸多调试信息。

 (7) HOOK 相关-- 服务于debug模块

  int basehookcount;
  int hookcount;
  volatile lua_Hook hook;
  l_signalT hookmask;
  lu_byte allowhook;

  volatile lua_Hook hook 存放了debug调用的钩子函数

struct lua_Debug {
  int event;
  const char *name;	/* (n) */
  const char *namewhat;	/* (n) 'global', 'local', 'field', 'method' */
  const char *what;	/* (S) 'Lua', 'C', 'main', 'tail' */
  const char *source;	/* (S) */
  int currentline;	/* (l) */
  int linedefined;	/* (S) */
  int lastlinedefined;	/* (S) */
  unsigned char nups;	/* (u) number of upvalues */
  unsigned char nparams;/* (u) number of parameters */
  char isvararg;        /* (u) */
  char istailcall;	/* (t) */
  char short_src[LUA_IDSIZE]; /* (S) */
  /* private part */
  struct CallInfo *i_ci;  /* active function */
};

关于debug库的一些介绍可以看一下我的这篇博客:【Lua进阶系列】之Debug库

 

 (8) GC 垃圾回收

GCObject *gclist;

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Lampard杰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值