LoginSignup
9
8

More than 5 years have passed since last update.

【翻訳】Port drivers #Erlang

Posted at

公式ドキュメント[6 Port drivers]の翻訳です。


6 ポートドライバ

ポートドライバ接続を使った例題の解決方法の例
port_driver
Figure 6.1: ポートドライバ通信

6.1 Port Drivers

ポートドライバはErlangプログラムからポートとして利用可能なドライバに接続する。ドライバはシェアードライブラリ(UnixはSOファイル、WindowsはDLLファイル)で、特別なエントリポイントを持つ。Erlangのランタイムはドライバを開始するときとポートへデータを送るときにこれらのエントリポイントを呼び出す。ポートドライバはErlangへデータを送ることもできる。

ポートドライバは仮想的なプロセス( emulator process )へ動的に接続するので、ErlangからCのコードを呼び出す方法の中で最も早い。ポートドライバの中の呼び出された関数はコンテキストスイッチを要求しない。しかし、ポートドライバがクラッシュするとエミュレータも落ちるので、最も危険な方法である。

6.2 Erlangプログラム

ポートプログラムと同じ方法で、ポートがErlangプロセスと通信する。全ての通信はポートドライバの 繋がったErlangプロセス を通る。このプロセスが終了するとポートドライバも閉じる。
ポートが作られる前にドライバをロードする必要がある。これは関数 erl_dll:load_driver/1 で行う。引数として、共有ライブラリの名前が必要である。
ポートはBIF open_port/2 を使って作られる。第一引数はタプル {spawn, DriverName} である。変数 SharedLib はポートドライバの名前である。第2引数はオプションのリストで、後のサンプルでは空である。

-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/1complex5: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.3 Cドライバ

Cドライバは共有ライブラリにコンパイルしてリンクされたモジュールである。ドライバ構造を使い、 erl_driver.h をインクルードしている。
ドライバ構造はドライバの名前と関数ポインタで構成される。特別なエントリポイントを返し、 DRIVER_INIT(<driver_name>) を宣言する。
データを送受信する機能は、関数に結合され、ドライバ構造によって参照されている。ポートへのデータの送信は引数として与えられ、ポートが送り返したデータはCの関数の driver_output で送られる。
ドライバは共有モジュールである(プログラムではない)ので、メイン関数はあるべきではない。すべての関数ポインタは、この例で使用されておらず、driver_entry構造内の対応するフィールドはNULLに設定されている。
ドライバ内の全ての関数は
ドライバの全ての機能は、( start から返される)、ハンドルによって持つ。それはただErlangプロセスによって渡される。これは何らかの方法でポートドライバインスタンスを参照しなければならない。
example_drv_startは、ポートインスタンスのためにハンドルを呼び出す唯一の関数なので、これを守る必要がある。これは、このインスタンスのために割り当てられたドライバ定義の構造体を使用し、参照としてポインタを戻すのが普通である。
これは、グローバル変数を使用するのは良くない。ポートドライバは、複数のErlangのプロセスによって生成することができるので、このドライバ構造体が毎回インスタンス化されるべきです。

TODO: ErlDrvEntryのコメント

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.4 サンプルの実行

1. Compile the C code.

unix> gcc -o exampledrv -fpic -shared complex.c port_driver.c
windows> cl -LD -MD -Fe exampledrv.dll complex.c port_driver.c

2. Start Erlang and compile the Erlang code.

> erl
Erlang (BEAM) emulator version 5.1

Eshell V5.1 (abort with ^G)
1> c(complex5).
{ok,complex5}

3. Run the example.

2> complex5:start("example_drv").
<0.34.0>
3> complex5:foo(3).
4
4> complex5:bar(5).
10
5> complex5:stop().
stop
9
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
8