[toc]
# Lua 虚拟机到执行代码流程
如上图所示步骤如下:
-
- lua_open->lua_newState 创建一个
luaState虚拟机
,在这个过程中会触发内存分配
- lua_open->lua_newState 创建一个
-
- lua_openlibs 这个过程会注入所有的 lib
-
- lua_dofile 会执行对应的 lua 源码,do_file 时会先进行
luaL_loadfile
, 然后进入lua_pcall
执行对应的代码
- lua_dofile 会执行对应的 lua 源码,do_file 时会先进行
-
- 最终会进入
f_parser
, 通过这个函数开始词法 (lexer) 和语义 (parser) 分析
- 最终会进入
-
- 先进入 luaX_next (词法分析), 获取到 token
-
- 词法分析结束后进入 chunk (EBNF 语义分析), 生成字节码,返回
Proto
对象回f_parser
- 词法分析结束后进入 chunk (EBNF 语义分析), 生成字节码,返回
-
- 这些数据包裹到
Closure
上,处理结束后,进入第3
步里的lua_pcall
, 准备执行字节码
- 这些数据包裹到
-
- 选择函数的过程需要了解下 lua 中的闭包,(等下讲), 这里说下,lua 中每个闭包语义分析结束后都放到了栈中,所以我们可以通过闭包找到
function
的位置,即下一个待执行块的函数指针(即前面放入状态机的Closure)
- 选择函数的过程需要了解下 lua 中的闭包,(等下讲), 这里说下,lua 中每个闭包语义分析结束后都放到了栈中,所以我们可以通过闭包找到
-
- 继续往下执行,最终会进入
luaD_call
函数,luaD_call 进入luaD_precall
进行函数执行前的准备工作
- 9.1 从 lua_State 的 CallInfo 数组中得到一个新的
CallInfo
结构体,设置它的func、base、top
指针 - 9.2 从
Closure
指针中取出保存下来的Proto
结构体,这个结构体保存着分析过程完成之后生成的字节码等信息。 - 9.3 Proto 的 Code 里存放的就是字节码,然后把上面的
CallInfo
里的 top 和 base 赋值给 lua_State 结构体的top
和base
- 继续往下执行,最终会进入
-
- 接着会进入
luaV_execute
,pc 指针存放的是虚拟机的OpCode
- 接着会进入
-
- 大循环
luaV_execute
结束后,回到luaD_poscall
函数恢复到上一次函数调用的环境
- 大循环
简要的流程如下:
Proto 是语法 / 词法分析阶段和执行指令的
上下文(数据传输结构体)
接下来介绍 Lua 中的几个关键组件
# 数据结构与栈
Lua 虚拟机相关的数据结构与栈 lua_State 中的 TValue env;
就是对应虚拟机的模拟栈数组,然后虚拟机主要包含以下几个相关成员
- stack : 栈数组的起始位置
- base: 当前函数栈的基地址 - top: 当前函数栈下一个可用位置 其中执行函数与栈的关系如下: 由上图可以看出,每个函数执行 Callinfo
都维护了 func 指针和 top 以及 base 指针,top 和 base 维护的是 函数的参数
Tips: 栈是从下往上扩展的 注意:
Lua 栈大小是有限的,CallInfo 也是有数量限制的
# 指令的解析
接下来分析指令是如何生成的
我们知道 Lua 里函数可以嵌套函数,我们称这个为 闭包
Lua 中使用 FuncState暂存这些信息
,然后通过这些信息来生成 OpCode
1 | --最外层FuncState fs1 |
闭包: 即把每个函数当做一个结构 上面的函数的依赖关系如下: FuncState 和 Proto 之间的依赖关系如上图所示
# 指令格式
Lua 虚拟机的指令格式如下图上所示 Lua 中解析指令是从低位到高位解析的 首先是解析 OpCode
, 紧接着跟上三个操作数, A,B,C
指令列表在 Lua设计与实现这本书的P48