在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
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) 尾调用
(未完成,待修改)
|
请发表评论