一. 前言
无论是和静态语言交互,还是内部的API,永远少不了lua_State
,这就是Lua最核心的栈结构体,也是本文的重点介绍对象。由于该结构体涉及到了众多功能,因此这里不会详细到每一个变量的作用都展开说明,而是站在脚本语言设计和开发者的角度看,设计一个栈结构体,需要哪些部分,留下一个比较系统性的框架概念即可。
二. 核心结构体分析
写过Lua的demo程序,都不会陌生这个API
1 | lua_State* luaL_newstate() |
在使用Lua时,首先需要调用该API生成栈,之后再加载文件或是直接填写逻辑。如果说Lua虚拟机执行指令模拟的是CPU的运作,那么Lua栈模拟的就是可执行文件加载在内存的堆栈的操作。在Lua内部参数的传递是通过Lua栈,Lua与C等外部进行交互的时候也是使用的栈。lua_State
中保存了大量信息,直接看结构体源码可能就会陷进去了,所以我们可以先分个类。
设想如果是自己写一个保存脚本执行信息的栈结构体,那可能需要以下几方面内容
- 全局存储信息,起到类似全局变量之类的效果
- 栈信息,毫无疑问需要上下限索引、计数等
- 函数相关,用链表或者其他形式去把所有函数存储起来并且能够获取到
- GC相关,用于垃圾回收
- 函数跟踪调试,给出对外的接口以便于调试代码以及打印情况
- 异常抛出处理,用于处理异常情况
而Lua的栈结构体基本也就包括了这几块。首先看看整体的源码,对照刚刚所说的应该就能看懂个大概了,下面会详细就各个方面进行更详细的说明。
1 | // Lua 5.1,本文均为此版本,后续不在标注 |
2.1 全局信息
全局信息成员变量如下,TValue
在数值一讲中详谈。
1 | struct lua_State { |
对应结构体global_State
主要包含几方面的全局内容
- 字符串的哈希表(下一讲中会详细说明)
- 原表、上值等
- 内存分配函数
- GC相关的众多链表及相应变量(后续在GC一文详细展开说明)
- 全局统计信息:总内存、正在使用的内存估算
源码如下,可以对照着过一遍,有个大致印象就好,后面会用到的地方会具体说明。
1 | /* |
2.2 栈信息
既然是Lua栈,那么自然需要保存栈底和栈顶,以及入栈内容的相关信息,相关源码如下。这里注释都很清楚,没有什么难懂的地方,就是通过几个索引位置让各种栈操作可以方便的执行。
1 | typedef TValue *StkId; /* index to stack elements */ |
2.3 函数
多个函数的入栈和调用等操作当然都需要保存在栈内,所以需要函数相关的一系列成员变量
1 | struct lua_State { |
lua_State
中的CallInfo
保存当前调用函数的信息,也提供了一系列指针方便索引栈、函数顶、其他函数。定义如下:
1 | /* |
lua_State
和CallInfo
初始化主要在stack_init()
函数中执行。
1 | static void stack_init(lua_State *L1, lua_State *L) |
2.4 GC
GC的主要相关变量在global_State
中定义,但是除此之外仍有两个特殊变量
1 | struct lua_State { |
这里之所以会存在一个gclist
,是因为lua_State
本身也是一个可以GC的对象,为了方便GC统一处理因此做了这种统一化的存在,而不适合放在global_State
之中。openupval
的话,是因为专属于这个栈,因此必须放在这儿。关于上值,在后续基础数据类型中会有详细说明。
2.5 Hook
对于脚本语言,我们也希望能够提供一种跟踪、调试的方法,对于Lua来说,其实现就是Hook系列。该部分内容比较有趣,后续会单独展开详谈。
1 | struct lua_State { |
三. 总结
本文对lua_State进行了概要介绍,希望是有一个提纲式的了解,掌握栈的基础构造,细节内容后面慢慢填充即可。
参考文献
《A No Frills Introduction to Lua 5.1 VM Instructions》
《The Implementation of Lua 5.0》