近期学习Lua、C、 Erlang过程中的心得体会

在服务器上配置Lua时,需要考虑到该机子是32位的还是64位机子,而且还要根据选择不同的安装Lua方法采取不同的处理方式,

以解决有关-fPIC问题(32bitOS与64bitOS上都存在),和使用源码安装中make出现的warning:mplicit declaration of function ?readline?错

误(在64bitOS上安装时会出现此错误)

首先问题一:

因为这里的配置是专门针对Erlang+C+lua的,在编译.so文件时,需要使用到-fPIC选项,会出现relocation R_X86_64_32 against `luaO_nilobject_'

can not be used when making a shared object;

recompile with -fPIC /usr/local/lib/liblua.a: could not read symbols: Bad value

collect2: ld returned 1 exit status 错误,有关-fPIC问题在其他情况下调用是否出问题,与所调用的链接库或程序安装设置有关;

而这里的错误,是因为默认情况下,Lua的MakeFile文件中CFLAGS选项没有启动-fPIC设置。

解决方法:

进入Lua的源码目录(也就是解压Lua之后的目标目录),使用编辑器打开Makefile(命令:Vim src/Makefile),

注意其第11行,把CFLAGS= -O2 -Wall $(MYCFLAGS) 修改为 CFLAGS= -O2 -Wall -fPIC $(MYCFLAGS),保存..

问题二:

通常我们会首先下载Lua的源码安装包,然后解压,进入目标目录,使用 #make linux 命令编译,但在这里就会出现错误,

系统提示Error;安装步骤中断....

解决方法:

yum install libtermcap-devel [1/4]

yum install ncurses-devel [2/4]

yum install libevent-devel [3/4]

yum install readline-devel [4/4]

安装完以上库,再重新执行make linux,系统不再提示问题,然后执行#make install,安装。

安装以上方法设置好,重新编译安装lua,

能够解决将在下一步编译生成.so动态链接库是产生的system warning和error;

接下来是编译、运行代码

由于这里解决方案是Erlang 使用 driver方法 与C交互,而C直接通过其已有的API与Lua交互,这里会涉及到三种程序代码的编写,因此需要在C代码中,根据与C交互的目标代码的功能而分开独立的代码模块,

如port_dirver.c是专门写与Erlang交互的C代码,c_lua_XX.c是专门保存port_dirver_XX.c通过C调用Lua的函数(即Erlang调用lua,同理,C调用lua),

lua_c_XX.c则是由C程序处理的专门给Lua调用的方法。

Erlang + C:

Erlang driver方法中,C代码中通常需要至少有3个方法,其中为:ErlDrvData (*start)(ErlDrvPort port, char *command, [SysDriverOpts* opts]);

void (*stop)(ErlDrvData drv_data); void (*output)(ErlDrvData drv_data, char *buf, int len);而最多与Erlang直接相关的方法也只能是10个

函数签名定义通常如下:

ErlDrvEntry example_driver_entry = {

/* F_PTR init, N/A */

/* L_PTR start, called when port is opened */

/* F_PTR stop, called when port is closed */

/* F_PTR output, called when erlang has sent */

/* F_PTR ready_input, called when input descriptor ready */

/* F_PTR ready_output, called when output descriptor ready */

/* char *driver_name, the argument to open_port */→→→→→→↓

/* F_PTR finish, called when unloaded */ ↓

/* F_PTR control, port_command callback */ ↓

/* F_PTR timeout, reserved */ ↓

/* F_PTR outputv, reserved */ ↓

}; ↓←←←←←←←←←←←←←←←←←←←←←两个参数值必须一样←←←←←←←←←←←←←←←←←←←←←↓

DRIVER_INIT(example_drv) /* must match name in driver_entry */

{

return &example_driver_entry;

}

对于以上函数中的方法签名,

详细可见Erlang document :http://www.erlang.org/doc/man/driver_entry.html

C + Lua

lua调用C函数时,首先是需要注册函数的,也就是说我们必须把C函数的地址以一个适当的方式传递给lua解析器。

Lua调用C函数,与C调用Lua一样,都是需要使用相同类型的栈来交互,而且这交互的栈并不是全局变量,每个函数都有自己的私有栈。

当Lua调用C函数的时候,第一个参数总是在这个私有栈的index=1的位置。甚至当一个C函数调用Lua代码(Lua代码调用同一个C函数或者其他的C函数),

每一个C函数都有自己的独立的私有栈,并且第一个参数在index=1的位置。

C与lua的交互:

首先需要在C中引入Lua的库,一般程序代码如下:

static void upload_luatest()

{

int error;

L = lua_open(); /*创建一个指向Lua解释器的指针。*/

luaL_openlibs(L); /*函数加载所有Lua库*/

lua_pushcfunction(L, average); /*此处注册将来给Lua调*/

lua_setglobal(L, "avge"); /*用的函数名avge,绑定到C函数average;*/

error = luaL_dofile(L,"divlua.lua"); /*加载.lua脚本文件*/

}

C调用lua时,

lua调用C时,

首先需要先在.c的文件中注册(在调用lua_open()后面适当的加上以下函数:lua_pushcfunction(lua_State* L, FunName);lua_setglobal(lua_State* L,YouFunName)第一个函数将类型为function的值入栈,第二个函数将function赋值给全局变量YouFunName),这样修改之后,重新编译Lua,你就可以在你的Lua程序中使用新的YouFunName函数了。也就是说,一旦一个C函数被注册之后并保存到Lua中,在Lua程序中就可以直接引用他的地址(当我们注册这个函数的时候传递给Lua的地址)来访问这个函数了。

换句话说,一旦C函数被注册之后,Lua调用这个函数并不依赖于函数名,包的位置,或者调用函数的可见的规则。

编译:

进入代码目录,使用gcc编译器编译,命令如下:

gcc -o example_drv.so -I /usr/local/lib/erlang/lib/erl_interface-3.6.2/include/ -I /usr/local/lib/erlang/erts-5.7.2/include/ -I /usr/local/include/ -L /usr/local/lib -fpic -shared -L /usr/local/lib/erlang/lib/erl_interface-3.6.2/lib/ -L ./lua_attr.h complex.c luafun.c port_driver.c -lei -lerl_interface -llua -ldl -lm -Wall

解析: -I 指示在此编译中需要链接的库类文件目录, -L 指明需要链接的库文件或头文件 -fPIC -shared

-lei -lerl_interface 需要使用到Erlang中ei module 和erl_interface module 的BIF,在这里指示标明

-llua -ldl 指示在编译中使用Lua的库,和Lua编译命令

-lm 这命令指明需要链接数学库,因为Lua中需要调用C方法,而这里的C是标准C, 这就相当于Lua直接调用了标准库的C API,因此需要使用-lm链接其数学库。

以上编译成功之后,生成一个.so 的文件

把该文件copy到需要加载这.so的erl源码目录(假设为目录A)中,同时需要在相同目录下添加相关的类库,这里可以通过 ldd 命令来获得运行时加载的动态库,

如:#ldd example_drv.so 显示结果如下:

libdl.so.2 => /lib64/libdl.so.2 (0x00002aafb4b94000)

libm.so.6 => /lib64/libm.so.6 (0x00002aafb4d98000)

libc.so.6 => /lib64/libc.so.6 (0x00002aafb501b000)

/lib64/ld-linux-x86-64.so.2 (0x0000003ca6a00000)

由以上可以,需要把libdl.so.2,libm.so.6,libc.so.6,d-linux-x86-64.so.2copy到源码目录(目录A)中。

到此,Erlang可以加载.so,执行 Erlang+C+lua 的交互。

另:

(1)

what is PIC

PIC stands for Position-independent code and is machine instruction code that executes properly regardless of where in memory it resides.

PIC is commonly used for shared libraries, so that the same library code can be loaded in a location in each program address space where

it won't overlap any other uses of memory (for example, other shared libraries).

(2)

有关在编译.c代码文件时出现的型如:Warning: no newline at end of file的错误

解决方法:

英文的意思就是说文末没有换行符。Unix文档的回车换行符是一个字符\n,

Windows的是分别的两个\n\r,所以在Windows下编辑的最后一个字符是\r不是\n,

而我们很多时候都是在windows环境下编写代码,然后在上传到linux系统上,因此编译器以为有错误。

只要在文件最后补一个新行即可。

在服务器上配置Lua时,需要考虑到该机子是32位的还是64位机子,而且还要根据选择不同的安装Lua方法采取不同的处理方式,

以解决有关-fPIC问题(32bitOS与64bitOS上都存在),和使用源码安装中make出现的warning:mplicit declaration of function ?readline?错

误(在64bitOS上安装时会出现此错误)

首先问题一:

因为这里的配置是专门针对Erlang+C+lua的,在编译.so文件时,需要使用到-fPIC选项,会出现relocation R_X86_64_32 against `luaO_nilobject_'

can not be used when making a shared object;

recompile with -fPIC /usr/local/lib/liblua.a: could not read symbols: Bad value

collect2: ld returned 1 exit status 错误,有关-fPIC问题在其他情况下调用是否出问题,与所调用的链接库或程序安装设置有关;

而这里的错误,是因为默认情况下,Lua的MakeFile文件中CFLAGS选项没有启动-fPIC设置。

解决方法:

进入Lua的源码目录(也就是解压Lua之后的目标目录),使用编辑器打开Makefile(命令:Vim src/Makefile),

注意其第11行,把CFLAGS= -O2 -Wall $(MYCFLAGS) 修改为 CFLAGS= -O2 -Wall -fPIC $(MYCFLAGS),保存..

问题二:

通常我们会首先下载Lua的源码安装包,然后解压,进入目标目录,使用 #make linux 命令编译,但在这里就会出现错误,

系统提示Error;安装步骤中断....

解决方法:

yum install libtermcap-devel [1/4]

yum install ncurses-devel [2/4]

yum install libevent-devel [3/4]

yum install readline-devel [4/4]

安装完以上库,再重新执行make linux,系统不再提示问题,然后执行#make install,安装。

安装以上方法设置好,重新编译安装lua,

能够解决将在下一步编译生成.so动态链接库是产生的system warning和error;

接下来是编译、运行代码

由于这里解决方案是Erlang 使用 driver方法 与C交互,

而C直接通过其已有的API与Lua交互,这里会涉及到三种程序代码的编写,因此需要在C代码中,

根据与C交互的目标代码的功能而分开独立的代码模块,如port_dirver.c是专门写与Erlang交互的C代码,

c_lua_XX.c是专门保存port_dirver_XX.c通过C调用Lua的函数(即Erlang调用lua,同理,C调用lua),

lua_c_XX.c则是由C程序处理的专门给Lua调用的方法。

Erlang + C:

Erlang driver方法中,C代码中通常需要至少有3个方法,

其中为:ErlDrvData (*start)(ErlDrvPort port, char *command, [SysDriverOpts* opts]);void (*stop)(ErlDrvData drv_data);

void (*output)(ErlDrvData drv_data, char *buf, int len);

而最多与Erlang直接相关的方法也只能是10个函数签名定义通常如下:

ErlDrvEntry example_driver_entry = {

NULL, /* F_PTR init, N/A */

example_drv_start, /* L_PTR start, called when port is opened */

example_drv_stop, /* F_PTR stop, called when port is closed */

example_drv_output, /* F_PTR output, called when erlang has sent */

NULL, /* F_PTR ready_input, called when input descriptor ready */

NULL, /* F_PTR ready_output, called when output descriptor ready */

"example_drv", /* char *driver_name, the argument to open_port */

NULL, /* F_PTR finish, called when unloaded */

NULL, /* F_PTR control, port_command callback */

NULL, /* F_PTR timeout, reserved */

NULL /* F_PTR outputv, reserved */

};

DRIVER_INIT(example_drv) /* must match name in driver_entry */

{

return &example_driver_entry;

}

对于以上函数中的方法签名,详细可见Erlang document :http://www.erlang.org/doc/man/driver_entry.html

C + Lua

lua调用C函数时,

首先是需要注册函数的,也就是说我们必须把C函数的地址以一个适当的方式传递给lua解析器。

Lua调用C函数,与C调用Lua一样,都是需要使用相同类型的栈来交互,而且这交互的栈并不是全局变量,每个函数都有自己的私有栈。

当Lua调用C函数的时候,第一个参数总是在这个私有栈的index=1的位置。

甚至当一个C函数调用Lua代码(Lua代码调用同一个C函数或者其他的C函数),

每一个C函数都有自己的独立的私有栈,并且第一个参数在index=1的位置。

C与lua的交互:

首先需要在C中引入Lua的库,一般程序代码如下:

static void upload_luatest(){

int error; L = lua_open(); /*创建一个指向Lua解释器的指针。*/

luaL_openlibs(L); /*函数加载所有Lua库*/

lua_pushcfunction(L, average); /*此处注册将来给Lua调*/

lua_setglobal(L, "avge"); /*用的函数名avge,绑定到C函数average;*/

error = luaL_dofile(L,"divlua.lua"); /*加载.lua脚本文件*/

}

C调用lua时,

首先要获取全局的变量(函数签名,如lua_getglobal( L, "add")),然后使用lua_pushnumber()的方法连续往栈中压进参数数,

再使用lua_call(lua_State *L, int nargs, int nresults)调用该Lua函数。lua调用C时,首先需要先在.c的文件中注册(在调用

lua_open()后面适当的加上以下函数:lua_pushcfunction(lua_State* L, FunName);lua_setglobal(lua_State* L,YouFunName)

第一个函数将类型为function的值入栈,第二个函数将function赋值给全局变量YouFunName),这样修改之后,重新编译Lua,

你就可以在你的Lua程序中使用新的YouFunName函数了。也就是说,一旦一个C函数被注册之后并保存到Lua中,

在Lua程序中就可以直接引用他的地址(当我们注册这个函数的时候传递给Lua的地址)来访问这个函数了。

换句话说,一旦C函数被注册之后,Lua调用这个函数并不依赖于函数名,包的位置,或者调用函数的可见的规则。

编译:

进入代码目录,使用gcc编译器编译,命令如下:

gcc -o example_drv.so -I /usr/local/lib/erlang/lib/erl_interface-3.6.2/include/ -I /usr/local/lib/erlang/erts-5.7.2/include/

-I /usr/local/include/ -L /usr/local/lib -fpic -shared -L /usr/local/lib/erlang/lib/erl_interface-3.6.2/lib/

-L ./lua_attr.h complex.c luafun.c port_driver.c -lei -lerl_interface -llua -ldl -lm -Wall

解析:

-I 指示在此编译中需要链接的库类文件目录,

-L 指明需要链接的库文件或头文件

-fPIC -shared

-lei -lerl_interface 需要使用到Erlang中ei module 和erl_interface module 的BIF,在这里指示标明

-llua -ldl 指示在编译中使用Lua的库,和Lua编译命令

-lm 这命令指明需要链接数学库,因为Lua中需要调用C方法,而这里的C是标准C, 这就相当于Lua直接调用了标准库的C API,

因此需要使用-lm链接其数学库。以上编译成功之后,生成一个.so 的文件;

把该文件copy到需要加载这.so的erl源码目录(假设为目录A)中,同时需要在相同目录下添加相关的类库,

这里可以通过 ldd 命令来获得运行时加载的动态库,

如:#ldd example_drv.so

显示结果如下:

libdl.so.2 => /lib64/libdl.so.2 (0x00002aafb4b94000)

libm.so.6 => /lib64/libm.so.6 (0x00002aafb4d98000)

libc.so.6 => /lib64/libc.so.6 (0x00002aafb501b000)

/lib64/ld-linux-x86-64.so.2 (0x0000003ca6a00000)

由以上可以,需要把libdl.so.2,libm.so.6,libc.so.6,d-linux-x86-64.so.2copy到源码目录(目录A)中。

到此,Erlang可以加载.so,执行 Erlang+C+lua 的交互。

另:

(1)

what is PIC?

PIC stands for Position-independent code and is machine instruction code that executes properly regardless of where in memory it resides. PIC is commonly used for shared libraries, so that the same library code can be loaded in a location in each program address space whereit won't overlap any other uses of memory (for example, other shared libraries).

(2)

有关在编译.c代码文件时出现的型如:Warning: no newline at end of file的错误解决方法:英文的意思就是说文末没有换行符。

Unix文档的回车换行符是一个字符\n,Windows的是分别的两个\n\r,所以在Windows下编辑的最后一个字符是\r不是\n,

而我们很多时候都是在windows环境下编写代码,然后在上传到linux系统上,因此编译器以为有错误。

只要在文件最后补一个新行即可。