Simplest Linked-In Driver EVAH
Posted: March 24th, 2009 | Author: kevin | Filed under: Erlang | View CommentsI’ve been tinkering around with linked-in drivers lately. There are several interesting projects using linked-in drivers so I thought I should learn how to write one.
I quickly discovered a dearth of simple examples on the web. All of the examples I found were either poorly documented or fairly complicated. I really wanted to see a basic “hello, world” style driver so I could see the “flow” of a driver. The closest I came to a minimal example was Cliff Moon’s cherly project.
So, in the finest open source tradition, I’ve taken Cliff’s project and cut it down to its absolute essentials. What’s left is a very basic driver which receives data via port_command/2 and echoes the received data back via driver_output_term().
The C side of the driver is contained in a single source file, basic_driver.c. The structure of the driver is straightforward. First, the driver includes the required header files, declares a struct to contain the driver’s state and exports functions required by erl_driver
#include <erl_driver.h>
#include <ei.h>
#include "config.h"
typedef struct _basic_drv_t {
ErlDrvPort port;
} basic_drv_t;
static ErlDrvData start(ErlDrvPort port, char* cmd);
static void stop(ErlDrvData handle);
static void process(ErlDrvData handle, ErlIOVec *ev);
Next, it defines a static instance of the ErlDrvEntry struct which is used by Erlang to interface with the driver. The driver initialization function is defined using the DRIVER_INIT macro.
static ErlDrvEntry basic_driver_entry = {
NULL, /* init */
start, /* startup */
stop, /* shutdown */
NULL, /* output */
NULL, /* ready_input */
NULL, /* ready_output */
"basic_drv", /* the name of the driver */
NULL, /* finish */
NULL, /* handle */
NULL, /* control */
NULL, /* timeout */
process, /* process */
NULL, /* ready_async */
NULL, /* flush */
NULL, /* call */
NULL, /* event */
ERL_DRV_EXTENDED_MARKER, /* ERL_DRV_EXTENDED_MARKER */
ERL_DRV_EXTENDED_MAJOR_VERSION, /* ERL_DRV_EXTENDED_MAJOR_VERSION */
ERL_DRV_EXTENDED_MAJOR_VERSION, /* ERL_DRV_EXTENDED_MINOR_VERSION */
ERL_DRV_FLAG_USE_PORT_LOCKING /* ERL_DRV_FLAGs */
};
DRIVER_INIT(basic_drv) {
return &basic_driver_entry;
}
Driver startup and teardown is handled by the start() and stop() functions. Since the driver doesn’t do very much, these functions merely allocate and free memory associated with the driver’s state.
static ErlDrvData start(ErlDrvPort port, char* cmd) {
basic_drv_t* retval = (basic_drv_t*) driver_alloc(sizeof(basic_drv_t));
retval->port = port;
return (ErlDrvData) retval;
}
static void stop(ErlDrvData handle) {
basic_drv_t* driver_data = (basic_drv_t*) handle;
driver_free(driver_data);
}
The real work is performed in process(). The function is called with the driver’s current state and a vector, or list, of binaries sent by the caller. The driver unbundles the user data from the vector, wraps it up in a tuple and send it back to the caller.
static void process(ErlDrvData handle, ErlIOVec *ev) {
basic_drv_t* driver_data = (basic_drv_t*) handle;
ErlDrvBinary* data = ev->binv[1];
ErlDrvTermData spec[] = {ERL_DRV_ATOM, driver_mk_atom("ok"),
ERL_DRV_BINARY, (ErlDrvTermData) data, data->orig_size, 0,
ERL_DRV_TUPLE, 2};
driver_output_term(driver_data->port, spec, sizeof(spec) / sizeof(spec[0]));
}
Finally, the driver is loaded and started from Erlang code in the Erlang module basic_driver.
-module(basic_driver).
-export([test/0, test/1]).
test(Message) when is_list(Message) ->
{ok, P} = load_driver(),
port_command(P, list_to_binary(Message)),
receive
Data ->
io:format("Data: ~p~n", [Data])
after 100 ->
io:format("Received nothing!~n")
end,
port_close(P).
test() ->
{ok, P} = load_driver(),
port_command(P, [<<"a">>, <<"b">>, <<"c">>]),
receive
Data ->
io:format("Data: ~p~n", [Data])
after 100 ->
io:format("Received nothing!~n")
end,
port_close(P).
%% Private functions
load_driver() ->
SearchDir = filename:join([filename:dirname(code:which(basic_driver)), "..", "priv"]),
case erl_ddll:load(SearchDir, "basic_drv") of
ok ->
{ok, open_port({spawn, 'basic_drv'}, [binary])};
Error ->
Error
end.
Source code for the basic_driver project is available here.