【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;
【Lua进阶系列】lua_Stack相关推荐
- 【Lua进阶系列】实例lua调用capi
[Lua进阶系列]实例lua调用capi 大家好,我是Lampard~~ 欢迎来到Lua进阶系列的博客 首先祝大家2021新年 ...
- 【Lua进阶系列】lua元方法
[Lua进阶系列]之Lua元方法案例+字段 大家好,我是Lampard~~ 欢迎来到Lua进阶系列的博客 前文再续,书接上一回.今天和大家讲解一 ...
- malloc开辟的空间在哪一个区间_C++进阶系列之STL(2)SGI版本空间配置器
1.STL中的空间配置器在STL中,空间配置器分了2组,分别为一级空间配置器和二级空间配置器,但是它们都有自己各自运用的场合:一般说来,一级空间配置器一般分配的空间大于128B,二级空间配置器的分配空 ...
- C#进阶系列——WebApi 身份认证解决方案:Basic基础认证
阅读目录 一.为什么需要身份认证 二.Basic基础认证的原理解析 1.常见的认证方式 2.Basic基础认证原理 三.Basic基础认证的代码示例 1.登录过程 2./Home/Index主界面 3 ...
- C#进阶系列——WebApi 接口参数不再困惑:传参详解
看这边文章时的疑惑是:WebApi中的参数加了[FromBody],不知所以然,就百度了下,看到了以下文章,和大家分享下: 原文链接:http://www.cnblogs.com/landeanfen ...
- C#进阶系列——WebApi 跨域问题解决方案:CORS
C#进阶系列--WebApi 跨域问题解决方案:CORS 参考文章: (1)C#进阶系列--WebApi 跨域问题解决方案:CORS (2)https://www.cnblogs.com/landea ...
- C#进阶系列——DDD领域驱动设计初探(五):AutoMapper使用
前言:前篇搭建了下WCF的代码,就提到了DTO的概念,对于为什么要有这么一个DTO的对象,上章可能对于这点不太详尽,在此不厌其烦再来提提它的作用: 从安全上面考虑,领域Model都带有领域业务,让Cl ...
- JavaScript进阶系列01,函数的声明,函数参数,函数闭包
本篇主要体验JavaScript函数的声明.函数参数以及函数闭包. □ 函数的声明 ※ 声明全局函数 通常这样声明函数: function doSth() { alert("可以在任何时候调 ...
- HTML5 进阶系列:indexedDB 数据库
前言 在 HTML5 的本地存储中,有一种叫 indexedDB 的数据库,该数据库是一种存储在客户端本地的 NoSQL 数据库,它可以存储大量的数据.从上篇:HTML5 进阶系列:web Stora ...
最新文章
- maven 一个模块生成多个jar包
- 第一百三十四期:MySQL分页查询方法及优化
- 阶段3 2.Spring_06.Spring的新注解_3 AnnotationConfigApplicationContext的使用
- Oracle pmon是什么,oracle 11g pmon工作内容系列二
- 一万八的M1 iPad Pro ,怎么就成了“期货”
- Java项目校园兼职平台(含代码)
- 凸包算法Graham扫描法
- 4G浏览器 随机遇而生
- Linux: Top命令查询结果参数详解
- python坑爹的黑店_曝光米兰爱马仕黑店极其坑爹配货经验!大家不要再白白上当了!...
- 城市智商的提出,基于互联网云脑的智慧城市发展水平评测研究
- 2.1 安装 go-gtk
- 快速梳理23种常用的设计模式
- C++语言——求圆柱表面积
- web框架详解之 tornado 四 模板引擎、session、验证码、xss
- Jmeter批量导入数据联想
- 如何从微信跳到外部浏览器进行apk文件(app)下载如何解决
- 阿尔法围棋击败人类是计算机在那方面的应用,三问人机战! 阿尔法围棋攻陷人类智慧最后堡垒?...
- 微信 10 年,张小龙的 7 个逻辑和一个选择
- c语言如何让窗口无法关闭,无法关闭窗口的程序