`
chinamming
  • 浏览: 141065 次
  • 性别: Icon_minigender_1
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

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) 尾调用

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics