经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » Lua » 查看文章
lua虚拟机概述
来源:cnblogs  作者:bytemode  时间:2018/12/17 9:43:00  对本文有异议

何为虚拟机

用于模拟计算机运行的程序.是个中间层,它处于脚本语言和硬件之间的一个程序.每一门脚本语言都会有自己定义的opcode(”操作码”),可以理解为这门程序自己定义的”汇编语言”.一般的编译型语言,比如C等,经过编译器编译之后生成的都是与当前硬件环境相匹配的汇编代码;而脚本型的语言,经过前端的处理之后,生成的就是opcode,再将该opcode放在这门语言的虚拟机中执行.虚拟机是作为单独的程序独立存在,而Lua由于是一门嵌入式的语言是附着在宿主环境中的.

lua代码到虚拟机执行的流程

在Lua中,Lua代码从词法分析到语法分析再到生成opcode,最后进入虚拟机执行的大体流程是什么样子的呢?

Lua的API中提供了luaL_dofile函数,它实际上是个宏,内部首先调用luaL_loadfile函数,加载Lua代码进行语法,词法分析,生成Lua虚拟机可执行的代码,再调用lua_pcall函数,执行其中的代码:

  1. #define luaL_dofile(L, fn) (luaL_loadfile(L, fn) || lua_pcall(L, 0, LUA_MULTRET, 0))

前半部分调用luaL_loadfile函数对Lua代码进行词法和语法分析,后半部分调用lua_pcall将第一步中分析的结果(也就是opcode)到虚拟机中执行.

首先来看luaL_loadfile函数,暂时不深入其中研究它如何分析一个Lua代码文件,先看它最后输出了什么.它最终会调用f_parser函数,这是对一个Lua代码进行分析的入口函数:

  1. static void f_parser (lua_State *L, void *ud) {
  2. int i;
  3. Proto *tf;
  4. Closure *cl;
  5. struct SParser *p = cast(struct SParser *, ud);
  6. int c = luaZ_lookahead(p->z);
  7. luaC_checkGC(L);
  8. tf = ((c == LUA_SIGNATURE[0]) ? luaU_undump : luaY_parser)(L, p->z,
  9. &p->buff, p->name);
  10. cl = luaF_newLclosure(L, tf->nups, hvalue(gt(L)));
  11. cl->l.p = tf;
  12. for (i = 0; i < tf->nups; i++) /* initialize eventual upvalues */
  13. cl->l.upvals[i] = luaF_newupval(L);
  14. setclvalue(L, L->top, cl);
  15. incr_top(L);
  16. }

在完成词法分析之后,返回了Proto类型的指针tf,然后将其绑定在新创建的Closure指针上,初始化UpValue,最后压入Lua栈中.

不难想像,Lua词法分析之后产生的opcode等相关数据都在这个Proto类型的结构体中.

再来看lua_pcall函数是如何将产生的opcode放入虚拟机执行的.

lua_pcall函数中,首先获取需要调用的函数指针:

  1. c.func = L->top - (nargs+1); /* function to be called */

这里的nargs是由函数参数传入的,luaL_dofile中调用lua_pcall时这里传入的参数是0,换句话说,这里得到的函数对象指针就是在f_parser函数中最后放入Lua栈的指针.

继续往下执行,走到luaD_call函数,有这一段代码:

  1. if (luaD_precall(L, func, nResults) == PCRLUA) /* is a Lua function? */
  2. luaV_execute(L, 1); /* call it */

进入luaV_execute函数,这里是虚拟机执行代码的主函数:

  1. void luaV_execute (lua_State *L, int nexeccalls) {
  2. LClosure *cl;
  3. StkId base;
  4. TValue *k;
  5. const Instruction *pc;
  6. reentry: /* entry point */
  7. lua_assert(isLua(L->ci));
  8. pc = L->savedpc;
  9. cl = &clvalue(L->ci->func)->l;
  10. base = L->base;
  11. k = cl->p->k;
  12. /* main loop of interpreter */
  13. for (;;) {
  14. const Instruction i = *pc++;
  15. StkId ra;
  16. if ((L->hookmask & (LUA_MASKLINE | LUA_MASKCOUNT)) &&
  17. (--L->hookcount == 0 || L->hookmask & LUA_MASKLINE)) {
  18. traceexec(L, pc);
  19. if (L->status == LUA_YIELD) { /* did hook yield? */
  20. L->savedpc = pc - 1;
  21. return;
  22. }
  23. base = L->base;
  24. }
  25. /* warning!! several calls may realloc the stack and invalidate `ra' */
  26. ra = RA(i);
  27. // 以下是各种opcode的情况处理
  28. }

可以看到,这里的pc指针里存放的是虚拟机opcode代码,它最开始从L->savepc初始化而来,而L->savepc在luaD_precall中赋值:

  1. L->savedpc = p->code; /* starting point */

这里的p就是第一步f_parser中返回的Proto指针.

回顾一下整个流程:

  1. 函数f_parser中,对Lua代码文件的分析返回了Proto指针

  2. 函数luaD_precall中,将Lua_state的savepc指针指向1中的Proto结构体的code指针

  3. 函数luaV_execute中,pc指针指向2中的savepc指针,紧跟着就是一个大的循环体,依次取出其中的opcode进行执行.

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号