6.端口驱动 | 6. Port Drivers
6 端口驱动程序
本节概述了如何Problem Example
使用链接的端口驱动程序解决示例问题的示例。
端口驱动程序是一个链接的驱动程序,可以从Erlang程序中作为端口访问。它是一个共享库(UNIX中的SO,Windows中的DLL),带有特殊的入口点。Erlang运行时系统在启动驱动程序和将数据发送到端口时调用这些入口点。端口驱动程序也可以发送数据给Erlang。
由于端口驱动程序是动态链接到仿真程序的,所以这是从Erlang调用C代码的最快方法。在端口驱动程序中调用函数不需要上下文切换。但它也是最不安全的方式,因为端口驱动程序崩溃也会导致仿真器失效。
该方案如下图所示:
图6.1:端口驱动程序通信
6.1 Erlang程序
像端口程序一样,端口与Erlang进程
通信。所有的通信都通过一个Erlang进程
,这个进程
是端口驱动程序的连接进程
。终止此过程将关闭端口驱动程序。
在创建端口之前,必须加载驱动程序。这是通过函数完成的erl_dll:load_driver/1
,共享库的名称作为参数。
然后使用BIF创建端口open_port/2
,并将元组{spawn, DriverName}
作为第一个参数。该字符串SharedLib
是端口驱动程序的名称。第二个参数是选项列表,在这种情况下是none:
-module(complex5).
-export([start/1, init/1]).
start(SharedLib) ->
case erl_ddll:load_driver(".", SharedLib) of
ok -> ok;
{error, already_loaded} -> ok;
_ -> exit{error, could_not_load_driver})
end,
spawn(?MODULE, init, [SharedLib]).
init(SharedLib) ->
register(complex, self()),
Port = open_port{spawn, SharedLib}, []),
loop(Port).
现在complex5:foo/1
和complex5:bar/1
可以实现。两者都向该complex
流程发送消息并收到以下回复:
foo(X) ->
call_port{foo, X}).
bar(Y) ->
call_port{bar, Y}).
call_port(Msg) ->
complex ! {call, self(), Msg},
receive
{complex, Result} ->
Result
end.
complex
过程执行以下操作:
- 将消息编码为一系列字节。
- 将其发送到端口。
- 等待回复。
- 解码答复。
- 将其发回给调用者:
loop(Port) ->
receive
{call, Caller, Msg} ->
Port ! {self(), {command, encode(Msg)}},
receive
{Port, {data, Data}} ->
Caller ! {complex, decode(Data)}
end,
loop(Port)
end.
假设C函数的参数和结果都小于256,则采用简单的编码/解码方案。在这个方案中,foo
由字节1 bar
表示,由2表示,并且参数/结果也由单个字节表示:
encode{foo, X}) -> [1, X];
encode{bar, Y}) -> [2, Y].
decode([Int]) -> Int.
生成的Erlang程序包括停止端口和检测端口故障的功能如下:
-module(complex5).
-export([start/1, stop/0, init/1]).
-export([foo/1, bar/1]).
start(SharedLib) ->
case erl_ddll:load_driver(".", SharedLib) of
ok -> ok;
{error, already_loaded} -> ok;
_ -> exit{error, could_not_load_driver})
end,
spawn(?MODULE, init, [SharedLib]).
init(SharedLib) ->
register(complex, self()),
Port = open_port{spawn, SharedLib}, []),
loop(Port).
stop() ->
complex ! stop.
foo(X) ->
call_port{foo, X}).
bar(Y) ->
call_port{bar, Y}).
call_port(Msg) ->
complex ! {call, self(), Msg},
receive
{complex, Result} ->
Result
end.
loop(Port) ->
receive
{call, Caller, Msg} ->
Port ! {self(), {command, encode(Msg)}},
receive
{Port, {data, Data}} ->
Caller ! {complex, decode(Data)}
end,
loop(Port
stop ->
Port ! {self(), close},
receive
{Port, closed} ->
exit(normal)
end;
{'EXIT', Port, Reason} ->
io:format("~p ~n", [Reason]),
exit(port_terminated)
end.
encode{foo, X}) -> [1, X];
encode{bar, Y}) -> [2, Y].
decode([Int]) -> Int.
6.2 C驱动器
C驱动程序是一个编译并链接到共享库的模块。它使用驱动程序结构并包含头文件erl_driver.h
。
驱动程序结构填充了驱动程序名称和函数指针。它是从宏指定的特殊入口点返回的DRIVER_INIT(<driver_name>)。
接收和发送数据的功能被组合成一个由驱动器结构指出的功能。发送到端口的数据作为参数给出,并且回复的数据与C函数一起发送driver_output
。
由于驱动程序是共享模块,而不是程序,因此不存在主要功能。在这个例子中,所有函数指针都没有使用,并且driver_entry
结构中的相应字段被设置为NULL。
驱动程序中的所有函数都接收一个start
刚刚由Erlang进程传递的句柄(返回)。这必须以某种方式引用端口驱动程序实例。
example_drv_start
是唯一被调用的端口实例句柄,因此必须保存。习惯上使用一个分配的驱动程序定义的结构,并将指针传回作为参考。
使用全局变量并不是一个好主意,因为端口驱动程序可以由多个Erlang进程产生。这个驱动程序结构将被多次实例化:
/* port_driver.c */
#include <stdio.h>
#include "erl_driver.h"
typedef struct {
ErlDrvPort port;
} example_data;
static ErlDrvData example_drv_start(ErlDrvPort port, char *buff)
{
example_data* d = (example_data*)driver_alloc(sizeof(example_data)
d->port = port;
return (ErlDrvData)d;
}
static void example_drv_stop(ErlDrvData handle)
{
driver_free((char*)handle
}
static void example_drv_output(ErlDrvData handle, char *buff,
ErlDrvSizeT bufflen)
{
example_data* d = (example_data*)handle;
char fn = buff[0], arg = buff[1], res;
if (fn == 1) {
res = foo(arg
} else if (fn == 2) {
res = bar(arg
}
driver_output(d->port, &res, 1
}
ErlDrvEntry example_driver_entry = {
NULL, /* F_PTR init, called when driver is loaded */
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, /* void *handle, Reserved by VM */
NULL, /* F_PTR control, port_command callback */
NULL, /* F_PTR timeout, reserved */
NULL, /* F_PTR outputv, reserved */
NULL, /* F_PTR ready_async, only for async drivers */
NULL, /* F_PTR flush, called when port is about
to be closed, but there is data in driver
queue */
NULL, /* F_PTR call, much like control, sync call
to driver */
NULL, /* F_PTR event, called when an event selected
by driver_event() occurs. */
ERL_DRV_EXTENDED_MARKER, /* int extended marker, Should always be
set to indicate driver versioning */
ERL_DRV_EXTENDED_MAJOR_VERSION, /* int major_version, should always be
set to this value */
ERL_DRV_EXTENDED_MINOR_VERSION, /* int minor_version, should always be
set to this value */
0, /* int driver_flags, see documentation */
NULL, /* void *handle2, reserved for VM use */
NULL, /* F_PTR process_exit, called when a
monitored process dies */
NULL /* F_PTR stop_select, called to close an
event object */
};
DRIVER_INIT(example_drv) /* must match name in driver_entry */
{
return &example_driver_entry;
}
6.3运行示例
第一步。
编译C代码:
unix> gcc -o example_drv.so -fpic -shared complex.c port_driver.c
windows> cl -LD -MD -Fe example_drv.dll complex.c port_driver.c
第二步。
启动Erlang并编译Erlang代码:
> erl
Erlang (BEAM) emulator version 5.1
Eshell V5.1 (abort with ^G)
1> c(complex5).
{ok,complex5}
第三步。
运行示例:
2> complex5:start("example_drv").
<0.34.0>
3> complex5:foo(3).
4
4> complex5:bar(5).
10
5> complex5:stop().
stop