标签:io os 使用 ar for sp 文件 on 问题
上节说到了 lua_dofile 执行脚本文件,或者编译过的脚本二进制文件。
这节看下,Lua 是如何区别这两种文件的,以及虚拟机在开始执行字节码之前,程序里面都发生了什么?
lua.c 里面的调用了 lua_dofile 来执行文件,看下 lua_dofile
/*
** Open file, generate opcode and execute global statement. Return 0 on
** success or 1 on error.
*/
int lua_dofile (char *filename)
{
int status;
int c;
FILE *f = lua_openfile(filename);
if (f == NULL)
return 1;
c = fgetc(f);
ungetc(c, f);
status = (c == ID_CHUNK) ? luaI_undump(f) : do_protectedmain();
lua_closefile();
return status;
}
注释里写得很清楚,这个函数是用来打开文件,生成字节码,执行全局的语句。
成功返回 0 ,失败返回 1 。
lua_openfile 打开文件部分已经在编译器分析的时候说过了,这里就不再重复了(后面再遇到这样的已经分析过的可能就不再说明,直接略过了。)。
看下面的
c = fgetc(f); ungetc(c, f);
这两句是读取文件的第一个字符,然后再把该字符放回到文件输入流中去,使输入流的还是保持在文件头的位置(也就是尚未读取的状态)。
接下来的这一句
status = (c == ID_CHUNK) ? luaI_undump(f) : do_protectedmain();
就是检查刚才读到那个字符。在编译器分析时,我们知道编译器生成的 *.out 二进制文件的开头第一个字符就是 ID_CHUNK ,ASCII 码为 27 。而在正常的脚本文件中,这个字符是不会出现的。所以可以根据这个标签来判定文件是个 *.out 的二进制文件或者是个脚本文件。
如果是编译过的二进制文件,调用 luaI_undump 恢复场景并执行。
/*
** load and run all chunks in a file
*/
int luaI_undump(FILE* D)
{
TFunc* m;
while ((m=luaI_undump1(D)))
{
int status=luaI_dorun(m);
luaI_freefunc(m);
if (status!=0) return status;
}
return 0;
}
这里可以看到,luaI_undump 先是调用 luaI_undump1 恢复场景,至于怎么恢复场景的,下节再说。
恢复之后,调用 luaI_dorun 。
回到 lua_dofile,如果是一个脚本文件的话,调用 do_protectedmain。
static int do_protectedmain (void)
{
TFunc tf;
int status;
jmp_buf myErrorJmp;
jmp_buf *oldErr = errorJmp;
errorJmp = &myErrorJmp;
luaI_initTFunc(&tf);
tf.fileName = lua_parsedfile;
if (setjmp(myErrorJmp) == 0)
{
lua_parse(&tf);
status = luaI_dorun(&tf);
}
else
{
status = 1;
adjustC(0); /* erase extra slot */
}
errorJmp = oldErr;
luaI_free(tf.code);
return status;
}
这里我们看到,在做一些初始化,和设置异常恢复断点之后,语法分析 lua_parse 之后,它也是调用了 luaI_dorun ,调到这一步的时候,编译过二进制文件和脚本文件就没有差别了。setjmp 可以先简单的认为是 C 语言版的 try...catch 异常处理, 虽然它们之间是有区别的。
后面代码是做一些异常发生的善后处理,设置状态位,释放相关的资源。
int luaI_dorun (TFunc *tf)
{
int status;
adjustC(1); /* one slot for the pseudo-function */
stack[CBase].tag = LUA_T_FUNCTION;
stack[CBase].value.tf = tf;
status = do_protectedrun(0);
adjustC(0);
return status;
}
把编译好的 TFunc 设置到栈上,调用 do_protectedrun。
/*
** Execute a protected call. Assumes that function is at CBase and
** parameters are on top of it. Leave nResults on the stack.
*/
static int do_protectedrun (int nResults)
{
jmp_buf myErrorJmp;
int status;
StkId oldCBase = CBase;
jmp_buf *oldErr = errorJmp;
errorJmp = &myErrorJmp;
if (setjmp(myErrorJmp) == 0)
{
do_call(CBase+1, nResults);
CnResults = (top-stack) - CBase; /* number of results */
CBase += CnResults; /* incorporate results on the stack */
status = 0;
}
else
{ /* an error occurred: restore CBase and top */
CBase = oldCBase;
top = stack+CBase;
status = 1;
}
errorJmp = oldErr;
return status;
}
注释比较清楚,不再细说。函数名字中有 protected 字样的就是受保护的调用,内部使用 setjmp 来设置异常恢复点。函数调用 do_call 执行刚才压栈的 TFunc.
/*
** Call a function (C or Lua). The parameters must be on the stack,
** between [stack+base,top). The function to be called is at stack+base-1.
** When returns, the results are on the stack, between [stack+base-1,top).
** The number of results is nResults, unless nResults=MULT_RET.
*/
static void do_call (StkId base, int nResults)
{
StkId firstResult;
Object *func = stack+base-1;
int i;
if (tag(func) == LUA_T_CFUNCTION)
{
tag(func) = LUA_T_CMARK;
firstResult = callC(fvalue(func), base);
}
else if (tag(func) == LUA_T_FUNCTION)
{
tag(func) = LUA_T_MARK;
firstResult = lua_execute(func->value.tf->code, base);
}
else
{ /* func is not a function */
/* Call the fallback for invalid functions */
open_stack((top-stack)-(base-1));
stack[base-1] = luaI_fallBacks[FB_FUNCTION].function;
do_call(base, nResults);
return;
}
/* adjust the number of results */
if (nResults != MULT_RET && top - (stack+firstResult) != nResults)
adjust_top(firstResult+nResults);
/* move results to base-1 (to erase parameters and function) */
base--;
nResults = top - (stack+firstResult); /* actual number of results */
for (i=0; i<nResults; i++)
*(stack+base+i) = *(stack+firstResult+i);
top -= firstResult-base;
}
判断栈上的是 C 函数还是,Lua 的函数。
如果是 C 的调用 callC。
如果是 Lua 的函数,则调用 lua_execute 由虚拟机执行。
如果不是一个函数,则调用 "function" 的回退函数。
之后,是调整返回值和栈。
调整栈有一个简单的规则就是:函数调用后栈的状态要和函数调用前保持一样,除了可能在栈上剩下几个返回值。这句话可能不容易理解,举个小例子。比如在 C 语言里,我们有一个函数:
int add(int a, int b)
{
return a + b;
}
如果我们有一个调用 int result = add(3, 5),在这句执行完后,它所产生的效果和 int result = 8 是完全一样的。套到这里就是,在该调用 add(3, 5) 的地方,你直接放个 8 到栈上去效果是一样的,对于后续程序的执行完全没有任何影响。
回忆一下汇编里的函数调用返回的栈桢结构,是不是觉得这里的函数调用返回有点眼熟了。
callC 就是调用一个 C 的函数指针。
lua_execute 是一个很大的 switch...case 来执行指令。这个在之前的版本里有详细介绍,在这个版本里可能就略过了。
fallback 对程序的主流程基本没什么影响。
----------------------------------------
到目前为止的问题:
> luaI_undump1 怎么恢复场景的
----------------------------------------
标签:io os 使用 ar for sp 文件 on 问题
原文地址:http://my.oschina.net/xhan/blog/332997