文章本文转载自微信公众号《编程杂技》,作者theanarkh。转载本文请联系编程杂技公众号。最近体验了Openresty,了解到Openresty是使用lua语言来增强Nginx的能力,于是重新学习了lua。lua语言小而强大,lua引擎也值得学习。周末看了一下lua引擎的一些实现,体验了一些lua语言的东西。本文对其进行简要介绍。如果有时间,我会写一篇文章来分析引擎的实现。1c语言嵌入lua引擎lua引擎本身就是一个库,类似于V8,我们可以将其嵌入到其他项目中,我们先安装相关文件,安装lua(我安装的是5.1.5)。那就写个demo体验一下吧。#include#include#include#includeintecho(lua_State*L){printf("world");}intmain(intargc,char*argv[]){ints=0;lua_State*L=lua_open();//注册自定义函数lua_register(L,"echo",echo);luaL_openlibs(L);//执行lua脚本luaL_dofile(L,"hello.lua");lua_close(L);return1;}编译以上代码gcchello.c-llua-lm-ldl,编写hello.lua脚本。打印(“你好”);回声();执行./a.out,我们看到输出了helloworld。这是一个简单的体验demo,类似于直接使用lua提供的命令行工具,只是我们还扩展了一个自定义的echo函数,供lua脚本调用。如果我们想动态执行一个脚本而不是执行一个lua文件,也是可以的。#include#include#includeconstchar*script="print('hi');";intmain(intargc,char*argv[]){lua_State*L=lua_open();luaL_openlibs(L);luaL_dostring(L,script);lua_close(L);return1;}编译执行上面的代码,我们会看到输出hi。上面的好像效果不大,因为我们只是简单的使用了lua语言提供的能力。而lua的能力远不止于此。Lua被称为胶水语言。除了嵌入其他语言,还支持扩展。让我们看看如何扩展lua的能力。2基于Lua的demoruntime虽然这只是Lua的简单扩展,但这里称之为runtime,因为和Node.js基于V8一样,我们也可以通过扩展Lua来实现基于Lua的runtime。下面看看如何扩展(也就是如何调用其他语言的代码,这里是c)。创建一个新的test.c文件。#include#include#includestaticinttest(lua_State*L){//取栈的第一个参数constchar*a=luaL_checkstring(L,1);//返回值入栈="test";luaL_register(L,libName,reg_test);return1;}lua和c通过栈进行通信。当lua调用c函数时,c函数可以从栈中获取lua参数,也可以从栈中返回执行结果给lua。我们将上面的代码编译成一个动态库。gcctest.c-fPIC-shared-otest.so然后写一个测试luademo。localtest=require"test"a=test.test("helloworld!")print(a)我们可以看到在lua中成功调用了test模块的test函数,输出hi。当我们require“test”时,lua会到当前目录下找到test.o,并执行其中的luaopen_test函数。luaopen_前缀是约定俗成的,test是模块名。我们可以设置在哪里找到需要加载的模块。下面分析一下c文件的代码,看看扩展lua时的一些内容。先看luaL_register。LUALIB_APIvoid(luaL_register)(lua_State*L,constchar*libname,constluaL_Reg*l){luaI_openlib(L,libname,l,0);}我们主要关注luaL_register的第二个和第三个参数libname和luaL_Reg。因为我们知道这个参数的格式,我们就知道怎么写c代码了。其中name是库名,也就是我们在require的时候传入的字符串。luaL_Reg的定义如下typedefint(*lua_CFunction)(lua_State*L);typedefstructluaL_Reg{constchar*name;lua_CFunctionfunc;}luaL_Reg;luaL_Reg是封装kv的结构体。name是导出函数的名字,也就是lua中可以调用的函数。func是对应的函数。在lua中执行name函数的时候,会执行func的代码。3Lua变量存储设计Lua是一种动态类型语言,这意味着变量的值类型是可以改变的。下面我们就来看看Lua是如何设计底层存储的。lua中的所有变量都由TValue结构表示。#defineTValuefieldsValuevalue;inttttypedefstructlua_TValue{TValuefields;}TValue;只有两个字段。tt是变量类型。lua的类型比较简单。如下#defineLUA_TNIL0#defineLUA_TBOOLEAN1#defineLUA_TLIGHTUSERDATA2#defineLUA_TNUMBER3#defineLUA_TSTRING4//数组和对象都使用一个类型#defineLUA_TTABLE5#defineLUA_TFUNCTION6#defineLUA_TUSERDATA7#defineLUA_TTHREAD8接下来我们看定义typedefunion{GCObject*p;bumber_inta}Value;Value分为两种,一种不需要gc,比如数字,一种需要gc,比如数组,lua是带gc的语言。我们继续看GCObject。unionGCObject{GCheadergch;unionTStringts;unionUdatau;unionClosurecl;structTableh;structProtop;structUpValuv;structlua_Stateth;/*thread*/};我们看到GCObject是一个联合体,可以存储不同类型的变量。我们再看一下TString的定义。typedefunionTString{L_Umaxaligndummy;/*内存对齐,性能优化*/struct{CommonHeader;lu_bytereserved;unsignedinthash;size_tlen;}tsv;}TString;string结构体中的主要字段是len和hash,len是字符串的长度,hash类似于一个索引,lua中的字符串本身并不存储在结构体中,而是统一管理的,主要是为了复用。比如两个变量有相同的字符串,那么在lua中,只有A字符串的值,而这两个变量会通过hash指向这个字符串的值。我们可以看一段代码来获得一个概览。//新建一个字符串,如果存在则直接复用>5)+1;size_tl1;//计算字符串的hash值for(l1=l;l1>=step;l1-=step)/*computehash*/h=h^((h<<5)+(h>>2)+cast(unsignedchar,str[l1-1]));//判断是否存在相同的字符串,如果存在则分享,直接返回,否则新建for(o=G(L)->strt.hash[lmod(h,G(L)->strt.size)];o!=NULL;o=o->gch。next){TString*ts=rawgco2ts(o);if(ts->tsv.len==l&&(memcmp(str,getstr(ts),l)==0)){if(isdead(G(L),o))changewhite(o);returnts;}}//notfound然后新建一个returnnewlstr(L,str,l,h);/*notfound*/}我们看到Lua的变量存储设计是一个树结构,然后通过上层的变量类型进行不同的访问操作。所以我们也可以了解到动态语言在变量存储方面的一些设计思想。后记:这是周末学习lua的一些内容,有时间会继续更新。Lua是一个非常有趣的项目。