lua 脚本执行流程

2.1k 词

简介

之所以写这篇文章,因为在看lua源码时候,感觉对lua脚本执行流程有一个全局认识对 研究lua源码本身起着重要作用,故此在这里简单介绍一下lua脚本是何时被加载、又是 何时执行的。

LuaState

想要更好的理解脚本执行流程,就必须先了解LuaState相关初始化,源码里简称L,lua 状态机是lua框架的核心,无论是lua的编译和lua的执行都离不开这个lua状态机,这里 使用UniLua作为源码参考,方便大家理解,初始化lua状态的方式很简单:

ILuaState Lua = LuaAPI.NewState();
Lua.L_OpenLibs();

需要注意的是在初始化完lua状态机后,需要初始化lua标准库,也就是上面的第二行代码 否则lua标准库的函数是无法调用的。

执行流程

lua脚本执行流程是在LuaState初始化完成的基础上进行的,核心可以分为两部分,即lua 的编译和lua的执行。

编译

编译lua源码主要调用这些函数进行源码编译,可以跟踪源码这些方法进行查看:

ThreadStatus L_DoFile(string filename);
public ThreadStatus L_LoadFileX(string filename, string mode);
ThreadStatus Load(ILoadInfo loadinfo, string name, string mode);
private ThreadStatus D_PCall<T>(PFuncDelegate<T> func, ref T ud, int oldTopIndex, int errFunc);
private ThreadStatus D_RawRunProtected<T>(PFuncDelegate<T> func, ref T ud);
private static void F_Load(ref LoadParameter param);

上面函数除了最后一个是进入实际编译解析阶段,其他函数都是初始化数据阶段,这里关心 F_Load函数的实现是什么?

private static void F_Load(ref LoadParameter param) {
	var L = param.L;
	LuaProto proto;
	var c = param.LoadInfo.PeekByte();
	if(c == LuaConf.LUA_SIGNATURE[0]){
		L.CheckMode( param.Mode, "binary" );
		proto = Undump.LoadBinary(L, param.LoadInfo, param.Name);
	} else {
		L.CheckMode( param.Mode, "text" );
		proto = Parser.Parse(L, param.LoadInfo, param.Name);
	}
	var cl = new LuaLClosureValue( proto );
	Utl.Assert(cl.Upvals.Length == cl.Proto.Upvalues.Count);
	L.Top.V.SetClLValue(cl);
	L.IncrTop();
}

该函数关键是调用Parser.Parse函数进入编译,具体代码实现可以去Parser的内部实现, 函数返回LuaProto,然后生成一个LuaLClosureValue对象cl,最终的结果是将函数编译 后的cl类型Push到栈顶(L.Top.V.SetClLValue(cl)),lua脚本的编译阶段到此全部结 束。

执行

lua执行是以lua脚本编译后的输出作为输入交给虚拟机进行运行的过程,执行主要涉及 下面这些脚本。

ThreadStatus PCall(int numArgs, int numResults, int errFunc);
private ThreadStatus D_PCall<T>(PFuncDelegate<T> func, ref T ud, int oldTopIndex, int errFunc);
private void D_Call(StkId func, int nResults, bool allowYield);
private void V_Execute();

上面函数只有最后一个是进入执行虚拟机指令阶段,虚拟机执行字节码指令的过程是很简单 的,这里简单给出函数原型:

private void V_Execute() {
	while(true){
		Instruction i
		switch(i){
			case OpCpde.OP_MOVE:
			//....
			case OpCpde.OP_LOADK:
			//....
			default:
			break
		}
	}
}

可以看出该函数使用一个无限循环加switch方式执行字节码,知道字节码执行完毕,lua脚本执行 也结束了。

参考资料

UniLua源码

lua源码