[erlang-questions] Port Driver, outputv, binaries.

Tony Rogvall tony@REDACTED
Thu Dec 10 11:12:36 CET 2009


Hi!

Yes, the behavior is a bit strange.
But there a number of parameters the control how binaries and io vectors are constructed.
There are 3 types of binary representations internally:

- Heap binaries 
	Binary data is on the process heap.
	This type of binary is constructed when data is less than 64 bytes (may be changed!)

- Proc binaries
	Binary data is on the global heap. ProcBins are reference counted and  are
	mark and sweep kind of things.

- Sub binaries
	Sub binaries refer to either a  part of a ProcBin or a part of a SubBin.

When the compiler compiles binaries they may end up in different flavors, depending on
size and if they are constant or not.

When the shell constructs binaries the result may sometimes be a ProcBin when you really
expected a HeapBin to show up. In your case you really wanted a ProcBin but I thought that
a HeapBin should have been there ;-) The shell actually constructed a SubBin pointing to a
ProcBin maybe some one at the OTP team knows why!?

Next when the runtime system constructs the io vector it will preserve ProcBins and put
them in the vector for zero copy mode. HeapBins are copied in the same way as characters lists.
Everything that is not ProcBin is (internally) copied in to a new binary. Then the io vector entry will point to 
parts of that binary. Note that vectors are only broken up if the runtime hits a ProcBin.

Example:

	[1,2,3,<<100,101,102>>,[5,6,7],<<ProcBin1>>, 8,9,10,<<ProcBin2>>]

Will generate create one area with 
	NewBin = <<1,2,3,100,101,102,5,6,7,8,9,10>>
and the vector will look something like this (sketchy):

	iov[0].base = 0
	iov[0].len = 0
	binv[0] = 0
	( Index 0 is reserved packet byte stuff, header injection)

	iov[1].base = &NewBin
	iov[1].len    = 9
	binv[1] = NewBin

	iov[2].base = &ProcBin1
	iov[2].len    = size(ProcBin1)
	binv[2] = ProcBin1;

	iov[3].base = &NewBin + 9
	iov[3].len = 3
	binv[3] = NewBin

	iov[4].base = &ProcBin2
	iov[4].len    = size(ProcBin2)
	binv[4] = ProcBin2

To summarize. You can not assume how the vector will look like. The idea is that the
runtime system will try to "help" you do zero copy on "large" binaries.

/Tony






On 9 dec 2009, at 19.58, Matthew Sackman wrote:

> Hi,
> 
> So I'm writing a port driver for a linked in binary. I've implemented
> outputv, and that seems to be working fine.
> 
> In order to see what I'm being sent, I have the following in C:
> 
> void dump_ev(ErlIOVec *ev) {
>  printf("total size: %d\r\nvec len: %d\r\n", ev->size, ev->vsize);
>  int idx;
>  for (idx = 0; idx < ev->vsize; ++idx) {
>    printf("iov[%d] = ", idx);
>    SysIOVec iov = ev->iov[idx];
>    printf("[base = %p, len = %zd]\r\n", iov.iov_base, iov.iov_len);
>    printf("binv[%d] = ", idx);
>    if (NULL == ev->binv[idx]) {
>      printf("NULL\r\n");
>    } else {
>      ErlDrvBinary* bin = ev->binv[idx];
>      printf("[orig_bytes = %p; orig_size = %zd]\r\n", bin->orig_bytes, bin->orig_size);
>    }
>  }
>  printf("done\r\n");
> }
> 
> which is called at the start of outputv:
> 
> static void test_outputv(ErlDrvData drv_data, ErlIOVec *ev)
> {
>  dump_ev(ev);
>  ...
> }
> 
> Now, in the shell, I do the following:
> 
> 1> ok = erl_ddll:load_driver("ebin", "libtest"), Port = open_port({spawn_driver, libtest}, [binary]).
> 2> port_command(Port, [<<2>>,<<0,0,0,0,0,0,0,0>>,<<0>>,<<0>>,<<0>>]).
> 
> And I get out the following:
> 
> total size: 12
> vec len: 6
> iov[0] = [base = (nil), len = 0]
> binv[0] = NULL
> iov[1] = [base = 0x17a89b0, len = 1]
> binv[1] = [orig_bytes = 0x17a89b0; orig_size = 1]
> iov[2] = [base = 0x17a89e8, len = 8]
> binv[2] = [orig_bytes = 0x17a89e8; orig_size = 8]
> iov[3] = [base = 0x17a8a20, len = 1]
> binv[3] = [orig_bytes = 0x17a8a20; orig_size = 1]
> iov[4] = [base = 0x17a8a58, len = 1]
> binv[4] = [orig_bytes = 0x17a8a58; orig_size = 1]
> iov[5] = [base = 0x17a8a90, len = 1]
> binv[5] = [orig_bytes = 0x17a8a90; orig_size = 1]
> done
> 
> Which makes a lot of sense - each arg corresponds to an entry in the iov
> and binv. That's all fine.
> 
> Now, in some erlang code (a gen_server) I have this:
> 
> init([]) ->
>    erl_ddll:start(),
>    ok = erl_ddll:load_driver("ebin", ?LIBNAME),
>    Port = open_port({spawn_driver, ?LIBNAME}, [binary, stream]),
>    {ok, Port}.
> 
> handle_call({tune, BNum, APow, FPow, Opts}, _From, Port) ->
>    Data = [<<2/native>>,
>            <<BNum:64/signed-integer-native>>,
>            <<APow:8/signed-integer-native>>,
>            <<FPow:8/signed-integer-native>>,
>            <<Opts:8/native>>],
>    port_command(Port, Data),
>    ...
> 
> Then in the shell I do this:
> 
> 1> {ok, Pid} = test_drv:start_link(), gen_server:call(Pid, {tune, 0, 0, 0, 0}).
> 
> and I get out:
> 
> total size: 12
> vec len: 2
> iov[0] = [base = (nil), len = 0]
> binv[0] = NULL
> iov[1] = [base = 0x1faded0, len = 12]
> binv[1] = [orig_bytes = 0x1faded0; orig_size = 12]
> done
> 
> Why on earth has it gone and pushed all those binaries into one big
> binary? What on earth is the actual behaviour meant to be? Where is it
> defined? How can I reliably pull args out of the iov and binv? Certainly
> the behaviour is different to that in
> http://www.erlang.org/pipermail/erlang-questions/2006-March/019818.html
> 
> In short, WTF?!
> 
> Matthew
> 
> ________________________________________________________________
> erlang-questions mailing list. See http://www.erlang.org/faq.html
> erlang-questions (at) erlang.org
> 



More information about the erlang-questions mailing list