As of erts version 5.5.3 the driver interface has been extended
(see
As of erts version 5.9 old drivers have to be recompiled
and have to use the extended interface. They also have to be
adjusted to the
The driver calls back to the emulator, using the API
functions declared in
A driver is a library with a set of function that the emulator calls, in response to Erlang functions and message sending. There may be multiple instances of a driver, each instance is connected to an Erlang port. Every port has a port owner process. Communication with the port is normally done through the port owner process.
Most of the functions take the
Some of the functions take a parameter of type
Many of the output functions have a "header buffer", with
In the runtime system with SMP support, drivers are locked either
on driver level or port level (driver instance level). By default
driver level locking will be used, i.e., only one emulator thread
will execute code in the driver at a time. If port level locking
is used, multiple emulator threads may execute code in the driver
at the same time. There will only be one thread at a time calling
driver call-backs corresponding to the same port, though. In order
to enable port level locking set the
Most drivers written before the runtime system with SMP support existed will be able to run in the runtime system with SMP support without being rewritten if driver level locking is used.
It is assumed that drivers do not access other drivers. If drivers should access each other they have to provide their own mechanism for thread safe synchronization. Such "inter driver communication" is strongly discouraged.
Previously, in the runtime system without SMP support, specific driver call-backs were always called from the same thread. This is not the case in the runtime system with SMP support. Regardless of locking scheme used, calls to driver call-backs may be made from different threads, e.g., two consecutive calls to exactly the same call-back for exactly the same port may be made from two different threads. This will for most drivers not be a problem, but it might. Drivers that depend on all call-backs being called in the same thread, have to be rewritten before being used in the runtime system with SMP support.
Regardless of locking scheme used, calls to driver call-backs may be made from different threads.
Most functions in this API are not thread-safe, i.e., they may not be called from an arbitrary thread. Functions that are not documented as thread-safe may only be called from driver call-backs or function calls descending from a driver call-back call. Note that driver call-backs may be called from different threads. This, however, is not a problem for any function in this API, since the emulator has control over these threads.
Functions not explicitly documented as thread safe are not thread safe. Also note that some functions are only thread safe when used in a runtime system with SMP support.
A function not explicitly documented as thread safe may at some point in time have a thread safe implementation in the runtime system. Such an implementation may however change to a thread unsafe implementation at any time without any notice at all.
Only use functions explicitly documented as thread safe from arbitrary threads.
All functions that a driver needs to do with Erlang are performed through driver API functions. There are functions for the following functionality:
Every driver instance has an associated queue. This queue is a
The queue can be manipulated from arbitrary threads if
a port data lock is used. See documentation of the
A POSIX thread like API for multi-threading is provided. The Erlang driver thread API only provide a subset of the functionality provided by the POSIX thread API. The subset provided is more or less the basic functionality needed for multi-threaded programming:
The Erlang driver thread API can be used in conjunction with the POSIX thread API on UN-ices and with the Windows native thread API on Windows. The Erlang driver thread API has the advantage of being portable, but there might exist situations where you want to use functionality from the POSIX thread API or the Windows native thread API.
The Erlang driver thread API only returns error codes when it is reasonable to recover from an error condition. If it isn't reasonable to recover from an error condition, the whole runtime system is terminated. For example, if a create mutex operation fails, an error code is returned, but if a lock operation on a mutex fails, the whole runtime system is terminated.
Note that there exists no "condition variable wait with timeout" in
the Erlang driver thread API. This is due to issues with
In order for the Erlang driver thread API to function, thread
support has to be enabled in the runtime system. An Erlang driver
can check if thread support is enabled by use of
NOTE: When executing in an emulator thread, it is very important that you unlock all locks you have locked before letting the thread out of your control; otherwise, you are very likely to deadlock the whole emulator. If you need to use thread specific data in an emulator thread, only have the thread specific data set while the thread is under your control, and clear the thread specific data before you let the thread out of your control.
In the future there will probably be debug functionality
integrated with the Erlang driver thread API. All functions
that create entities take a
A driver can add and later remove drivers.
A driver can monitor a process that does not own a port.
Version management is enabled for drivers that have set the
The emulator will refuse to load a driver that does not use
the extended driver interface since,
to allow for 64-bit capable drivers,
incompatible type changes for the callbacks
Therefore it is not enough to just recompile drivers written with version management for pre-R15B types; the types have to be changed in the driver suggesting other rewrites especially regarding size variables. Investigate all warnings when recompiling!
Also, the API driver functions
For erts-5.9 two new integer types
To not update a driver and just recompile it probably works when building for a 32-bit machine creating a false sense of security. Hopefully that will generate many important warnings. But when recompiling the same driver later on for a 64-bit machine there will be warnings and almost certainly crashes. So it is a BAD idea to postpone updating the driver and not fixing the warnings!
When recompiling with
Here follows a checklist for rewriting a pre erts-5.9 driver, most important first.
Rewrite driver callback
Rewrite driver callback
These changes are essential to not crash the emulator or worse cause malfunction. Without them a driver may return garbage in the high 32 bits to the emulator causing it to build a huge result from random bytes either crashing on memory allocation or succeeding with a random result from the driver call.
Driver callback
Driver callback
Driver callback
Sane compiler's calling conventions probably make these changes
necessary only for a driver to handle data chunks that require
64-bit size fields (mostly larger than 2 GB since that is what
an
The argument type change is from signed to unsigned which may cause problems for e.g. loop termination conditions or error conditions if you just change the types all over the place.
The
Automatic type casting probably makes these changes necessary only for a driver that encounters sizes larger than 32 bits.
The
Many driver API functions have changed argument type
and/or return value to
This is a change from signed to unsigned which may cause problems for e.g. loop termination conditions and error conditions if you just change the types all over the place.
An unsigned integer type to be used as
A signed integer type the size of
typedef struct ErlDrvSysInfo {
int driver_major_version;
int driver_minor_version;
char *erts_version;
char *otp_release;
int thread_support;
int smp_support;
int async_threads;
int scheduler_threads;
int nif_major_version;
int nif_minor_version;
} ErlDrvSysInfo;
The
typedef struct ErlDrvBinary {
ErlDrvSint orig_size;
char orig_bytes[];
} ErlDrvBinary;
The
The
Some driver calls, such as
Using a driver binary instead of a normal buffer, is often faster, since the emulator doesn't need to copy the data, only the pointer is used.
A driver binary allocated in the driver, with
Driver binaries are used in the
If the driver for some reason or another, wants to keep a
driver binary around, in a static variable for instance, the
reference count should be incremented,
and the binary can later be freed in the
Note that since a driver binary is shared by the driver and the emulator, a binary received from the emulator or sent to the emulator, must not be changed by the driver.
Since erts version 5.5 (OTP release R11B), orig_bytes is guaranteed to be properly aligned for storage of an array of doubles (usually 8-byte aligned).
The
This is a system I/O vector, as used by
typedef struct ErlIOVec {
int vsize;
ErlDrvSizeT size;
SysIOVec* iov;
ErlDrvBinary** binv;
} ErlIOVec;
The I/O vector used by the emulator and drivers, is a list
of binaries, with a
When a driver creates a monitor for a process, a
The driver writer should provide the memory for storing the
monitor when calling
The
If certain port specific data have to be accessed from other threads than those calling the driver call-backs, a port data lock can be used in order to synchronize the operations on the data. Currently, the only port specific data that the emulator associates with the port data lock is the driver queue.
Normally a driver instance does not have a port data lock. If
the driver instance wants to use a port data lock, it has to
create the port data lock by calling
A port data lock is reference counted, and when the reference
count reaches zero, it will be destroyed. The emulator will at
least increment the reference count once when the lock is
created and decrement it once when the port associated with
the lock terminates. The emulator will also increment the
reference count when an async job is enqueued and decrement
it after an async job has been invoked, or canceled. Besides
this, it is the responsibility of the driver to ensure that
the reference count does not reach zero before the last use
of the lock by the driver has been made. The reference count
can be read, incremented, and decremented, respectively, by
use of
Thread identifier.
See also:
int suggested_stack_size;
Thread options structure passed to
See also:
Mutual exclusion lock. Used for synchronizing access to shared data. Only one thread at a time can lock a mutex.
See also:
Condition variable. Used when threads need to wait for a specific condition to appear before continuing execution. Condition variables need to be used with associated mutexes.
See also:
Read/write lock. Used to allow multiple threads to read shared data while only allowing one thread to write the same data. Multiple threads can read lock an rwlock at the same time, while only one thread can read/write lock an rwlock at a time.
See also:
Key which thread specific data can be associated with.
See also:
This function will write information about the Erlang runtime
system into the
See the documentation of the
The
The data is queued in the port owner process' message queue. Note that this does not yield to the emulator. (Since the driver and the emulator run in the same thread.)
The parameter
The return value for all output functions is 0. (Unless the driver is used for distribution, in which case it can fail and return -1. For normal use, the output function always returns 0.)
The
The point of sending data as a list header, is to facilitate matching on the data received.
The return value is 0 for normal use.
This function sends data to port owner process from a
driver binary, it has a header buffer (
The parameter
Driver binaries are created with
The data in the header is sent as a list and the binary as an Erlang binary in the tail of the list.
E.g. if
The return value is 0 for normal use.
Note that, using the binary syntax in Erlang, the driver application can match the header directly from the binary, so the header can be put in the binary, and hlen can be set to 0.
This function sends data from an IO vector,
The
You get vectors of
E.g. if
The return value is 0 for normal use.
The comment for
This function collects several segments of data, referenced
by
If the data is to be sent from the driver to the port owner
process, it is faster to use
The return value is the space left in the buffer, i.e. if
the
This function sets a timer on the driver, which will count
down and call the driver when it is timed out. The
When the timer reaches 0 and expires, the driver entry
function
Note that there is only one timer on each driver instance; setting a new timer will replace an older one.
Return value is 0 (-1 only when the
This function cancels a timer set with
The return value is 0.
This function reads the current time of a timer, and places
the result in
The return value is 0.
This function reads a timestamp into the memory pointed to by
the parameter
The return value is 0 unless the
This function is used by drivers to provide the emulator with events to check for. This enables the emulator to call the driver when something has happened asynchronously.
The
The
The
Some OS (Windows) do not differentiate between read and write events.
The call-back for a fired event then only depends on the value of
ERL_DRV_USE was added in OTP release R13. Old drivers will still work
as before. But it is recommended to update them to use
The return value is 0 (failure, -1, only if the
This function allocates a memory block of the size specified
in
Memory allocated must be explicitly freed with a corresponding
call to
This function is thread-safe.
This function resizes a memory block, either in place, or by
allocating a new block, copying the data and freeing the old
block. A pointer is returned to the reallocated memory. On
failure (out of memory),
This function is thread-safe.
This function frees the memory pointed to by
This function is thread-safe.
This function allocates a driver binary with a memory block
of at least
Note that a driver binary has an internal reference counter,
this means that calling
The driver binary has a field,
This function is thread-safe.
This function resizes a driver binary, while keeping the
data. The resized driver binary is returned. On failure (out
of memory),
This function is only thread-safe when the emulator with SMP support is used.
This function frees a driver binary
This function is only thread-safe when the emulator with SMP support is used.
Returns current reference count on
This function is only thread-safe when the emulator with SMP support is used.
Increments the reference count on
This function is only thread-safe when the emulator with SMP support is used.
Decrements the reference count on
This function is only thread-safe when the emulator with SMP support is used.
You should normally decrement the reference count of a
driver binary by calling
This function enqueues data in the driver queue. The data in
The driver queue is available to queue output from the emulator to the driver (data from the driver to the emulator is queued by the emulator in normal erlang message queues). This can be useful if the driver has to wait for slow devices etc, and wants to yield back to the emulator. The driver queue is implemented as an ErlIOVec.
When the queue contains data, the driver won't close, until the queue is empty.
The return value is 0.
This function can be called from an arbitrary thread if a
This function puts data at the head of the driver queue. The
data in
The return value is 0.
This function can be called from an arbitrary thread if a
This function dequeues data by moving the head pointer
forward in the driver queue by
The return value is the number of bytes remaining in the queue or -1 on failure.
This function can be called from an arbitrary thread if a
This function returns the number of bytes currently in the driver queue.
This function can be called from an arbitrary thread if a
This function enqueues a driver binary in the driver
queue. The data in
This function can be called from an arbitrary thread if a
The return value is 0.
This function puts data in the binary
This function can be called from an arbitrary thread if a
The return value is 0.
This function retrieves the driver queue into a supplied
If
Nothing is removed from the queue by this function, that must be done
with
This function can be called from an arbitrary thread if a
This function retrieves the driver queue as a pointer to an
array of
Nothing is removed from the queue by this function, that must be done
with
The returned array is suitable to use with the Unix system
call
This function can be called from an arbitrary thread if a
This function enqueues the data in
The return value is 0.
This function can be called from an arbitrary thread if a
This function puts the data in
The return value is 0.
This function can be called from an arbitrary thread if a
This function creates a port data lock associated with
the
On success a newly created port data lock is returned. On
failure
This function locks the port data lock passed as argument
(
This function is thread-safe.
This function unlocks the port data lock passed as argument
(
This function is thread-safe.
This function returns the current reference count of
the port data lock passed as argument (
This function is thread-safe.
This function increments the reference count of
the port data lock passed as argument (
The current reference count after the increment has been performed is returned.
This function is thread-safe.
This function decrements the reference count of
the port data lock passed as argument (
The current reference count after the decrement has been performed is returned.
This function is thread-safe.
Start monitoring a process from a driver. When a process is
monitored, a process exit will result in a call to the
provided
The
The function returns 0 on success, < 0 if no call-back is provided and > 0 if the process is no longer alive.
This function cancels a monitor created earlier.
The function returns 0 if a monitor was removed and > 0 if the monitor did no longer exist.
The function returns the process id associated with a living
monitor. It can be used in the
The function returns
This function is used to compare two
The function returns 0 if
This function adds a driver entry to the list of drivers
known by Erlang. The
To use this function for adding drivers residing in
dynamically loaded code is dangerous. If the driver code
for the added driver resides in the same dynamically
loaded module (i.e.
Use of this function is generally deprecated.
This function removes a driver entry
Driver entries added by the
This function returns the atom name of the erlang error,
given the error number in
Sets and gets limits that will be used for controling the busy state of the port message queue.
The port message queue will be set into a busy
state when the amount of command data queued on the
message queue reaches the
Valid limits are values in the range
By passing a pointer to an integer variable containing
the value
The busy message queue feature can be disabled either
by setting the
Processes sending command data to the port will be suspended if either the port is busy or if the port message queue is busy. Suspended processes will be resumed when neither the port is busy, nor the port message queue is busy.
For information about busy port functionality
see the documentation of the
This function set and unset the busy state of the port. If
Processes sending command data to the port will be suspended
if either the port is busy or if the port message queue
is busy. Suspended processes will be resumed when neither the
port is busy, nor the port message queue is busy. Command data
is in this context data passed to the port using either
If the
For information about busy port message queue functionality
see the documentation of the
This function sets flags for how the
Currently there are only two meaningful values for
This function signals to erlang that the driver has
encountered an EOF and should be closed, unless the port was
opened with the
The return value is 0.
These functions signal to Erlang that the driver has
encountered an error and should be closed. The port is
closed and the tuple
The driver should fail only when in severe error situations,
when the driver cannot possibly keep open, for instance
buffer allocation gets out of memory. For normal errors
it is more appropriate to send error codes with
The return value is 0.
This function returns the port owner process.
Note that this function is not thread-safe, not even when the emulator with SMP support is used.
This function returns the process id of the process that
made the current call to the driver. The process id can be
used with
Note that this function is not thread-safe, not even when the emulator with SMP support is used.
This functions sends data in the special driver term
format to the port owner process. This is a fast way to
deliver term data from a driver. It also needs no binary
conversion, so the port owner process receives data as
normal Erlang terms. The
Note that the
The
Tuple and lists (with the exception of strings, see below), are built in reverse polish notation, so that to build a tuple, the elements are given first, and then the tuple term, with a count. Likewise for lists.
A tuple must be specified with the number of elements. (The
elements precede the
A list must be specified with the number of elements,
including the tail, which is the last term preceding
The special term
Term type Argument(s) =========================================== ERL_DRV_NIL ERL_DRV_ATOM ErlDrvTermData atom (from driver_mk_atom(char *string)) ERL_DRV_INT ErlDrvSInt integer ERL_DRV_UINT ErlDrvUInt integer ERL_DRV_INT64 ErlDrvSInt64 *integer_ptr ERL_DRV_UINT64 ErlDrvUInt64 *integer_ptr ERL_DRV_PORT ErlDrvTermData port (from driver_mk_port(ErlDrvPort port)) ERL_DRV_BINARY ErlDrvBinary *bin, ErlDrvUInt len, ErlDrvUInt offset ERL_DRV_BUF2BINARY char *buf, ErlDrvUInt len ERL_DRV_STRING char *str, int len ERL_DRV_TUPLE int sz ERL_DRV_LIST int sz ERL_DRV_PID ErlDrvTermData pid (from driver_connected(ErlDrvPort port) or driver_caller(ErlDrvPort port)) ERL_DRV_STRING_CONS char *str, int len ERL_DRV_FLOAT double *dbl ERL_DRV_EXT2TERM char *buf, ErlDrvUInt len
The unsigned integer data type
The unsigned integer data type
To build the tuple
Where
The term
The
The
orig_bytes, binp->orig_size
ERL_DRV_TUPLE, 2,
};
erl_drv_output_term(driver_mk_port(drvport), spec, sizeof(spec) / sizeof(spec[0]));
]]>
If you want to pass a binary and don't already have the content
of the binary in an
The
This function is only thread-safe when the emulator with SMP support is used.
The parameters
Note that this function is not thread-safe, not even when the emulator with SMP support is used.
This function returns an atom given a name
Note that this function is not thread-safe, not even when the emulator with SMP support is used.
This function converts a port handle to the erlang term
format, usable in the
Note that this function is not thread-safe, not even when the emulator with SMP support is used.
This function is the only way for a driver to send data to
other processes than the port owner process. The
Note that the
The parameters
This function is only thread-safe when the emulator with SMP support is used.
Also note that parameters of
The parameters
This function is only thread-safe when the emulator with SMP support is used.
This function performs an asynchronous call. The function
Erlang is by default started without an async thread pool. The
number of async threads that the runtime system should use
is specified by the
If there is a thread pool available, a thread will be
used. If the
To make sure that a driver instance always uses the same thread, the following call can be used:
It is enough to initialize
If a thread is already working, the calls will be queued up and executed in order. Using the same thread for each driver instance ensures that the calls will be made in sequence.
The
When the async operation is done,
The return value is a handle to the asynchronous task, which
can be used as argument to
As of erts version 5.5.4.3 the default stack size for
threads in the async-thread pool is 16 kilowords,
i.e., 64 kilobyte on 32-bit architectures.
This small default size has been chosen since the
amount of async-threads might be quite large. The
default stack size is enough for drivers delivered
with Erlang/OTP, but might not be sufficiently large
for other dynamically linked in drivers that use the
driver_async() functionality. A suggested stack size
for threads in the async-thread pool can be configured
via the
This function used to cancel a scheduled asynchronous operation, if it was still in the queue. It returned 1 if it succeeded, and 0 if it failed.
Since it could not guarantee success, it was more or less useless.
The user had to implement synchronization of cancellation anyway.
It also unnecessarily complicated the implementation. Therefore,
as of OTP-R15B
This function locks the driver used by the port
This function creates a new port executing the same driver code as the port creating the new port. A short description of the arguments:
The caller of
When
Arguments:
This function creates a new thread. On success
You are not allowed to allocate the
The created thread will terminate either when
All created threads need to be joined by the driver before it is unloaded. If the driver fails to join all threads created before it is unloaded, the runtime system will most likely crash when the code of the driver is unloaded.
This function is thread-safe.
Arguments:
This function allocates and initialize a thread option
structure. On failure
You are not allowed to allocate the
This function is thread-safe.
Arguments:
This function destroys thread options previously created by
This function is thread-safe.
Arguments:
This function terminates the calling thread with the exit
value passed as argument. You are only allowed to terminate
threads created with
This function is thread-safe.
Arguments:
This function joins the calling thread with another thread, i.e.,
the calling thread is blocked until the thread identified by
This function is thread-safe.
This function returns the thread identifier of the calling thread.
This function is thread-safe.
Arguments:
This function compares two thread identifiers for equality,
and returns
A Thread identifier may be reused very quickly after
a thread has terminated. Therefore, if a thread
corresponding to one of the involved thread identifiers
has terminated since the thread identifier was saved,
the result of
This function is thread-safe.
Arguments:
This function creates a mutex and returns a pointer to it. On
failure
This function is thread-safe.
Arguments:
This function destroys a mutex previously created by
This function is thread-safe.
Arguments:
This function locks a mutex. The calling thread will be blocked until the mutex has been locked. A thread which currently has locked the mutex may not lock the same mutex again.
If you leave a mutex locked in an emulator thread when you let the thread out of your control, you will very likely deadlock the whole emulator.
This function is thread-safe.
Arguments:
This function tries to lock a mutex. If successful
If you leave a mutex locked in an emulator thread when you let the thread out of your control, you will very likely deadlock the whole emulator.
This function is thread-safe.
Arguments:
This function unlocks a mutex. The mutex currently has to be locked by the calling thread.
This function is thread-safe.
Arguments:
This function creates a condition variable and returns a
pointer to it. On failure
This function is thread-safe.
Arguments:
This function destroys a condition variable previously
created by
This function is thread-safe.
Arguments:
This function signals on a condition variable. That is, if other threads are waiting on the condition variable being signaled, one of them will be woken.
This function is thread-safe.
Arguments:
This function broadcasts on a condition variable. That is, if other threads are waiting on the condition variable being broadcasted on, all of them will be woken.
This function is thread-safe.
Arguments:
This function waits on a condition variable. The calling thread is blocked until another thread wakes it by signaling or broadcasting on the condition variable. Before the calling thread is blocked it unlocks the mutex passed as argument, and when the calling thread is woken it locks the same mutex before returning. That is, the mutex currently has to be locked by the calling thread when calling this function.
This function is thread-safe.
Arguments:
This function creates an rwlock and returns a pointer to it. On
failure
This function is thread-safe.
Arguments:
This function destroys an rwlock previously created by
This function is thread-safe.
Arguments:
This function read locks an rwlock. The calling thread will be blocked until the rwlock has been read locked. A thread which currently has read or read/write locked the rwlock may not lock the same rwlock again.
If you leave an rwlock locked in an emulator thread when you let the thread out of your control, you will very likely deadlock the whole emulator.
This function is thread-safe.
Arguments:
This function tries to read lock an rwlock. If successful
If you leave an rwlock locked in an emulator thread when you let the thread out of your control, you will very likely deadlock the whole emulator.
This function is thread-safe.
Arguments:
This function read unlocks an rwlock. The rwlock currently has to be read locked by the calling thread.
This function is thread-safe.
Arguments:
This function read/write locks an rwlock. The calling thread will be blocked until the rwlock has been read/write locked. A thread which currently has read or read/write locked the rwlock may not lock the same rwlock again.
If you leave an rwlock locked in an emulator thread when you let the thread out of your control, you will very likely deadlock the whole emulator.
This function is thread-safe.
Arguments:
This function tries to read/write lock an rwlock. If successful
If you leave an rwlock locked in an emulator thread when you let the thread out of your control, you will very likely deadlock the whole emulator.
This function is thread-safe.
Arguments:
This function read/write unlocks an rwlock. The rwlock currently has to be read/write locked by the calling thread.
This function is thread-safe.
Arguments:
This function creates a thread specific data key. On success
This function is thread-safe.
Arguments:
This function destroys a thread specific data key
previously created by
A destroyed key is very likely to be reused soon. Therefore, if you fail to clear the thread specific data using this key in a thread prior to destroying the key, you will very likely get unexpected errors in other parts of the system.
This function is thread-safe.
Arguments:
This function sets thread specific data associated with
If you fail to clear thread specific data in an emulator thread before letting it out of your control, you might not ever be able to clear this data with later unexpected errors in other parts of the system as a result.
This function is thread-safe.
Arguments:
This function returns the thread specific data
associated with
This function is thread-safe.
Arguments:
This function sets the value of an environment variable.
It returns
The result of passing the empty string ("") as a value is platform dependent. On some platforms the value of the variable is set to the empty string, on others, the environment variable is removed.
Do not use libc's
This function is thread-safe.
Arguments:
This function retrieves the value of an environment variable.
When called,
Do not use libc's
This function is thread-safe.
An Alternative Distribution Driver (ERTS User's Guide Ch. 3)