`

Lua5.1代码阅读(八):ldo.h/ldo.c

 
阅读更多

 

Lua5.1代码阅读(八):ldo.h/ldo.c

 

(未完成,待修改)

一、概览

ldo.h/ldo.c描述Lua的堆栈和调用的结构。

提供对调用、协程、异常等复杂控制流的支持。

模块中对外公开的API主要分为以下几类:

(1) 错误恢复:

luaD_seterrorobj,luaD_throw,luaD_rawrunprotected,luaD_pcall

(2) 堆栈操纵:

luaD_reallocCI

luaD_reallocstack,luaD_growstack,luaD_checkstack,

incr_top,savestack,restorestack

(3) 函数调用:

luaD_callhook,luaD_precall,luaD_poscall,luaD_call

(4) 协程控制:

lua_resume,lua_yield

(5) 代码加载:

luaD_protectedparser

因为ldo模块的luaD_call与lvm模块的luaV_execute是相互引用的,所以ldo和lvm可以看成是相互耦合的同一模块,共同构成lua的VM。

参考资料:

1. 官方代码参考src/ldo.c

http://www.lua.org/source/5.1/ldo.h.html

http://www.lua.org/source/5.1/ldo.c.html

2. Luaソースコード勉強会 (3)

http://d.hatena.ne.jp/hzkr/20080428

3. luaのお勉強[6]

http://d.hatena.ne.jp/jmk/20060428/p2

4. yield不能在c里调用  

http://dheartf.blog.163.com/blog/static/38505465200762611034354/

5. Lua Source, Module Structure

http://lua-users.org/wiki/LuaSource

6. LUA源码分析八:小总结,完整分析dofile的过程和堆栈

http://lin-style.iteye.com/blog/1020290

7. Lua源码分析

http://wenku.baidu.com/view/d09e2f91dd88d0d233d46a7f.html

8. Lua 5.1.4: ldo.c

http://stevedonovan.github.com/lua-5.1.4/ldo.c.html

模块内部函数的相互调用,与模块外部对模块内函数的调用图如下:(红色圆圈为ldo模块内的函数)

 


 

注意:图中还有一些重要的关系没有表示出来:

(1) luaD_call->luaV_execute(在lvm.c中)->luaD_call

这个间接关系是由于ldo与lvm相互耦合导致的。

(2) lua_cpcall或lua_pcall(在lapi.c中)->luaD_pcall->luaD_rawrunprotected->f_Ccall或f_call(在lapi.c中)->luaD_call

这个间接关系是由于f_Ccall或f_call是作为luaD_pcall的一个函数指针参数传入而导致的。

二、头文件

#include "lobject.h"

#include "lstate.h"

#include "lzio.h"

#include <setjmp.h>

#include <stdlib.h>

#include <string.h>

#include "lua.h"

#include "ldebug.h"

#include "ldo.h"

#include "lfunc.h"

#include "lgc.h"

#include "lmem.h"

#include "lobject.h"

#include "lopcodes.h"

#include "lparser.h"

#include "lstate.h"

#include "lstring.h"

#include "ltable.h"

#include "ltm.h"

#include "lundump.h"

#include "lvm.h"

#include "lzio.h"

 

三、公共或私有的宏定义

1. #define luaD_checkstack(L,n) \

 if ((char *)L->stack_last - (char *)L->top <= (n)*(int)sizeof(TValue)) \

luaD_growstack(L, n); \

 else condhardstacktests(luaD_reallocstack(L, L->stacksize - EXTRA_STACK - 1));

如果堆栈容量不足加n,则确保堆栈容量足够加n(必要时增长容量)。否则保持(检查?)堆栈容量为L->stacksize - EXTRA_STACK - 1大小。

condhardstacktests是定义在llimit.h中,是一个默认不执行任何操作的宏(因为HARDSTACKTESTS未定义)。

#ifndef HARDSTACKTESTS

#define condhardstacktests(x) ((void)0)

#else

#define condhardstacktests(x) x

#endif

2. #define incr_top(L) {luaD_checkstack(L,1); L->top++;}

先确保堆栈容量足够加1,然后让L->top加1。

3. #define savestack(L,p) ((char *)(p) - (char *)L->stack)

保存p相对于栈底L->stack的字节数偏移(p指针为StkId类型,即TValue *型),

返回值保存为函数的局部变量,

再执行一些操作(修改了L->stack),

最后把局部变量传给restorestack作为参数以还原p指针。

4. #define restorestack(L,n) ((TValue *)((char *)L->stack + (n)))

通过返回值还原堆栈指针(见savestack)

5. #define saveci(L,p) ((char *)(p) - (char *)L->base_ci)

保存p(CallInfo *型)相对于L->base_ci的偏移到局部变量(类似savestack)

在luaD_pcall中用于维持旧的L->ci指针值不被破坏。

6. #define restoreci(L,n) ((CallInfo *)((char *)L->base_ci + (n)))

通过返回值还原CI指针(类似restorestack)

7. #define PCRLUA 0 /* initiated a call to a Lua function */

luaD_precall返回值,表示初始化一个对Lua函数的调用

8. #define PCRC 1 /* did a call to a C function */

luaD_precall返回值,执行了对C函数的调用

9. #define PCRYIELD 2 /* C funtion yielded */

luaD_precall返回值,C函数已经挂起

10. #define ldo_c

象征性质,表示ldo.c文件被编译

11. #define LUA_CORE

象征性质,表示此模块为内核

12. #define inc_ci(L) \

 ((L->ci == L->end_ci) ? growCI(L) : \

  (condhardstacktests(luaD_reallocCI(L, L->size_ci)), ++L->ci))

如果L->ci == L->end_ci,确保CI数组容量足够加1,否则保持(检查?)CI数组容量为L->size_ci(实际上为无操作,因为HARDSTACKTESTS未定义,同luaD_checkstack中的condhardstacktests)。

最后让CI数组大小加1(作用类似于incr_top,不过是针对CI数组的)。

 

四、类型

1. typedef void (*Pfunc) (lua_State *L, void *ud);

luaD_pcall与luaD_rawrunprotected的函数指针参数类型,用于这种场合:

 LUAI_TRY(L, &lj,

(*f)(L, ud);

 );

定义这个类型的目的是,可以集中执行:

struct lua_longjmp lj;

lj.status = 0;

lj.previous = L->errorJmp;

L->errorJmp = &lj;

...

L->errorJmp = lj.previous;

return lj.status;

而结构体lua_longjmp只需要定义为ldo.c的私有结构体即可,ldo.c模块外的函数不需要知道它。

满足Pfunc类型的有如下函数:

(1) 作为luaD_pcall的参数:lapi.c的f_call,f_Ccall,f_parser

(2) 作为luaD_rawrunprotected的参数:ldo.c的resume,lapi.c的f_call,f_Ccall,f_parser(luaD_pcall内部也调用luaD_rawrunprotected),lstate.c的f_luaopen,callallgcTM。

2. struct lua_longjmp {

 struct lua_longjmp *previous;

 luai_jmpbuf b;

 volatile int status;  /* 错误码 */

};

一个类似链表头的结构体,作为luaD_rawrunprotected的局部变量,把执行f前的L->errorJmp暂时保存以下,在退出luaD_rawrunprotected后还原回L->errorJmp。

根据luaD_rawrunprotected的注释,它的作用是链接和还原新旧的错误处理链,把局部变量lj插入到L->errorJmp链表的第一个位置(或可理解L->errorJmp指向堆栈的栈顶)。

lstate.h:

struct lua_State {

...

struct lua_longjmp *errorJmp;  /* 当前错误恢复点 */

...

};

另外,因为调用了LUAI_TRY

ldo.c

struct lua_longjmp lj;

...

LUAI_TRY(L, &lj,

(*f)(L, ud);

);

...

return lj.status;

lua_longjmp的b域专门用于C的异常跳转机制,而status域用于抛出异常时记录错误码(不一定在luaD_rawrunprotected中记录,也可能在luaD_throw中记录)以作为luaD_rawrunprotected的返回值(它们都可能需要在LUAI_TRY中使用):

(1) status域用于确保LUAI_TRY的try被throw触发时(C++风格异常),总是被赋值为非0值(至少为-1),因为C++异常不像C跳转那样,可能不是显式抛出的:

luaconf.h

#if defined(__cplusplus)

...

#define LUAI_TRY(L,c,a) try { a } catch(...) \

{ if ((c)->status == 0) (c)->status = -1; }

这并不意味着luaD_rawrunprotected返回status值总是0和-1,因为其它函数也可能通过L->errorJmp修改到status域。

(2) b域用于判断LUAI_TRY的_setjmp和setjmp是否被longjmp触发(C风格异常)(即(c)->b)

luaconf.h

#elif defined(LUA_USE_ULONGJMP)

...

#define LUAI_TRY(L,c,a) if (_setjmp((c)->b) == 0) { a }

...

#else

...

#define LUAI_TRY(L,c,a) if (setjmp((c)->b) == 0) { a }

3. struct SParser {  /* f_parser的数据 */

 ZIO *z;

 Mbuffer buff;  /* 被扫描器使用的缓冲 */

 const char *name;

};

作为luaD_protectedparser的局部变量struct SParser p;

或作为f_parser的局部变量struct SParser *p = cast(struct SParser *, ud);

它的作用是把luaD_protectedparser传入的参数ZIO *z和const char *name保存起来以及创建Mbuffer buff。

最后,它的指针作为luaD_pcall的void *u传入,最后传递给f_parser的void *ud参数。

过程如下:

lua_load->luaD_protectedparser->luaD_pcall->luaD_rawrunprotected->f_parser

这个结构体是ldo私有的。

 

五、私有的静态函数

1. static void restore_stack_limit (lua_State *L) {

luaD_pcall和luaD_throw中使用。

(1) 检查L->stack_last - L->stack == L->stacksize - EXTRA_STACK - 1。

(2) 如果出现CI数组分配溢出情况(超过LUAI_MAXCALLS),尝试撤销它。

2. static void resetstack (lua_State *L, int status) {

在luaD_throw中使用,执行G(L)->panic(L);之前的L栈操作。

3. static void correctstack (lua_State *L, TValue *oldstack) {

luaD_reallocstack中使用。用于修正与堆栈有关的指针值。

根据oldstack的值(栈偏移)调整L->top,L->openupval数组(每个upvalue的v指针),L->base_ci数组(每个CI的top,base,func指针)和L->base

4. static CallInfo *growCI (lua_State *L) {

CI(调用信息)数组加一,CI容量以L->size_ci的2倍增长。如果超过LUAI_MAXCALLS(宏定义为20000),则抛出LUA_ERRERR异常。

5. static StkId adjust_varargs (lua_State *L, Proto *p, int actual) {

在luaD_precall中调用,用于变长参数函数的特殊处理:

(1) LUA_COMPAT_VARARG宏块,创建arg表(htab)

(2) 移动参数位置

6. static StkId tryfuncTM (lua_State *L, StkId func) {

在luaD_precall中执行,如果堆栈中func位置上的对象不是function型,

那么尝试取出其元表的__call元方法(必须为function型),插入到堆栈的func位置中。

7. static StkId callrethooks (lua_State *L, StkId firstResult) {

luaD_poscall中调用,执行调用返回钩子LUA_HOOKRET

8. static void resume (lua_State *L, void *ud) {

重新恢复挂起调用的执行。

没有考虑异常处理,异常处理由luaD_rawrunprotected完成(见lua_resume)。

9. static int resume_error (lua_State *L, const char *msg) {

执行resume失败,见lua_resume

有两种可能:

cannot resume non-suspended coroutine

无法恢复非挂起的协程

C stack overflow

C堆栈溢出

10. static void f_parser (lua_State *L, void *ud) {

执行luaU_undump或luaY_parser加载Lua代码

这是一个函数指针(带异常处理的回调),用于luaD_pcall(见luaD_protectedparser)

六、公开的导出函数

1. LUAI_FUNC void luaD_seterrorobj (lua_State *L, int errcode, StkId oldtop);

void luaD_seterrorobj (lua_State *L, int errcode, StkId oldtop) {

根据errcode的值设置错误对象(可能是MEMERRMSG即"not enough memory","error in error handling",或L->top - 1位置上的错误消息字符串),然后移动到oldtop位置上(?)。

参数errcode是异常的错误码,用于LUA_ERRMEM和LUA_ERRERR的特殊情况处理。

执行完luaD_seterrorobj后L栈还原的栈顶为oldtop+1(L->top = oldtop + 1;),即L栈顶为错误对象。

2. LUAI_FUNC void luaD_throw (lua_State *L, int errcode);

void luaD_throw (lua_State *L, int errcode) {

抛出异常,异常将被调用栈上最近的luaD_rawrunprotected捕获。

如果没有异常处理上下文(顶级调用?),执行resetstack和G(L)->panic(L)钩子后退出程序(exit(EXIT_FAILURE))

3. LUAI_FUNC int luaD_rawrunprotected (lua_State *L, Pfunc f, void *ud);

int luaD_rawrunprotected (lua_State *L, Pfunc f, void *ud) {

捕获异常,如果执行f时出现异常(可能是luaD_throw主动抛出的,也可能是隐式的),那么立刻跳转回此函数,恢复前一个异常处理上下文,并且返回异常错误码。

4. LUAI_FUNC void luaD_reallocstack (lua_State *L, int newsize);

void luaD_reallocstack (lua_State *L, int newsize) {

扩展L->stack和L->stacksize至新的大小newsize,

然后执行correctstack

5. LUAI_FUNC void luaD_reallocCI (lua_State *L, int newsize);

void luaD_reallocCI (lua_State *L, int newsize) {

扩展L->base_ci和L->size_ci至新的大小newsize,

然后调整L->ci和L->end_ci

6. LUAI_FUNC void luaD_growstack (lua_State *L, int n);

void luaD_growstack (lua_State *L, int n) {

luaD_reallocstack的封装,不过参数n是个增量最小值。而且如果小于L->stacksize,就增加1倍的L->stacksize,否则堆栈容量才加n。

7. LUAI_FUNC void luaD_callhook (lua_State *L, int event, int line);

void luaD_callhook (lua_State *L, int event, int line) {

调用钩子,第二参数event传入的可能值有:

lua.h:

#define LUA_HOOKCALL 0 //Lua函数开始调用时触发,见ldo.c的luaD_precall

#define LUA_HOOKRET    1 //Lua函数(包括尾调用)返回时触发,见ldo.c的callrethooks

#define LUA_HOOKLINE 2 //进入新的Lua代码行时触发,见lvm.c的traceexec

#define LUA_HOOKCOUNT 3 //debug.sethook的count参数不等于0时触发,见lvm.c的traceexec

#define LUA_HOOKTAILRET 4 //Lua函数尾调用返回时触发,见ldo.c的callrethooks

用于Debug API的debug.sethook(见《Lua参考手册》中debug.sethook的注释)

8. LUAI_FUNC int luaD_precall (lua_State *L, StkId func, int nresults);

int luaD_precall (lua_State *L, StkId func, int nresults) {

luaD_call、resume和luaV_execute的OP_CALL和OP_TAILCALL分支中执行,执行堆栈调整和钩子操作。

9. LUAI_FUNC int luaD_poscall (lua_State *L, StkId firstResult);

int luaD_poscall (lua_State *L, StkId firstResult) {

被luaD_precall和resume和luaV_execute的OP_RETURN分支调用。

执行调用返回后的堆栈恢复和钩子(callrethooks)操作。

10. LUAI_FUNC void luaD_call (lua_State *L, StkId func, int nResults); 

void luaD_call (lua_State *L, StkId func, int nResults) {

原文注释:

** Call a function (C or Lua). The function to be called is at *func.

** The arguments are on the stack, right after the function.

** When returns, all the results are on the stack, starting at the original

** function position.

翻译:

调用一个函数(C或Lua)。func上的函数被调用。

参数在堆栈上,正好在函数后面(上方)。

当返回时,所有结果(返回值)在堆栈上,从原来的函数位置开始。

注意,luaD_call没有考虑异常处理,异常处理工作由luaD_rawrunprotected完成(通过f_Ccall或f_call)

11. LUA_API int lua_resume (lua_State *L, int nargs) {

重新恢复之前挂起的调用。考虑异常处理。

12. LUA_API int lua_yield (lua_State *L, int nresults) {

挂起(暂停)Lua调用。

保护堆栈内容和标记状态为LUA_YIELD:

L->base = L->top - nresults;  /* protect stack slots below */

L->status = LUA_YIELD;

13. LUAI_FUNC int luaD_pcall (lua_State *L, Pfunc func, void *u, ptrdiff_t oldtop, ptrdiff_t ef); 

int luaD_pcall (lua_State *L, Pfunc func, void *u, ptrdiff_t old_top, ptrdiff_t ef) {

执行luaD_rawrunprotected,如果出错的话恢复执行luaD_rawrunprotected前的L栈信息。

它被lua_pcall和lua_cpcall调用。

    14. LUAI_FUNC int luaD_protectedparser (lua_State *L, ZIO *z, const char *name); 

int luaD_protectedparser (lua_State *L, ZIO *z, const char *name) {

在lua_load中调用,用于加载Lua代码(相当于编译器和加载器)

七、值得关注的函数

1. luaD_call/luaD_seterrorobj/luaD_throw/luaD_rawrunprotected/

f_Ccall/f_call/f_parser

lua_cpcall/lua_pcall/lua_load/luaD_pcall

观察错误保护和异常处理过程,包括:

(1) 涉及lapi.c的f_Ccall/f_call回调函数

(2) Lua/C调用执行次序:(在luaD_call中下断点)

lua_cpcall或lua_pcall->luaD_pcall->luaD_rawrunprotected->f_Ccall或f_call->luaD_call

(3) Lua代码加载执行次序:(在f_parser中下断点)

lua_load->luaD_protectedparser->luaD_pcall->luaD_rawrunprotected->f_parser

(4) 观察异常捕捉的实现方式和数据结构(在luaD_rawrunprotected中下断点)。

2. luaD_callhook/luaD_precall/luaD_poscall/luaD_call

lua_resume/lua_yield/resume

观察调用过程,包括:

(1) 堆栈(stack和CI)内存容量和大小操纵。

(2) lua_State结构体中与调用相关的数据结构(stack和CI)及改变。

(3) debug.sethook钩子函数

(4) __call元方法

(5) 变长参数处理

(6) 协程(yield/resume)的实现

(7) 尾调用

(未完成,待修改)

 

 

  • 大小: 190.4 KB
分享到:
评论

相关推荐

    lua-utf8.zip

    a utf-8 support module for Lua and LuaJIT 源码地址:https://github.com/starwing/luautf8 编译后可用的库: Linux版:lua-utf8.so Windows版:lua-utf8.dll(若是用在openresty中,openresty版本需使用32位版本...

    luadec解密工具 包含了5.1、5.2、5.3版本的 luareplace.exe/ luaopswap.exe/ luadec.exe/ lua.exe

    ├─5.1 │ └─bin │ liblua51.lib │ lua.exe │ lua51.dll │ lua51.lib │ luac.exe │ luadec.exe │ luaopswap.exe │ luareplace.exe │ ├─5.2 │ └─bin │ liblua52.lib │ lua.exe │ lua52.dll │ ...

    lua5.1 cjson.dll模块

    cjson.dll 需要lua5.1.dll 调用require “cjson” cjson.dll 需要lua5.1.dll 调用require “cjson”

    Lua5.1.5-lib库

    1. **lua.h**: 这是Lua C API的头文件,定义了所有与C语言交互的函数和数据结构。 2. **lua.c/lua.o**: Lua的主解析器和虚拟机实现,编译后的对象文件或静态库。 3. **lapi.c/lapi.o**: 实现了Lua与C之间的接口,...

    lua5.1版本源代码

    Lua脚本可以很容易的被C/C++代码调用,也可以反过来调用C/C++的函数,这使得Lua在应用程序中可以被广泛应用。不仅仅作为扩展脚本,也可以作为普通的配置文件,代替XML,Ini等文件格式,并且更容易理解和维护。 Lua...

    Lua5.1全三套:Lua Programming(中英文版)+中文手册

    本文将详细解析"Lua5.1全三套:Lua Programming(中英文版)+中文手册"中包含的知识点。 首先,我们有《Lua Programming》第二版的中文版和英文版。这本书由Mario J. Silva、Luiz Henrique de Figueiredo和Roberto ...

    lua5.1基础环境包(LuaForWindows_v5.1.5-52及mingw).zip

    总结来说,这个压缩包为Windows用户提供了完整的Lua 5.1开发环境,包括解释器、调试工具以及用于编译C扩展的MinGW。通过这个环境,开发者能够快速入门并深入掌握Lua编程,利用其强大的功能来解决各种问题。

    lua-5.1.5-Win64-bin (exe)程序

    2. **luac5.1.exe**:这是Lua的编译器,它可以将Lua源代码转换为预编译的字节码,这种字节码可以直接由Lua虚拟机执行,提高了加载速度。这对于大型应用或者需要快速启动的环境尤其有用。 3. **bin2c5.1.exe**:这是...

    FairyGUI生成lua代码插件.rar

    FairyGUI生成lua代码插件 导入到FairyGUI编辑器,可以为UI生成lua代码。 Git路径: https://github.com/qufangliu/Plugin_FairyGUI_Lua

    在线编码环境Praxis.zip

    Praxis 是基于 Lua,Lisp 和 Forth 的在线编码环境。 特性 OpenGL 实时音频生成 Midi 立体引擎 可编程的文本编辑器 等等 Introduction: https://www.youtube.com/watch?v=1VRtRazMYSA Running the ...

    ZeroBraneStudio1.9和lua脚本测试代码

    ZeroBrane Studio是一个免费、开源、跨平台(Windows、MacOSX和Linux)的Lua集成开发环境(IDE),它提供了代码提示、远程调试、代码分析、语法高亮等功能,支持Lua 5.1、Lua 5.2、Lua 5.3、LuaJIT和其他Lua引擎1。

    Lua-5.1.5-部分源码注释.rar

    1. **API接口(lapi.c)**:这部分代码定义了Lua与C语言交互的接口,包括创建和销毁lua_State,调用lua_pcall,注册C函数到全局环境等操作。 2. **编译器(lcode.c, llex.c, lparser.c)**:Lua的编译器将源代码转换为...

    lua5.1.lib

    lua5.1.lib文件缺失,LNK1181

    lua5.1 +luarocks for windows64安装版

    Lua 5.1是Lua语言的一个版本,发布于2006年,它提供了强大的数据结构,如表(tables),支持动态类型的面向对象编程,以及灵活的接口机制,使它能够方便地与各种C/C++程序进行交互。这个版本相对于更早期或更晚期的...

    lua-nginx-module-0.10.9rc7

    例如,使用`lua_code_cache on|off`来控制Lua代码缓存策略,用`set_by_lua_file`或`access_by_lua_file`等指令执行Lua脚本。 4. **测试与启动**:在修改配置后,务必先运行`nginx -t`测试配置文件的正确性,无误后...

    lua5.1静态库

    源代码通常包含`.c`和`.h`文件,`.c`文件是实现函数和数据结构的C语言代码,`.h`文件则定义了头文件,包含对外接口声明。这对于理解Lua的内部工作原理以及根据特定需求定制功能非常有用。 "编译 lua5.1"标签指示了...

    lua5.1压缩包源文件

    其中,`lua.c` 和 `luac.c` 分别是 Lua 解释器和编译器的主要实现。通过阅读这些源码,你可以了解到 Lua 如何解析和执行代码。 2. `lualib/`:这个目录包含了 Lua 标准库的源代码,如数学运算、字符串处理、文件I/O...

    Cocos2d-x之C++和Lua通信5个入门Demo

    木头Cocos2d-x教程 Lua篇 Demo源代码。 教程地址: 第1章:http://blog.csdn.net/musicvs/article/details/8440707 第2章:http://blog.csdn.net/musicvs/article/details/8440919 第3章:...

    c#调用脚本语言Lua——简单Demo

    c#调用脚本语言Lua——简单Demo 配置: 1. 下载c#下的Lua支持类库。下载地址:http://files.luaforge.net/releases/luainterface/luainterface/2.0.3 将(lua51.dll\LuaInterface.dll)引用自己的项目中。 2. 修改...

    lua源码下载 Lua-5.3.4 源码 最新 截止2017-3-7

    3. **lualib.h** 和 **luac.h**: 分别包含了标准库和编译器的接口。 4. **lstate.h**: 描述了lua_State结构,它是Lua执行环境的核心,保存了所有运行时信息。 5. **lparser.h** 和 **llex.h**: 用于解析Lua源代码...

Global site tag (gtag.js) - Google Analytics