Port driver communication witout copy of binaries
Reto Kramer
kramer@REDACTED
Wed Aug 16 00:12:35 CEST 2006
Raimo,
I wish I could replicate your outputv result. Perhaps you could help
me spot my mistake. The example I've added further down (iovec.erl,
iovec_driver.c and build_iovec) is run on OS X 10.4.7 using R11. gcc
is 3.3. The port is opened as binary as it should.
$ ./build_iovec
Erlang (BEAM) emulator version 5.5 [source] [async-threads:32] [hipe]
Eshell V5.5 (abort with ^G)
1>ev->vsize 2
ev-size (total bytes size) 8
ev->binv[0] = null
ev->iov[0]->iov_len = 0
ev->binv[1]->orig_size = 8
ev->iov[1]->iov_len = 8
ev->iov[1]->iov_base[0] = 1
ev->iov[1]->iov_base[1] = 2
ev->iov[1]->iov_base[2] = 3
ev->iov[1]->iov_base[3] = 4
ev->iov[1]->iov_base[4] = 5
ev->iov[1]->iov_base[5] = 6
ev->iov[1]->iov_base[6] = 7
ev->iov[1]->iov_base[7] = 8
^C
In iovec.erl, the following iolist is sent:
encode() ->
[1,<<2,3,4>>,5,6|<<7,8>>].
It's really as if I used "output" (instead of "outputv") in that the
iolist seems to be flattened before handed to the driver (and hence,
copied, which I wanted to avoid by way of using outputv).
The 2nd puzzle is that the first iov entry is always length 0. This
is so, even if I pass (encode) just a single binary.
Any sharp eyed comment on iovec_driver.c would be much appreciated.
- Reto
PS: the async aspect is irrelevant here, I just left it in there
since this is a trimmed version of a larger driver that is async.
------------------------------------------------------------------------
-----
** iovec_driver.c **
#include <stdio.h>
#include "erl_driver.h"
typedef struct {
ErlDrvPort port;
} example_data;
typedef struct {
int res;
} example_async_data;
typedef void ASYNC_INVOKE(void*);
void async_iovectest(void* async_data) {
example_async_data* the_data = (example_async_data*)async_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_outputv(ErlDrvData handle, ErlIOVec *ev) {
example_data* d = (example_data*)handle;
example_async_data* the_async_data = driver_alloc(sizeof
(example_async_data));
ASYNC_INVOKE* fun;
printf("\n\r");
printf("ev->vsize %i\n\r", ev->vsize);
printf("ev-size (total bytes size) %i\n\r", ev->size);
int i;
for (i = 0; i < (ev->vsize); i++) {
SysIOVec b = ev->iov[i];
ErlDrvBinary* binv = ev->binv[i];
if (binv == NULL) {
printf("ev->binv[%i] = null\n\r", i);
} else {
printf("ev->binv[%i]->orig_size = %ld\n\r", i, binv->orig_size);
}
printf("ev->iov[%i]->iov_len = %i\n\r", i, b.iov_len);
int j;
for (j=0; j < b.iov_len; j++) {
printf("ev->iov[%i]->iov_base[%i] = %i\n\r", i, j, b.iov_base
[j]);
}
}
fun = &async_iovectest;
}
static void example_ready_async(ErlDrvData handle, ErlDrvThreadData
async_data)
{
example_data* d = (example_data*)handle;
example_async_data* the_async_data = (example_async_data*)async_data;
ErlDrvTermData spec[] = {
ERL_DRV_PORT, driver_mk_port(d->port),
ERL_DRV_ATOM, driver_mk_atom("ok"),
ERL_DRV_TUPLE, 2 };
driver_output_term(d->port, spec, sizeof(spec) / sizeof(spec[0]));
driver_free(the_async_data);
}
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 */
NULL, /* 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 */
"iovec_drv", /* char *driver_name, the argument to open_port */
NULL, /* finish */
NULL, /* handle */
NULL, /* control */
NULL, /* timeout */
example_drv_outputv, /* outputv */
example_ready_async,
NULL, /* flush */
NULL, /* call */
NULL /* event */
};
DRIVER_INIT(example_drv) /* must match name in driver_entry */ {
return &example_driver_entry;
}
------------------------------------------------------------------------
-----
** iovec.erl **
-module(iovec).
-export([start/1, init/1, test/0]).
-define(DRIVER, iovec_drv).
test() ->
ok = start(?DRIVER),
timer:sleep(100), % HACK: give process some time to start and
load .so
Port = init(?DRIVER),
test(Port).
start(SharedLib) ->
case erl_ddll:load_driver(".", SharedLib) of
ok -> ok;
{error, already_loaded} -> ok;
Error -> exit({error, could_not_load_driver, Error})
end.
init(SharedLib) ->
Port = open_port({spawn, SharedLib}, [binary]),
Port.
test(Port) ->
true = port_command(Port, encode()),
receive
{Port, ok} ->
io:format("ok!~n", []);
{'EXIT', Port, Reason} ->
io:format("~p ~n", [Reason]),
exit(port_terminated)
end.
encode() ->
[1,<<2,3,4>>,5,6|<<7,8>>].
------------------------------------------------------------------------
-----
** build_iovec **
cc -no-cpp-precomp -fPIC -fno-common -bundle -flat_namespace -
undefined suppress -I/usr/local/lib/erlang/usr/include -o
iovec_drv.so complex.c iovec_driver.c && erlc +debug_info iovec.erl
&& erl +A 32 -s iovec test
Thanks,
- Reto
On Mar 28, 2006, at 3:42 AM, Raimo Niskanen wrote:
> What you really want to do can not be done (as far as I know)
> but you might get it done with some tricks...
>
> To avoid copying your driver must implement the
> ->outputv() entry point and you must send it I/O lists
> being lists of binaries (might even be an improper list,
> that is a binary in the tail). You will have to map
> your tuples into that.
>
> If you send [1,<<2,3,4>>,5,6|<<7,8>>] to the driver,
> void (*outputv)(ErlDrvData drv_data, ErlIOVec *ev) will get:
>
> ev->iov[0].iov_len = 1;
> ev->iov[0].iov_base -> {1};
> ev->binv[0] = NULL;
> ev->iov[1].iov_len = 3;
> ev->iov[1].iov_base -> ev->binv[1]->orig_bytes;
> ev->binv[1]->orig_size = 3;
> ev->binv[1]->orig_bytes = {2,3,4};
> ev->iov[2].iov_len = 2;
> ev->iov[2].iov_base -> {5,6};
> ev->binv[2] = NULL;
> ev->iov[3].iov_len = 2;
> ev->iov[3].iov_base -> ev->binv[3]->orig_bytes;
> ev->binv[3]->orig_size = 2;
> ev->binv[3]->orig_bytes = {7,8};
>
> approximately, excuse my syntax :-)
>
> Binaries will be binaries and intermediate bytes
> will be loose vectors. If your driver wants to
> hang on to the data, it will have to use the
> reference count in the binary to avoid premature freeing.
>
> To send data back without copying your driver will
> have to use driver_outputv() and it arrives to erlang as
> a header list of integers followed by a list of
> binaries. Conversion to tuple format will have to
> be done in erlang.
>
> Keep on dreaming...
>
>
>
> Have a look at efile_drv.c in the sources...
>
>
>
> rlenglet@REDACTED (Romain Lenglet) writes:
>
>> Hi,
>>
>>
>> I have the following need: I want to wrap C functions in Erlang.
>> Those functions get big binaries as input parameters, and return
>> big binaries, among other kinds of data.
>> For efficiency, I would like to avoid to copy those binaries
>> around when communicating. Therefore, I am forced to implement a
>> C port driver, since this is the only available mechanism that
>> does not create a separate system process (and hence does not
>> require inter-process data copy when communicating).
>>
>> If I needed only to send one binary in every message, that would
>> be OK, e.g.:
>>
>> % in Erlang:
>> Binary = <<...>>,
>> port_command(Port, Binary),
>>
>> // in the C port implem:
>> void myoutput(ErlDrvData drv_data, char *buf, int len) {
>> ...
>> }
>>
>> I guess that the Binary is not copied, and its data in the Erlang
>> heap is directly pointed by the *buf argument.
>> By the way, is that true??? Sending binaries that way is what is
>> done in prim_inet for sending IP data, so I guess that no copy
>> is done here.
>>
>>
>> However, I want to send and receive more complex data, which must
>> be manipulated by the driver, typically a tuple of simple terms
>> and binaries (which may be large), e.g. the tuple:
>> Tuple = {ContextHandle, QopReq, Message}
>> %% ContextHandle = small binary()
>> %% QopReq = integer() | atom()
>> %% Message = large binary()
>>
>> Such a tuple cannot be passed to port_command(Port, Tuple), since
>> it is not an IO list. And if I encode it into a binary, by
>> calling encode(Tuple), I guess that the binaries in the tuple
>> will get copied in the process (can anybody confirm this?).
>>
>>
>> I have the same problem in the Driver -> Erlang direction, e.g.
>> to send the tuple:
>> Tuple = {MajorStatus, MinorStatus, ConfState, QopState,
>> OutputMessage}
>> %% MajorStatus = integer()
>> %% MinorStatus = integer()
>> %% ConfState = bool()
>> %% QopState = integer()
>> %% OutputMessage = large binary()
>> I hope that using the driver_output_term() C function and the
>> ErlDrvTermData construction technique, the binary data in the
>> tuple above will not be copied. Can anybody confirm this?
>>
>>
>>
>> Is there any clean solution to my problem? Or am I doomed to
>> write my own BIFs and use my custom erts? Or to send data in
>> multiple messages, in sequence?
>>
>> I dream of a way to extend the BIFs list at runtime, by loading
>> native libraries dynamically...
>>
>> --
>> Romain LENGLET
>
> --
>
> / Raimo Niskanen, Erlang/OTP, Ericsson AB
More information about the erlang-questions
mailing list