精华内容
下载资源
问答
  • Lua源码分析

    2018-01-12 23:08:57
    Lua源码分析Lua源码分析Lua源码分析Lua源码分析Lua源码分析Lua源码分析Lua源码分析Lua源码分析
  • lua源码分析

    2013-08-01 17:33:43
    lua源码分析(上) 带目录 本文作为学习笔记,边学习边整理边深入。
  • lua 源码分析

    2013-03-30 09:48:54
    lua分源,lua的性能不用说了吧,想深一步了解的可以看看
  • CodeLearn_Lua:Lua源码分析
  • 很久很久没有写博客了,一直忙于工作和项目...开启Lua源码阅读之旅 Lua是一门用C语言编写的脚本语言,一共1w多行代码,非常的轻巧,适合做web脚本、游戏脚本、物联网等场景下使用。 Lua可以进行独立编程,但是大部...

    目录

    一、Lua语言简介

    二、Lua架构图

    三、Lua源码结构


    很久很久没有写博客了,一直忙于工作和项目,最近依然想静下来阅读一些好的源码。自从读完了Nginx和Memcache的源码后,对服务器端的基础开源软件的实现原理有了一定的认识,接下来想看看Lua这门精巧的语言是如何实现的。

    一、Lua语言简介


    Lua是一门用C语言编写的脚本语言,一共1w多行代码,非常的轻巧,适合做web脚本、游戏脚本、物联网等场景下使用。

    Lua可以进行独立编程,但是大部分情况下是嵌入到大型语言里面,作为一个库,成为一种脚本编程语言存在。

    百度百科:Lua [1]  是一个小巧的脚本语言。它是巴西里约热内卢天主教大学(Pontifical Catholic University of Rio de Janeiro)里的一个由Roberto Ierusalimschy、Waldemar Celes 和 Luiz Henrique de Figueiredo三人所组成的研究小组于1993年开发的。 其设计目的是为了通过灵活嵌入应用程序中从而为应用程序提供灵活的扩展和定制功能。Lua由标准C编写而成,几乎在所有操作系统和平台上都可以编译,运行。Lua并没有提供强大的库,这是由它的定位决定的。所以Lua不适合作为开发独立应用程序的语言。Lua 有一个同时进行的JIT项目,提供在特定平台上的即时编译功能。

    Lua是解释型语言,通过对Lua的语言进行语法解析,然后生成二进制字节码,然后转由C语言进行执行操作。编译型语言,则会进行编译后生成机器码,直接由机器进行执行即可,执行效率会比较高。

    二、Lua架构图


    这个架构图,是整体读完了Lua的全部源码之后,才画出来的。如果有兴趣阅读Lua源码的同学,可以按照架构图的顺序,逐个模块进行学习。

    三、Lua源码结构


    Lua的下载地址:http://www.lua.org/

    源码包下载后,我们可以看一下lua-5.3.5/src目录下的代码结构。代码结构基本会分3部分

    虚拟机核心功能部分

    文件

    作用

    lua.c

    lua的可执行入口 main函数

    lapi.c C语言接口

    ldebug.c

    Debug 接口 

    ldo.c 

    函数调用以及栈管理 

    lfunc.c 

    函数原型及闭包管理 

    lgc.c

    垃圾回收机制

    lmem.c 

    内存管理接口

    lobject.c

    对象操作函数

    lopcodes.c

    虚拟机字节码定义

    lstate.c

    全局状态机 管理全局信息

    lstring.c

    字符串池 

    ltable.c

    表类型的相关操作 

    ltm.c

    元方法 

    lvm.c

    虚拟机 

    lzio.c 

    输入流接口

    源代码解析和预编译

    文件 作用
    lcode.c

    代码生成器 

    ldump.c

    序列化预编译的Lua 字节码 

    llex.c

    词法分析器 

    lparser.c

    解析器

    lundump.c

    还原预编译的字节码

    内嵌库

    文件 作用

    lauxlib.c

    库编写用到的辅助函数库 

    lbaselib.c

    基础库 

    ldblib.c

    Debug 库 

    linit.c

    内嵌库的初始化 

    liolib.c

    IO 库 

    lmathlib.c

    数学库 

    loadlib.c

    动态扩展库管理 

    loslib.c

    OS 库 

    lstrlib.c

    字符串库 

    ltablib.c

    表处理库

    每次阅读源码,其实最难的是开始,通过网上各种资料,先把lua的整个目录结构弄明白,幸好lua真的比较小,很容易就能弄明白每个文件是干什么的。接下去就是开始一点一点的啃整个源码的过程了。

    啃整个lua语言链路解析过程之前,我会优先把lua周边的库以及虚拟机字节码这块搞明白,然后再开始进行整个解析流程的阅读。

     

     

    展开全文
  • Lua的扩展库编写有三种方式: ... 动态调用:编写独立的扩展函数文件,编译成.so动态库文件,然后在lua中动态调用 扩展库编写:按照Lua语言本身的扩展库设计...《Lua源码分析 - 扩展库篇 - 扩展库Require的实现(12...

    目录

    一、实战篇 - 注册方式实现函数扩展

    二、实战篇 - 编写标准的扩展库

    三、实战篇 - 动态库加载方式实现

    四、实战篇 - 编译Lua的动态库liblua.so


    Lua的扩展库编写有三种方式:

    • 注册方式:在主体语言里面编写扩展函数,然后通过lua_register方式,注册到Lua全局注册表中
    • 扩展库编写:按照Lua语言本身的扩展库设计方式进行编写
    • 动态库加载方式:将扩展库编译成.so的动态库,Lua语言中通过require方式动态加载库文件

    如果忘记了扩展库的实现,可以回顾一下下面两篇文章:

    《Lua源码分析 - 扩展库篇 - 扩展库Require的实现(12)》

    《Lua源码分析 - 扩展库篇 - 扩展库Open的实现(13)》

    一、实战篇 - 注册方式实现函数扩展


    注册方式,我们在主体语言中,自定义一个my_func函数,入参为lua_State *L,函数内容将一个数字加上100。

    然后调用lua_register函数,将my_func函数注册到Lua栈的全局注册表中。

    当Lua语言中,遇到这个注册的C语言全局函数的时候,就会执行该函数。

        #include <stdio.h>
        #include <stdlib.h>
        #include  <math.h> 
        #include  <lua.h> 
        #include  <lualib.h>
        #include <lauxlib.h>
    
    
        //自定义一个Lua扩展函数
        static int my_func(lua_State *L) {
            int d = luaL_checkinteger(L, 1);
            lua_pushinteger(L, d + 100);  /* push result */
            return 1; 
        }
     
        int main() {
    
            printf("----------Hello Lua API------------\n");
    
            /* 初始化一个栈 */
            lua_State *L = luaL_newstate(); 
            luaL_openlibs(L);
            lua_pushstring(L, "Lua API"); 
            lua_pushstring(L, "Hello Lua"); 
    
            int size = lua_gettop(L);
            printf("栈个数:%i\n", size);
    
            /* 注册函数 */
            lua_register(L, "my_func", my_func);
    
            /* 文件加载方式 */
            luaL_loadfile(L, "test.lua"); //加载文件
            int size2 = lua_gettop(L);
            printf("加载文件后,栈个数:%i\n", size2);
            lua_pcall(L, 0, 0, 0); //执行文件
    
    
            /* 关闭栈 */
            lua_close(L);
            printf("----------Hello Lua API Close------------\n");
     
            return 0;
        }
    
    
    //test.lua
    
    print("luaL_loadfile:执行文件加载的Lua脚本!");
    print("自定义函数调用:" .. my_func(100));
    

    编译并执行,我们可以看到,在test.lua中调用my_func的执行结果,并打印出来

    ***6f:Togo zhuli$ gcc main.c -o main -l lua
    ***6f:Togo zhuli$ ./main 
    ----------Hello Lua API------------
    栈个数:2
    加载文件后,栈个数:3
    luaL_loadfile:执行文件加载的Lua脚本!
    自定义函数调用:200
    ----------Hello Lua API Close------------

    二、实战篇 - 编写标准的扩展库


    编写一个独立的扩展库文件,需要包含三个维度内容:

    • 扩展函数:自定义一个扩展函数,入参为lua_State对象,函数如果要获取外部入参参数,则从Lua的栈上获取。扩展函数默认返回1,则执行成功。
    • 配置数组:主要定义函数名称和扩展库中函数的映射关系。函数名称即为Lua语言中可以回调的函数。
    • 模块配置:定义一个以luaopen_*开头的函数,内部调用luaL_newlib函数实现模块的初始化。一般Lua中使用:模块名称.函数名称

    我们定义了lmylib.c的文件,放入Lua的src/目录下。

        #define lmylib_c
        #define LUA_LIB
    
        #include <stdio.h>
        #include <stdlib.h>
        #include <math.h> 
        #include <stdarg.h>
    
        #include "lprefix.h"
        #include "lua.h"
        #include "lualib.h"
        #include "lauxlib.h"
    
        //入参lua_State *L
        //返回1 则函数执行成功
        //通过luaL_checkinteger获取入参
        static int mylib_number(lua_State *L) {
            int d = luaL_checkinteger(L, 1); //获取参数
            lua_pushinteger(L, d + 100);  /* push result */
            return 1; 
        }
    
        //mylib_number函数名
        //调用方式:mylib.mylib_number(99)
        static const struct luaL_Reg mylib[] = {
            {"mylib_number", mylib_number},
            {NULL, NULL}
        };
    
        //mylib = 模块名称
        // luaL_newlib 初始化模块
        extern int luaopen_mylib(lua_State* L) {
            luaL_newlib(L, mylib);
            return 1;
        }

    添加了模块后,我们需要修改Lua的其它两个文件:

    • lualib.h文件:添加LUA_MYLIBNAME和LUAMOD_API
    • linit.c文件:添加{LUA_MYLIBNAME, luaopen_mylib}
    //lualib.h文件
    #define LUA_MYLIBNAME	"mylib"
    LUAMOD_API int (luaopen_mylib) (lua_State *L);
    
    //linit.c文件
    {LUA_MYLIBNAME, luaopen_mylib},
    
    

    然后我们需要修改src/Makefile文件,主要用于编译的时候生成目标对象lmylib.o文件。

    LIB_O=	lauxlib.o lbaselib.o lbitlib.o lcorolib.o ldblib.o liolib.o \
    	lmathlib.o loslib.o lstrlib.o ltablib.o lutf8lib.o loadlib.o linit.o lmylib.o 
    
    lmylib.o: lmylib.c lprefix.h lua.h luaconf.h lauxlib.h lualib.h
    
    

    重新安装Lua,执行unstall、clean、make、make install 命令

    **:lua-5.3.5 zhuli$ sudo make uninstall
    ......
    
    **:lua-5.3.5 zhuli$ sudo make macosx clean
    ....
    
    **:lua-5.3.5 zhuli$ sudo make macosx
    ....
    gcc -std=gnu99 -O2 -Wall -Wextra -DLUA_COMPAT_5_2 -DLUA_USE_MACOSX    -c -o linit.o linit.c
    gcc -std=gnu99 -O2 -Wall -Wextra -DLUA_COMPAT_5_2 -DLUA_USE_MACOSX    -c -o lmylib.o lmylib.c
    
    **:lua-5.3.5 zhuli$ sudo make install
    cd src && mkdir -p /usr/local/bin /usr/local/include /usr/local/lib 
    /usr/local/man/man1 /usr/local/share/lua/5.3 /usr/local/lib/lua/5.3
    cd src && install -p -m 0755 lua luac /usr/local/bin
    cd src && install -p -m 0644 lua.h luaconf.h lualib.h lauxlib.h lua.hpp /usr/local/include
    cd src && install -p -m 0644 liblua.a /usr/local/lib
    cd doc && install -p -m 0644 lua.1 luac.1 /usr/local/man/man1

    我们执行lua test.lua命令,则我们的扩展函数调用成功了

    //test.lua
    print("---------加载扩展函数------------");
    print(mylib.mylib_number(999));
    
    //直接调用Lua
    **:Togo zhuli$ lua test.lua
    ---------加载扩展函数------------
    1099
    
    //宿主方式调用test.lua文件
    **:Togo zhuli$ ./main 
    ----------Hello Lua API------------
    栈个数:2
    加载文件后,栈个数:3
    ---------加载扩展函数------------
    1099
    ----------Hello Lua API Close------------
    
    
    

    三、实战篇 - 动态库加载方式实现


    标准的扩展库,需要将扩展库直接编译进Lua源码里面,并且需要修改lualib.h和linit.c文件,对于集群线上大批量部署Lua语言,会造成巨大的困难。

    Lua可以通过requre方式动态加载扩展库,所以我们可以将扩展库编译成.so的文件,就可以实现动态加载方式。

    动态库和静态库的区别:

    • 静态库:在程序编译时会被连接到目标代码中,程序运行时将不再需要该静态库。
    • 动态库:在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入,因此在程序运行时还需要动态库存在。

    先看一下,我们编写好的动态库mylibi.c代码:

        #include <stdio.h>
        #include <stdlib.h>
        #include <math.h> 
        #include <stdarg.h>
    
        #include <lua.h>
        #include <lualib.h>
        #include <lauxlib.h>
    
        int luaopen_mylibi(lua_State *L);
    
        static int add(lua_State *L) {
            int d = luaL_checkinteger(L, 1);
            lua_pushinteger(L, d + 100);  /* push result */
            return 1; 
        }
    
        static const struct luaL_Reg mylibi[] = {
            {"add", add},
            {NULL, NULL}
        };
    
        extern int luaopen_mylibi(lua_State* L) {
            luaL_newlib(L, mylibi);
            return 1;
        }
    

    我们需要将mylibi.c编译成mylibi.so动态库文件。但是,如果我们编译的扩展库.so文件,依赖的是lua的静态库,则动态加载的时候就会出现多个虚拟机的报错。静态编译(-l lua),mylibi.so里面会将lua虚拟机代码加载进去进行独立编译,Lua的解析器又加载了一遍,所以会导致多个虚拟机冲突。

    ***:Togo zhuli$ gcc mylibi.c -fPIC -shared -o mylibi.so -l lua
    ***:Togo zhuli$ lua test.lua 
    ---------加载扩展函数------------
    lua: multiple Lua VMs detected
    stack traceback:
    	[C]: in ?
    	[C]: in function 'require'
    	test.lua:3: in main chunk
    	[C]: in ?

    Linux和Mac系统下的so文件编译方式不太一样,需要注意。编译完成后,mylibi.so和Lua执行脚本同一个目录下

    • Linux系统命令:gcc mylibi.c -fPIC -shared -o mylibi.so
    • macosx系统命令:gcc -O2 -fPIC -Wall -bundle -undefined dynamic_lookup -o mylibi.so  mylibi.c 
    //命令
    gcc -O2 -fPIC -Wall -bundle -undefined dynamic_lookup -o mylibi.so  mylibi.c 
    
    //执行
    ***:Togo zhuli$ lua test.lua 
    ---------加载扩展函数------------
    1124
    
    //test.lua
    print("---------加载扩展函数------------");
    local mylibi = require("mylibi"); //动态加载mylibi
    print(mylibi.add(1024)); //调用mylibi.add函数
    

    在C语言中动态调用Lua语言,也同样成功:

    ali-186590d6066f:Togo zhuli$ gcc main.c -o main -l lua
    ali-186590d6066f:Togo zhuli$ ./main 
    ----------Hello Lua API------------
    栈个数:2
    加载文件后,栈个数:3
    ---------加载扩展函数------------
    1124
    ----------Hello Lua API Close------------
    
    
    //main.c
        #include <stdio.h>
        #include <stdlib.h>
        #include  <math.h> 
        #include  <lua.h> 
        #include  <lualib.h>
        #include <lauxlib.h>
     
        int main() {
    
            printf("----------Hello Lua API------------\n");
    
            /* 初始化一个栈 */
            lua_State *L = luaL_newstate(); 
            luaL_openlibs(L);
            lua_pushstring(L, "Lua API"); 
            lua_pushstring(L, "Hello Lua"); 
    
            int size = lua_gettop(L);
            printf("栈个数:%i\n", size);
    
    
            /* 文件加载方式 */
            luaL_loadfile(L, "test.lua"); //加载文件
            int size2 = lua_gettop(L);
            printf("加载文件后,栈个数:%i\n", size2);
            lua_pcall(L, 0, 0, 0); //执行文件
    
    
            /* 关闭栈 */
            lua_close(L);
            printf("----------Hello Lua API Close------------\n");
     
            return 0;
        }

    四、实战篇 - 编译Lua的动态库liblua.so


    有些场景下,我们需要动态加载Lua的动态库。Lua语言本身编译的时候只生成静态库,要解决这个问题,我们需要将lua的动态库liblua.so(默认静态库liblua.a)也编译出来。

    编译Lua的时候,需要修改一下Lua的Makefile文件(主Makefile和src/Makefile):

    //修改src/Makefile文件,添加liblua.so
    LUA_SO=liblua.so
    ALL_T= $(LUA_A) $(LUA_T) $(LUAC_T) $(LUA_SO)
    $(LUA_SO): $(CORE_O) $(LIB_O)
        $(CC) -o $@ -shared $?
    
    //修改Makefile文件,TO_LIB结果对象加上liblua.so
    TO_LIB= liblua.a liblua.so

    重新安装Lua,执行unstall、clean、make、make install 命令

    我们看到liblua.so被拷贝到了 /usr/local/lib文件夹下,然后我们在文件夹下找到liblua.so

    **:lua-5.3.5 zhuli$ sudo make uninstall
    ......
    
    **:lua-5.3.5 zhuli$ sudo make macosx clean
    ....
    
    **:lua-5.3.5 zhuli$ sudo make macosx
    ....
    gcc -std=gnu99 -O2 -Wall -Wextra -DLUA_COMPAT_5_2 -DLUA_USE_MACOSX    -c -o linit.o linit.c
    gcc -std=gnu99 -O2 -Wall -Wextra -DLUA_COMPAT_5_2 -DLUA_USE_MACOSX    -c -o lmylib.o lmylib.c
    
    **:lua-5.3.5 zhuli$ sudo make install
    cd src && mkdir -p /usr/local/bin /usr/local/include /usr/local/lib 
    /usr/local/man/man1 /usr/local/share/lua/5.3 /usr/local/lib/lua/5.3
    cd src && install -p -m 0755 lua luac /usr/local/bin
    cd src && install -p -m 0644 lua.h luaconf.h lualib.h lauxlib.h lua.hpp /usr/local/include
    cd src && install -p -m 0644 liblua.a liblua.so /usr/local/lib
    cd doc && install -p -m 0644 lua.1 luac.1 /usr/local/man/man1

     

    展开全文
  • 关于Lua源码分析学习教程是本文要介绍的内容,主要来了解LUA中源码的使用方法。Lua首先将源程序编译成为字节码,然后交由虚拟机解释执行.对于每一个函数,Lua的编译器将创建一个原型(prototype),它由一组指令及其使用...

    http://www.61ic.com/Mobile/iPhone/201108/36968.html


    关于Lua源码分析学习教程是本文要介绍的内容,主要来了解LUA源码的使用方法。Lua首先将源程序编译成为字节码,然后交由虚拟机解释执行.对于每一个函数,Lua的编译器将创建一个原型(prototype),它由一组指令及其使用到的常量组成[1].最初的Lua虚拟机是基于栈的.到1993年,Lua5.0版本,采用了基于寄存器的虚拟机,使得Lua的解释效率得到提升,

    体系结构与指令系统

    与虚拟机和指令相关的文件主要有两个: lopcodes.c 和 lvm.c. 从名称可以看出来,这两个文件分别用于描述操作码(指令)和虚拟机.
    首先来看指令:

    Lua共有38条指令, 在下面两处地方分别描述了这些指令的名称和模式, 如下:

    1. lopcodes.c:16
    2. const char *const luaP_opnames[NUM_OPCODES+1] = {
    3. "MOVE",
    4. "LOADK",
    5. "LOADBOOL",
    6. "LOADNIL",
    7. "GETUPVAL",
    8. "GETGLOBAL",
    9. "GETTABLE",
    10. "SETGLOBAL",
    11. "SETUPVAL",
    12. "SETTABLE",
    13. "NEWTABLE",
    14. "SELF",
    15. "ADD",
    16. "SUB",
    17. "MUL",
    18. "DIV",
    19. "MOD",
    20. "POW",
    21. "UNM",
    22. "NOT",
    23. "LEN",
    24. "CONCAT",
    25. "JMP",
    26. "EQ",
    27. "LT",
    28. "LE",
    29. "TEST",
    30. "TESTSET",
    31. "CALL",
    32. "TAILCALL",
    33. "RETURN",
    34. "FORLOOP",
    35. "FORPREP",
    36. "TFORLOOP",
    37. "SETLIST",
    38. "CLOSE",
    39. "CLOSURE",
    40. "VARARG",
    41. NULL
    42. };
    43. #define opmode(t,a,b,c,m) (((t)<<7) | ((a)<<6) | ((b)<<4) | ((c)<<2) | (m))
    44. const lu_byte luaP_opmodes[NUM_OPCODES] = {
    45. /* T A B C mode opcode */
    46. opmode(0, 1, OpArgR, OpArgN, iABC) /* OP_MOVE */
    47. ,opmode(0, 1, OpArgK, OpArgN, iABx) /* OP_LOADK */
    48. ,opmode(0, 1, OpArgU, OpArgU, iABC) /* OP_LOADBOOL */
    49. ,opmode(0, 1, OpArgR, OpArgN, iABC) /* OP_LOADNIL */
    50. ,opmode(0, 1, OpArgU, OpArgN, iABC) /* OP_GETUPVAL */
    51. ,opmode(0, 1, OpArgK, OpArgN, iABx) /* OP_GETGLOBAL */
    52. ,opmode(0, 1, OpArgR, OpArgK, iABC) /* OP_GETTABLE */
    53. ,opmode(0, 0, OpArgK, OpArgN, iABx) /* OP_SETGLOBAL */
    54. ,opmode(0, 0, OpArgU, OpArgN, iABC) /* OP_SETUPVAL */
    55. ,opmode(0, 0, OpArgK, OpArgK, iABC) /* OP_SETTABLE */
    56. ,opmode(0, 1, OpArgU, OpArgU, iABC) /* OP_NEWTABLE */
    57. ,opmode(0, 1, OpArgR, OpArgK, iABC) /* OP_SELF */
    58. ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_ADD */
    59. ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_SUB */
    60. ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_MUL */
    61. ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_DIV */
    62. ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_MOD */
    63. ,opmode(0, 1, OpArgK, OpArgK, iABC) /* OP_POW */
    64. ,opmode(0, 1, OpArgR, OpArgN, iABC) /* OP_UNM */
    65. ,opmode(0, 1, OpArgR, OpArgN, iABC) /* OP_NOT */
    66. ,opmode(0, 1, OpArgR, OpArgN, iABC) /* OP_LEN */
    67. ,opmode(0, 1, OpArgR, OpArgR, iABC) /* OP_CONCAT */
    68. ,opmode(0, 0, OpArgR, OpArgN, iAsBx) /* OP_JMP */
    69. ,opmode(1, 0, OpArgK, OpArgK, iABC) /* OP_EQ */
    70. ,opmode(1, 0, OpArgK, OpArgK, iABC) /* OP_LT */
    71. ,opmode(1, 0, OpArgK, OpArgK, iABC) /* OP_LE */
    72. ,opmode(1, 1, OpArgR, OpArgU, iABC) /* OP_TEST */
    73. ,opmode(1, 1, OpArgR, OpArgU, iABC) /* OP_TESTSET */
    74. ,opmode(0, 1, OpArgU, OpArgU, iABC) /* OP_CALL */
    75. ,opmode(0, 1, OpArgU, OpArgU, iABC) /* OP_TAILCALL */
    76. ,opmode(0, 0, OpArgU, OpArgN, iABC) /* OP_RETURN */
    77. ,opmode(0, 1, OpArgR, OpArgN, iAsBx) /* OP_FORLOOP */
    78. ,opmode(0, 1, OpArgR, OpArgN, iAsBx) /* OP_FORPREP */
    79. ,opmode(1, 0, OpArgN, OpArgU, iABC) /* OP_TFORLOOP */
    80. ,opmode(0, 0, OpArgU, OpArgU, iABC) /* OP_SETLIST */
    81. ,opmode(0, 0, OpArgN, OpArgN, iABC) /* OP_CLOSE */
    82. ,opmode(0, 1, OpArgU, OpArgN, iABx) /* OP_CLOSURE */
    83. ,opmode(0, 1, OpArgU, OpArgN, iABC) /* OP_VARARG */
    84. };

    前面一个数组容易理解, 表示了每条指令的名称. 后面一个数组表示的是指令的模式. 奇怪的符号让人有些费解. 在看模式之前, 首先来看Lua指令的格式,如图:

    详解关于Lua源码分析学习教程

    如上图, Lua的指令可以分成三种形式. 即在上面的模式数组中也可以看到的iABC, iABx 和 iAsBx. 对于三种形式的指令来说, 前两部分都是一样的, 分别是6位的操作码和8位A操作数; 区别在于, 后面部是分割成为两个长度为9位的操作符(B, C),一个无符号的18位操作符Bx还是有符号的18位操作符sBx. 这些定义的代码如下:

    1. lopcodes.c : 34
    2. /*
    3. ** size and position of opcode arguments.
    4. */
    5. #define SIZE_C 9
    6. #define SIZE_B 9
    7. #define SIZE_Bx (SIZE_C + SIZE_B)
    8. #define SIZE_A 8
    9. #define SIZE_OP 6
    10. #define POS_OP 0
    11. #define POS_A (POS_OP + SIZE_OP)
    12. #define POS_C (POS_A + SIZE_A)
    13. #define POS_B (POS_C + SIZE_C)
    14. #define POS_Bx POS_C

    再来看指令的操作模式, Lua使用一个字节来表示指令的操作模式. 具体的含义如下:

    1、使用最高位来表示是否是一条测试指令. 之所以将这一类型的指令特别地标识出来, 是因为Lua的指令长度是32位,对于分支指令来说, 要想在这32位中既表示两个操作数来做比较, 同时还要表示一个跳转的地址, 是很困难的. 因此将这种指令分成两条, 第一条是测试指令, 紧接着一条无条件跳转. 如果判断条件成立则将PC(Program Counter, 指示下一条要执行的指令)加一, 跳过下一条无条件跳转指令, 继续执行; 否则跳转.

    2、第二位用于表示A操作数是否被设置

    3、接下来的二位用于表示操作数B的格式,OpArgN表示操作数未被使用, OpArgU表示操作数被使用(立即数?), OpArgR表示表示操作数是寄存器或者跳转的偏移量, OpArgK表示操作数是寄存器或者常量.

    最后, 给出Lua虚拟机的体系结构图(根据源代码分析得出):

    详解关于Lua源码分析学习教程

    首先, 我们注意到, Lua的解释器还是一个以栈为中心的结构. 在lua_State这个结构中,有许多个字段用于描述这个结构.stack用于指向绝对栈底, 而base指向了当前正在执行的函数的第一个参数, 而top指向栈顶的第一个空元素.

    我们可以看到,这个体系结构中并没有独立出来的寄存器. 从以下代码来看:

    1. lvm.c:343
    2. #define RA(i) (base+GETARG_A(i))
    3. /* to be used after possible stack reallocation */
    4. #define RB(i) check_exp(getBMode(GET_OPCODE(i)) == OpArgR, base+GETARG_B(i))
    5. #define RC(i) check_exp(getCMode(GET_OPCODE(i)) == OpArgR, base+GETARG_C(i))
    6. #define RKB(i) check_exp(getBMode(GET_OPCODE(i)) == OpArgK, \
    7. ISK(GETARG_B(i)) ? k+INDEXK(GETARG_B(i)) : base+GETARG_B(i))
    8. #define RKC(i) check_exp(getCMode(GET_OPCODE(i)) == OpArgK, \
    9. ISK(GETARG_C(i)) ? k+INDEXK(GETARG_C(i)) : base+GETARG_C(i))
    10. #define KBx(i) check_exp(getBMode(GET_OPCODE(i)) == OpArgK, k+GETARG_Bx(i))

    当指令操作数的类型是寄存器时,它的内容是以base为基址在栈上的索引值.如图所示.寄存器实际是base之上栈元素的别名;当指令操作数的类型的常数时, 它首先判断B操作数的最位是否为零.如果是零,则按照和寄存器的处理方法一样做,如果不是零,则在常数表中找相应的值.

    我们知道Lua中函数的执行过程是这样的. 首先将函数压栈,然后依次将参数压栈,形成图中所示的栈的内容. 因此R[0]到R[n]也分别表示了Arg[1]到Arg[N+1].在第一个参数之下,就是当前正在执行的函数,对于Lua的函数(相对C函数)来说,它是指向类型为 Prototype的TValue, 在Prototype中字段code指向了一个数组用来表示组成这个函数的所有指令,字段k指向一个数组来表示这个函数使用到的所有常量.最后,Lua在解释执行过程中有专门的变量pc来指向下一条要执行的指令.

    指令解释器

    有了前面对指令格式和体系结构的介绍,现在我们可以进入正题, 来看看Lua的指令是如何执行的了.主函数如下:

    1. lvm.c:373
    2. void luaV_execute (lua_State *L, int nexeccalls) {
    3. LClosure *cl;
    4. StkId base;
    5. TValue *k;
    6. const Instruction *pc;
    7. reentry: /* entry point */
    8. lua_assert(isLua(L->ci));
    9. pc = L->savedpc;
    10. cl = &clvalue(L->ci->func)->l;
    11. base = L->base;
    12. k = cl->p->k;

    这是最开始的初始化过程.其中, pc被初始化成为了L->savedpc,base被初始化成为了L->base, 即程序从L->savedpc开始执行 (在下一篇专题中,将会介绍到 L->savedpc在函数调用的预处理过程中指向了当前函数的code),而L->base指向栈中当前函数的下一个位置.cl表示当前正在执行闭包(当前可以理解成为函数),k指向当前闭包的常量表.

    接下来(注意,为了专注主要逻辑, 我将其中用于Debugger支持,断言等代码省略了):

    1. /* main loop of interpreter */
    2. for (;;) {
    3. const Instruction i = *pc++;
    4. StkId ra;
    5. /* 省略Debugger支持和Coroutine支持*/
    6. /* warning!! several calls may realloc the stack and invalidate `ra' */
    7. ra = RA(i);
    8. /* 省略断言 */
    9. switch (GET_OPCODE(i)) {

    进入到解释器的主循环,处理很简单,取得当前指令,pc递增,初始化ra,然后根据指令的操作码进行选择. 接下来的代码是什么样的, 估计大家都能想到,一大串的case来指示每条指令的执行.具体的实现可以参考源码, 在这里不对每一条指令展开, 只是对其中有主要的几类指令进行说明:

    传值类的指令,与MOVE为代表:

    1. lvm.c:403
    2. case OP_MOVE: {
    3. setobjs2s(L, ra, RB(i));
    4. continue;
    5. }
    6. lopcodes:154
    7. OP_MOVE,/* A B R(A) := R(B) */
    8. lobject.h:161
    9. #define setobj(L,obj1,obj2) \
    10. { const TValue *o2=(obj2); TValue *o1=(obj1); \
    11. o1->value = o2->value; o1->tt=o2->tt; \
    12. checkliveness(G(L),o1); }
    13. /*
    14. ** different types of sets, according to destination
    15. */
    16. /* from stack to (same) stack */
    17. #define setobjs2s setobj

    从注释来看, 这条指令是将操作数A,B都做为寄存器,然后将B的值给A. 而实现也是简单明了,只使用了一句. 宏展开以后, 可以看到, R[A],R[B]的类型是TValue, 只是将这两域的值传过来即可. 对于可回收对象来说,真实值不会保存在栈上,所以只是改了指针,而对于非可回收对象来说,则是直接将值从R[B]赋到R[A].

    数值运算类指令,与ADD为代表:

    1. lvm.c:470
    2. case OP_ADD: {
    3. arith_op(luai_numadd, TM_ADD);
    4. continue;
    5. }
    6. lvm.c:360
    7. #define arith_op(op,tm) { \
    8. TValue *rb = RKB(i); \
    9. TValue *rc = RKC(i); \
    10. if (ttisnumber(rb) && ttisnumber(rc)) { \
    11. lua_Number nb = nvalue(rb), nc = nvalue(rc); \
    12. setnvalue(ra, op(nb, nc)); \
    13. } \
    14. else \
    15. Protect(Arith(L, ra, rb, rc, tm)); \
    16. }
    17. lopcodes.c:171
    18. OP_ADD,/* A B C R(A) := RK(B) + RK(C) */

    如果两个操作数都是数值的话,关键的一行是:

    1. setnvalue(ra,op(nb,nc));

    即两个操作数相加以后,把值赋给R[A].值得注意的是,操作数B,C都是RK, 即可能是寄存器也可能是常量,这最决于最B和C的最高位是否为1,如果是1,则是常量,反之则是寄存器.具体可以参考宏ISK的实现.

    如果两个操作数不是数值,即调用了Arith函数,它尝试将两个操作转换成数值进行计算,如果无法转换,则使用元表机制.该函数的实现如下:

    1. lvm.c:313
    2. static void Arith (lua_State *L, StkId ra, const TValue *rb,
    3. const TValue *rc, TMS op) {
    4. TValue tempb, tempc;
    5. const TValue *b, *c;
    6. if ((b = luaV_tonumber(rb, &tempb)) != NULL &&
    7. (c = luaV_tonumber(rc, &tempc)) != NULL) {
    8. lua_Number nb = nvalue(b), nc = nvalue(c);
    9. switch (op) {
    10. case TM_ADD: setnvalue(ra, luai_numadd(nb, nc)); break;
    11. case TM_SUB: setnvalue(ra, luai_numsub(nb, nc)); break;
    12. case TM_MUL: setnvalue(ra, luai_nummul(nb, nc)); break;
    13. case TM_DIV: setnvalue(ra, luai_numdiv(nb, nc)); break;
    14. case TM_MOD: setnvalue(ra, luai_nummod(nb, nc)); break;
    15. case TM_POW: setnvalue(ra, luai_numpow(nb, nc)); break;
    16. case TM_UNM: setnvalue(ra, luai_numunm(nb)); break;
    17. default: lua_assert(0); break;
    18. }
    19. }
    20. else if (!call_binTM(L, rb, rc, ra, op))
    21. luaG_aritherror(L, rb, rc);
    22. }

    在上面call_binTM用于调用到元表中的元方法,因为在Lua以前的版本中元方法也被叫做tag method, 所以函数最后是以TM结尾的.

    1. lvm:163
    2. static int call_binTM (lua_State *L, const TValue *p1, const TValue *p2,
    3. StkId res, TMS event) {
    4. const TValue *tm = luaT_gettmbyobj(L, p1, event); /* try first operand */
    5. if (ttisnil(tm))
    6. tm = luaT_gettmbyobj(L, p2, event); /* try second operand */
    7. if (!ttisfunction(tm)) return 0;
    8. callTMres(L, res, tm, p1, p2);
    9. return 1;
    10. }

    在 这个函数中,试着从二个操作数中找到其中一个操作数的元方法(第一个操作数优先), 这里event表示具体哪一个元方法,找到了之后,再使用函数callTMres()去调用相应的元方法. callTMres()的实现很简单,只是将元方法,第一,第二操作数先后压栈,再调用并取因返回值.具体如下:

    1. lvm.c:82
    2. static void callTMres (lua_State *L, StkId res, const TValue *f,
    3. const TValue *p1, const TValue *p2) {
    4. ptrdiff_t result = savestack(L, res);
    5. setobj2s(L, L->top, f); /* push function */
    6. setobj2s(L, L->top+1, p1); /* 1st argument */
    7. setobj2s(L, L->top+2, p2); /* 2nd argument */
    8. luaD_checkstack(L, 3);
    9. L->top += 3;
    10. luaD_call(L, L->top - 3, 1);
    11. res = restorestack(L, result);
    12. L->top--;
    13. setobjs2s(L, res, L->top);
    14. }

    逻辑运算类指令,与EQ为代表:

    1. lvm.c:541
    2. case OP_EQ: {
    3. TValue *rb = RKB(i);
    4. TValue *rc = RKC(i);
    5. Protect(
    6. if (equalobj(L, rb, rc) == GETARG_A(i))
    7. dojump(L, pc, GETARG_sBx(*pc));
    8. )
    9. pc++;
    10. continue;
    11. }
    12. lopcodes.c:185
    13. OP_EQ,/* A B C if ((RK(B) == RK(C)) ~= A) then pc++ */

    在 这条指令实现的过程中,equalobj与之前的算术运算类似,读者可以自行分析.关键看它是如果实现中跳转的,如果RK[B]==RK[C]并且A为1 的情况下(即条件为真),则会使用pc取出下一条指令,调用dojump进行跳转,否则pc++,挂空紧接着的无条件跳转指令. dojump的实现如下:

    1. lvm.c:354
    2. #define dojump(L,pc,i) {(pc) += (i); luai_threadyield(L);}

    luai_threadyield只是顺序地调用lua_unlock和lua_lock,这里为释放一次锁,使得别的线程可以得到调度.

    函数调用类指令,与CALL为代表:

    1. lvm.c:582
    2. case OP_CALL: {
    3. int b = GETARG_B(i);
    4. int nresults = GETARG_C(i) - 1;
    5. if (b != 0) L->top = ra+b; /* else previous instruction set top */
    6. L->savedpc = pc;
    7. switch (luaD_precall(L, ra, nresults)) {
    8. case PCRLUA: {
    9. nexeccalls++;
    10. goto reentry; /* restart luaV_execute over new Lua function */
    11. }
    12. case PCRC: {
    13. /* it was a C function (`precall' called it); adjust results */
    14. if (nresults >= 0) L->top = L->ci->top;
    15. base = L->base;
    16. continue;
    17. }
    18. default: {
    19. return; /* yield */
    20. }
    21. }
    22. }
    23. lopcodes.c:192
    24. OP_CALL,/* A B C R(A), ... ,R(A+C-2) := R(A)(R(A+1), ... ,R(A+B-1)) */

    这一条指令将在下一个介绍Lua函数调用规范的专题中详细介绍. 在这里只是简单地说明CALL指令的R[A]表示的是即将要调用的函数,而B和C则分别表示参数个数加1,和返回值个数加1. 之所以这里需要加1,其原因是:B和C使用零来表示变长的参数和变长的返回值,而实际参数个数就向后推了一个.

    指令的介绍就先到此为止了, 其它的指令的实现也比较类似.仔细阅读源码就可很容易地分析出它的意义来. 下一篇将是一个专题, 详细地介绍Lua中函数的调用是如何实现的.

    小结:详解关于Lua源码分析学习教程的内容介绍完了,希望通过本文的学习能对你有所帮助!


    展开全文
  • Lua是一个小巧的脚本语言。也被广泛应用,比如游戏开发。引擎用c++,经常变动的业务层用lua实现。...Lua源码大体分为三个部分:虚拟机核心、内嵌库以及解释器、编译器 Lua 虚拟机工作流程 1.将 Lua代码进行词法...

    Lua是一个小巧的脚本语言。也被广泛应用,比如游戏开发。引擎用c++,经常变动的业务层用lua实现。做了几年的lua应用开发。最近想研究一下lua的具体实现,深入的了解lua。

    lua版本:5.3.5

    Lua特性:

    1. 可移植性
    2. 良好的嵌入性
    3. 非常小的尺寸
    4. 高效,速度快

    Lua源码大体分为个部分:虚拟机核心、内嵌库以及解释器、编译器

    Lua 虚拟机工作流程

    1. Lua代码进行词法分析、语法分 析等,最终生成字节码

    2.这些字节码装载到虚拟机中执行

     

    Lua中的数据类型

    Lua是一门动态类型的脚本语言,这意味着同一个变量可以在不同时刻指向不同类型的数据

    基本的数据类型有                                    类型                                            对应数据结构

    #define LUA_TNONE   (-1)                        无类型                                                 

    #define LUA_TNIL    0                                   空类型                                                 

    #define LUA_TBOOLEAN    1                      布尔类型                                            

    #define LUA_TLIGHTUSERDATA  2         指针                                                      void*

    #define LUA_TNUMBER   3                         数据                                                      lua_Number

    #define LUA_TSTRING   4                            字符串                                                  TString

    #define LUA_TTABLE    5                                                                                        Table

    #define LUA_TFUNCTION   6                      函数                                                      CClosureLClosure

    #define LUA_TUSERDATA   7                     指针                                                      void*

    #define LUA_TTHREAD   8                          Lua虚拟机,协程                                lua_State

     

    Lua中用lua_TValue来保存所有的数据类型

    typedef union Value {

      GCObject *gc;        可回收对象

      void *p;             自定义类型

      int b;           布尔类型

      lua_CFunction f;  c函数

      lua_Integer i;     整数

      lua_Number n;    浮点数

    } Value;

     

    #define TValuefields  Value value_; int tt_

    typedef struct lua_TValue {

      TValuefields;

    } TValue;

    展开之后:

    typedef struct lua_TValue {

      Value value_;

     int tt_    类型标记

    } TValue;

     

    有些数据类型需要进行垃圾回收,lua中用iscollectable宏判断类型是否需要垃圾回收。

    #define iscollectable(o)  (rttype(o) & BIT_ISCOLLECTABLE)

    相关宏:#define BIT_ISCOLLECTABLE  (1 << 6)

    #define rttype(o) ((o)->tt_)

    由此可见,tt_的第六位用于标记类型是否需要进行垃圾回收。

     

    可进行垃圾回收的类型:GCObject

    可回收对象的通用类型

    typedef struct GCObject GCObject;

    可回收对象的通用头部

    #define CommonHeader GCObject *next; lu_byte tt; lu_byte marked

    可回收对象的通用类型只包含通用头部

    struct GCObject {

      CommonHeader;

    };

     

    展开为:

    struct GCObject {

      GCObject *next;    指向下一个gc对象

     lu_byte tt;         对象类型

     lu_byte marked      gc标记

    ;

    };

     

    表示所有可回收对象的联合体

    union GCUnion {

      GCObject gc;

      struct TString ts;

      struct Udata u;

      union Closure cl;

      struct Table h;

      struct Proto p;

      struct lua_State th;

    };

     

    展开全文
  • Lua源码分析(1)

    万次阅读 2008-10-25 23:44:00
    Lua源码分析(1) -- 简介收藏 新一篇: Lua源码分析(2) -- 对象表示 | 旧一篇: Ruby 在 VC Express 2005 下的编译 function StorePage(){d=document;t=d.selection?(d.selection.type!=None?d.selection....
  • 在《Lua源码分析 - 主流程篇 - 函数调用栈的实现(08)》我们看到了整个Lua脚本语言的执行主流程。 Lua脚本主流程:通过文件解析->解析成语法Token->编译成二进制操作码->执行二进制操作码 上一章节我们...
  • Lua源码分析 -- 对象表示 Lua是动态类型的语言, 即是说类型附着于值而不变量[1]. Lua的八种基本类型空, 布尔, 数值, 字符串, 表, 函数和用户数据. 所有类似的值都是虚拟机的第一类值. Lua 解释器将其表示成为标签...
  • Lua 源码分析 lprefix.h

    2019-05-11 00:50:57
    Lua 源码里面很小的一个头文件, 没什么东西, 会预先进行一些设置, 每个 .c 文件都会引用. #ifndef lprefix_h #define lprefix_h // POSIX/XSI #if !defined(LUA_USE_C89) // 有关移植性 #if !defined(_...
  • Lua 源码分析之String

    千次阅读 2018-06-15 11:48:36
    因为每一版本的源码可能会有差别,现在基于lua 5.2.1来分析,保持一致性。 从虚拟机的大体来看,字符串通过一个结构体存放在global_State里,这个结构stringtable(lstate.h)是: GCObject(lstate.h)的结构是: ...
  • Lua简介 Lua是一个强大、高效、轻量、可嵌入的脚本编程语言,体积小、速度快,采用ANSI C语言编写,并且开放源代码。主要应用于游戏开发行业,在移动游戏开发领域,主流的引擎如Cocos2d-x、Unity都支持Lua作为脚本...
  • Lua 源码分析之TValue

    2018-09-06 14:59:25
    Lua数据结构设计确定了这个整体的设计理念,Lua所有的数据,一共有9种,都是通过TValue这个结构来存储。Lua的9种基础的数据类型有(lua.h:73): /* ** basic types */ #define LUA_TNONE (-1) #define LUA_TNIL ...
  • Lua 源码分析之Table

    2018-11-07 10:34:36
    一、创建一个table(lapi.c, line:666)...LUA_API void lua_createtable (lua_State *L, int narray, int nrec) { Table *t; lua_lock(L); luaC_checkGC(L); t = luaH_new(L); sethvalue(L, L->top, t); api...
  • LUA源码分析五:环境设置 版本日期 2011年4月22日 lua里的环境设置,可以看成是一个临时的域名空间。这个空间里有名字和变量等等。可以试着运行一下以下代码,输出全局的环境     local l_lindp=1 g_lindp=1...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 650
精华内容 260
关键字:

lua源码分析