最近在项目中频繁用到erlang的NIF接口,以扩展erlang虚拟机的功能,同时又能提供较高的性能。
NIF(native implemented functions)从R14B开始支持,其功能在于,能够使得erlang module的功能通过c/c++实现。
erlang虚拟机有很多与外部进行功能交互的方式,如通过spawn_executable类型的port调用其它程序,port_driver,nif等,它们各有适应的场合:
spawn_executable类型的port会产生一个外部进程执行命令,不会干扰到erlang虚拟机本身,但是性能较低;
port_driver遵循erlang虚拟机的port机制,功能强大,但需要对虚拟机有较多的了解,编程门槛较高,内嵌入虚拟机,会影响到虚拟机执行,执行结果异步地通过消息队列返回,一些同步的环境下不适合;
nif功能强大但编程接口相对简单,不需要对虚拟机了解太多即可编写,调用一个nif实现的erlang接口就如同调用一个c函数一般,同时也有异步向进程投递消息的能力,极大的提升了erlang虚拟机的扩展能力,缺点也是需要内嵌入虚拟机,会影响到虚拟机执行。
如何利用NIF编写接口,初学者可以看看《erlang otp in action》上的例子,进阶者可以看看一些开源项目,如riak依赖的bitcask、eleveldb、ebloom等等,都是非常好的范例。
本次将分析NIF的部分重要接口的实现,其中涉及到erlang虚拟机的部分仅介绍基本原理而不会深入分析。
照抄官方文档上给出的例子:
NIF实现:
niftest.c
#include "erl_nif.h"
static ERL_NIF_TERM hello(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
return enif_make_string(env, "Hello world!", ERL_NIF_LATIN1);
}
static ErlNifFunc nif_funcs[] =
{
{"hello", 0, hello}
};
ERL_NIF_INIT(niftest,nif_funcs,NULL,NULL,NULL,NULL)
ERL_NIF_INIT的第一个参数是该NIF的模块名,第二个参数是该模块包含的所有可供外部调用的函数定义数组,本例中,niftest即为NIF的模块名,而nif_funcs即为函数定义数组,nif_funcs包含了一个函数定义hello,其参数个数为0,具体实现为c函数static ERL_NIF_TERM hello(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])。
NIF的模块名可以将其与同名的erlang module对应起来。
对于linux平台,按照官方文档给出的编译方法,
gcc -fPIC -shared -o niftest.so niftest.c -I $ERL_ROOT/usr/include/
niftest.c将被编译为共享库niftest.so,以供虚拟机在需要的时候加载。
相应的erlang模块实现:
niftest.erl
-module(niftest).
-export([init/0, hello/0]).
init() ->
erlang:load_nif("./niftest", 0).
hello() ->
"NIF library not loaded".
niftest.erl定义了一个erlang模块niftest,包含一个init函数和一个hello函数,init函数用于初始化模块,通过load_nif函数加载niftest.so,这个定义与NIF对模块和函数的定义是一致的。
官方文档介绍时,明确的给出了调用结果:
1> c(niftest).
{ok,niftest}
2> niftest:hello().
"NIF library not loaded"
3> niftest:init().
ok
4> niftest:hello().
"Hello world!"
这是为什么呢?
首先来看加载时做了什么。
erlang:load_nif/2是一个bif,也即erlang的内建函数,要求必须由NIF对应的erlang module的某个函数直接调用。
erl_nif.c
BIF_RETTYPE load_nif_2(BIF_ALIST_2)
/*该函数的参数BIF_ALIST_2是一个数组,包含两个元素,对应了erlang:load_nif/2两个参数,第一个为共享库文件名,第二个为加载时用户传入的一些私有参数。*/
{
len = list_length(BIF_ARG_1);
if (len < 0) {
BIF_ERROR(BIF_P, BADARG);
}
lib_name = (char *) erts_alloc(ERTS_ALC_T_TMP, len + 1);
if (intlist_to_buf(BIF_ARG_1, lib_name, len) != len) {
erts_free(ERTS_ALC_T_TMP, lib_name);
BIF_ERROR(BIF_P, BADARG);
}
lib_n