lua 源码阅读(六)

2018-01-19 10:12

lua 源码系列文章:

lua源码欣赏的第六章内容,与 lua 协程相关

Contents

Lua对象#

从C层面来看,Lua的状态就是一个 lua_State,而在同一个Lua虚拟机中,多个 lua_State 共享一个 global_State。其中 lua_State 不应当被看为一个简单的静态数据结构,而是一个lua “线程” 中的状态机,其中保存着当前"线程"的执行状态、数据栈、调用栈等信息。

Lua 数据栈#

Lua 的数据可以被分为值类型和引用类型。在 lstate.h 中使用联合体 Value 来表示。

1
2
3
4
5
6
7
union Value {
GCObject *gc; /* collectable objects */
void *p; /* light userdata */
int b; /* booleans */
lua_CFunction f; /* light C functions */
numfield /* numbers */
};

可以看出引用类型使用一个指针 GCObject 来间接引用,其他值类型都是直接保存在联合体中。 为了区分联合体中的类型,还需绑定一个额外的类型字段。

在Lua中,数据栈的空间为 2 * LUA_MINSTACK,而在 Lua 的 C 调用中,数据栈大小只有 LUA_MINSTACK这么大。(LUA_MINSTACK默认为 20)

所以在Lua的C调用时,需要显示的扩展栈大小,使用 ldo.c 中的 luaD_growstack 函数来扩展数据栈大小,其中每次调用至少扩展一倍的大小。在扩展数据栈时,值类型的数据可以直接复制,而引用类型的数据需要调用 correctstack 来修正

调用栈#

Lua 中的调用栈在一个 CallInfo 结构体中,以双向链表的形式存储在"线程"对象里。

1
# define next_ci (L) (L->ci = (L->ci -> next ? L->ci -> next : luaE_extendCI (L)))

可以看到,在Lua 5.2实现中,调用栈被封装成一个可以无限扩展的堆栈,而仅在GC时清理掉无用的链表节点。

线程执行与中断#

Lua 作为一门嵌入式语言,为了实现不与硬件绑定的协程,在中断和异常处理时统一使用 C 的 longjmp 机制,而当嵌入 C++时,则使用 try / catch 机制来实现。这些是通过宏来切换的。

函数调用#

Lua 中的 pcall 是用函数而非语言机制实现的,实现pcall 是在 c 层面的堆栈来保存和恢复状态。

在纯 Lua 函数调用中,一般不涉及C函数的调用。其过程为:

是生成新的CallInfo,修正数据栈,然后把字节码的执行位置跳转到被调用的函数开头。而Lua的return操作则做了相反的操作,恢复数据栈,弹出CallInfo,修改字节码的执行位置,恢复到原有的执行序列上

在 Lua 底层API中,分为 luaD_precallluaD_poscall,原因即是:

  • 在Lua调用时,需要先执行luaD_precall 来指定字节码的执行位置,而在 luaD_poscall 时执行字节码,并恢复状态。
  • 在 C 调用时,不需要执行字节码恢复状态,只需执行 luaD_precall 即可。

C 技巧#

在 TValue 中采用了 NaNTrick 的技巧来节省内存(Lua 5.2特性),即:根据 IEEE754 中的说明,指数位全为 1 尾数位全为 0 (0xfff8000000000000) 用来表示这并不是一个数字。它用来表示无穷大,以及数字除0的结果。而当双精度浮点数大于 0xfff8000000000000 情况下,则可以认为这是刻意造出的数字,可用于特殊用途。
NaNTrick则利用这种情况,在32位机器上,将一个双精度浮点数的尾数位(52位)用于储存除数字类型外的其他类型(32位)和值类型(int)足够使用。这样每个值可以节省4个字节的内存。

Reference#

  1. IEEE_754
  2. float 采用IEEE R32.24, double 采用 IEEE R64.53


float


double

https://github.com/xiaocang/lua-5.2.2_with_comments/releases/tag/data_n_call_stack


标签:lua

点击加载Disqus评论
Creative Commons © 2013 — 2018 xiao_cang | Theme based on fzheng.me & NexT | Hosted by Netlify