加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 大数据 > 正文

Lua源代码阅读(五)数据栈与调用栈组成的 线程(协程)

发布时间:2020-12-14 22:03:09 所属栏目:大数据 来源:网络整理
导读:1: ? ? ? 若 lua 仅作为一种独立语言,支持协程可能并不算麻烦。可困难在于 lua 生来以一门嵌入式语言存 在,天生需要大量与宿主系统 C 语言做交互。 ?? ?2: ? ? ? ?典型的应用环境是由 C 语言开发的系统,嵌入 lua 解析器,加载 lua 脚本运行。同时注入一些 C

1: ? ? ? 若 lua 仅作为一种独立语言,支持协程可能并不算麻烦。可困难在于 lua 生来以一门嵌入式语言存 在,天生需要大量与宿主系统 C 语言做交互。
??
?2: ? ? ? ?典型的应用环境是由 C 语言开发的系统,嵌入 lua 解析器,加载 lua 脚本运行。同时注入一些 C 函数供 lua 脚本调用。lua 作为控制脚本,并不直接控制外界的模块,做此桥梁的正是那些注入的 C 接口。在较为复杂的应用环境中,这些注入的 C 函数还需要有一些回调方法。 当我们企图用 lua 脚本去定制这些回调行为时,就出现了 C 函数调用 lua 函数,lua 函数再调用 C 函数,这个 C 函数又调用 lua 函数的层层嵌套的过程。

3: ? C 语言本身并不支持协程或延续点,一旦中断 lua 协程,就面临 C 语言中调用栈如何处理的难题。


4: ? ?直接操作 C 层面的堆栈,可以较为容易的作到协程的切换。但这样做,会和硬件平台绑定。这是一个在 C 中实现延续点的不错的方法。但这个做法不符合 lua的设计原则。lua为了解决这个问题,对 lua语言的 实现以及和 C 交互的接口设计上做了大量的努力。最终使用标准的 C 语言,实现了完整功能的 lua协程。

5: ??刚接触 lua时,从 C 层面看待 lua,lua的虚拟机对象就是一个 lua_State 。但实际上,真正的 lua虚拟机对象被隐藏起来了。那就是lstate.h中定义的结构体 global_State 。
lua_State 是暴露给用户的数据类型。从名字上看,它想表示一个 lua程序的执行状态,在官方文档中,它指代 lua的一个线程。每个线程拥有独立的数据栈以及函数调用链,还有独立的调试钩子和错误处理设 施。所以我们不应当简单的把 lua_State 看成一个静态的数据集,它是一组 lua程序的执行状态机。所有的 luaC API 都是围绕这个状态机,改变其状态的:或把数据压入堆栈,或取出,或执行栈顶的函数,或继续 上次被中断的执行过程。
同一 lua虚拟机中的所有执行线程,共享了一块全局数据 global_State 。在 lua的实现代码中,需要访 问这个结构体的时候,会调用宏


? ? ? ? ??

? ??

? 略lstate.h及GC?,眼 lua_State?。?



/* Lua数据栈的初始化?

?1:一开始,数据栈的空间很有限,只有 2倍的LUA_MINSTACK(默认是20)的大小。

?2:LuaC使用的栈相关 API都是不检查数据栈越界的,这是因为通常我们编写C扩展都能把数据栈

? ? 空间的使用控制在LUA_MINSTACK以内,或是显式扩展。

?3: 对每次数据栈访问都强制做越界检查是非常低效的。

?4:数据栈不够用的时候,可以扩展。这种扩展是用realloc实现的,每次至少分配比原来大一倍的空间,

?? 并把旧的数据复制到新空间。

?*/


/* 数据栈的空间扩展

?1:数据栈扩展的过程,伴随着数据拷贝。这些数据都是可以直接值复制的,

? ? 所以不需要在扩展之后修正其中的指针。

?2:,有些外部结构对数据栈的引用需要修正为正确的新地址。这些需要修正的位置包括

?? ? upvalue以及执行栈对数据栈的引用.

?3:这个过程由correctstack函数实现

?*/


? Lua调用栈


/*Lua调用栈

?1:Lua把调用栈和数据栈分开保存。

?2:调用栈放在一个叫做CallInfo的结构中,以双向链表的形式储存在线程对象里。

?3:CallInfo 保存着正在调用的函数的运行状态;状态标示存放在lu_byte callstatus中。

?4:部分数据和函数的类型有关,以联合形式存放

?5:C 函数与 Lua函数的结构不完全相同

?6:callstatus中保存了一位标志用来区分是C函数还是Lua函数

?7:正在调用的函数一定存在于数据栈上,CallInfo结构中,func指向正在执行的函数在数据栈上的位置

?? 需要记录这个信息,是因为如果当前是一个Lua函数,且传入的参数个数不定的时候,需要用这个位置和当

?? 前数据栈底的位置相减,获得不定参数的准确数量

?8:同时,func还可以帮助我们调试嵌入式Lua代码:在用 GDB这样的调试器调试代码时,可以方便的查看C

?? 的调用栈信息,但一旦嵌入Lua,我们很难理解运行过程中的Lua代码的调用栈;不理解Lua的内部结构,

? ? 就可能面对一个简单的lua_State变量束手无策.

?9:实际上,遍历L中的Ci域指向的CallInfo链表可以获得完整的Lua调用链;

? 而每一级的CallInfo,都可以进一步的通过 func域取得所在函数的更详细信息。

? func为一个Lua函数时,根据它的函数原型,可以获得源文件名、行号等诸多调试信息。

?10:CallInfo是一个标准的双向链表结构,不直接被GC模块管理

?11:这个双向链表表达的是一个逻辑上的栈, 在运行过程中,并不是每次调入更深层次的函数,

? ? 就立刻构造出一个CallInfo节点。

?12:整个CallInfo链表会在运行中被反复复用。直到GC的时候才清理那些比当前调用层次更深的无用节点。

?? lstate.c中有luaE_extendCI的实现

?13:也就是说,调用者只需要把CallInfo链表当成一个无限长的堆栈使用即可

?14:当调用层次返回,之前分配的节点可以被后续调用行为复用。

?15:GC的时候只需要调用luaE_freeCI就可以释放过长的链表。

*/




线程

/*? ? ? 线程

?1:把数据栈和调用栈合起来就构成了Lua中的线程。

?2:在同一个Lua虚拟机中的不同线程因为共享了global_State而很难做到真正意义上的并发。

?3:它也绝非操作系统意义上的线程,但在行为上很相似。用户可以resume一个线程,

? ? 线程可以被yield打断。

?4:Lua的执行过程就是围绕线程进行的。

?5:我们从lua_newthread阅读起,可以更好的理解它的数据结构。

?6:这里我们能发现,内存中的线程结构并非lua_State,而是一个叫LX的东西。

?*/


/*LX的定义?

?1:lua_State之前留出了大小为LUAI_EXTRASPACE字节的空间。

?2:面对外部用户操作的指针是L而不是LX,L所占据的内存块的前面却是有所保留的。

?3:这是一个有趣的技巧。用户可以在拿到L指针后向前移动指针,取得一些LUAI_EXTRASPACE中额外的数据。

?4:把这些数据放在前面而不是lua_State结构的后面避免了向用户暴露结构的大小。

?5:这里,LUAI_EXTRASPACE是通过编译配置的,默认为0;

?6:开启LUAI_EXTRASPACE,需要一系列的宏提供支持(luai_userstateopen(L)。。。。)

?7:L附加一些用户自定义信息在追求性能的环境很有意义。可以在为Lua编写的C模块中,

?? 直接偏移L指针来获取一些附加信息。这比去读取L中的注册表要高效的多。

?8:另一方面,在多线程环境下,访问注册表本身会改变L的状态,是线程不安全的。

?*/

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读