nob_util - Named Object Utilities

The Named Object (NOB) utilities provide a general means of assigning names to arbitrary objects so that other tasks can access the objects by name.

The NOB utilities are intended for, but not limited to, use in library functions that both create new objects and access existing objects. An application can call such a library function without caring if the target object exists or not - the object will be created automatically if need be. For example, message queues are known by their ID under UNIX. A library function that creates new named message queues or accesses existing ones would use the NOB utilities as follows:

    #include  <errno.h>				-- System error definitions.
    #include  <sys/ipc.h>			-- Inter-process communication definitions.
    #include  <sys/msg.h>			-- Message queue definitions.
    #include  "nob_util.h"			-- Named object definitions.
    int  queue ;
    NamedObject  qobj ;


    if (!nobCreate ("MY_MSGQ", singleCPU, &qobj)) {
        queue = msgget (IPC_PRIVATE,		-- Brand new?
                        (IPC_CREAT | 0620)) ;
        nobCommit (qobj, (void *) queue) ;
    } else if (errno == EEXIST) {		-- Already exists?
        queue = (int) nobValue (qobj) ;
    } else {					-- Error?
        ... error ...

If the named queue already exists, the queue ID is retrieved from the object by calling nobValue(). If the queue doesn't exist, the queue is created and its ID is then stored in the object by a call to nobCommit(). The creation of a new object can be aborted in the event of an error by calling nobAbort() instead of nobCommit().

Processes that know an object exists or that depend upon the object existing can call nobExists() to lookup an object's value:

    qobj = nobExists ("MY_MSGQ", singleCPU) ;
    if (qobj == NULL) {				-- Doesn't exist or error?
        ... error ...
    } else {					-- Exists?
        queue = (int) nobValue (qobj) ;

Deleting the named message queue is done as follows:

    if (!nobDestroy (qobj)) {			-- Last user of queue?
        msgctl (queue, IPC_RMID, NULL) ;
    } else if (errno != EWOULDBLOCK) {		-- Error?
        ... error ...

Note that the last task using the message queue is the one that actually deletes the queue.

Implementation Notes (UNIX)

Under UNIX, the named object database is implemented using the ndbm(3) database facility. The base ndbm(3) pathname for the named object database files defaults to /tmp/nob_database; the user can specify a different pathname in environment variable, NOB_DATABASE.

If the directory in which the database files are stored is NFS mounted on multiple machines, the database is visible on each of those machines. The multi-system lockf(3) facility - lockf(2) depending on your OS - is used to prevent simultaneous updates to the database.

Since objects such as message queues and semaphores are only accessible from the CPU on which they are created, be wary of storing their "values" (IPC IDs) in an NFS-mounted, multi-CPU NOB database.

ndbm(3) caches retrieved records and updates in memory on a per-process basis. To make sure every process sees the same database, the cached images must be synchronized with the disk image before each database fetch and after each database store. The synchronization is performed by closing and reopening the database - a brute force approach, but no other solution has presented itself.

To prevent alignment errors (which occurred in my first version), the unaligned records returned by dbm_fetch() are copied into local, aligned record storage.

Implementation Notes (VxWorks)

Under VxWorks, the "named object database" is implemented using the system symbol table and, when VxMP is available, the shared memory database. The single-/multi-CPU scope argument to nobCreate() provides for a two-level database.

A semaphore is used to prevent simultaneous updates to the "named object database". This semaphore is itself accessed by name by different tasks. For local symbols (or on a non-VxMP system), the semaphore's name is stored in the system symbol table, which allows multiple instances of a symbol. It is possible that N tasks could simultaneously find the semaphore absent from the symbol table, create N new semaphores, and add N semaphores to the symbol table. To prevent this from happening, the tasks are allowed to create the semaphore, but then an internal function, nobExamine(), scans the symbol table for the earliest-added NOB semaphore; that semaphore becomes the NOB semaphore; all others are deleted by the tasks who created them. (Scanning the symbol table might seem slow, but it only happens when the semaphore is not found in the table; once the semaphore is created, no scanning is necessary.)

A separate semaphore is used for global symbols on a VxMP system. This semaphore's name is entered in the VxMP shared memory database, which doesn't allow multiple instances of a given symbol. Under VxMP, the objects are stored in VxMP shared memory; the NOB functions make the appropriate global-to-local address conversions. However, if an object's value requires conversions, the creator and users of the object are responsible for performing the necessary conversions.

Tasks that terminate prematurely or that don't delete their objects can leave the "named object database" in an indeterminate or unaccessible state.

Public Procedures

nobAbort() - aborts the creation of a named object.
nobCommit() - finalizes the creation of a named object.
nobCount() - returns the number of tasks referencing a named object.
nobCreate() - creates a named object.
nobDestroy() - deletes a named object.
nobExists() - looks up a named object.
nobName() - returns a named object's name.
nobReference() - registers a reference to a named object through an existing handle.
nobValue() - returns a named object's value.

Source Files

nob_util.c (UNIX)
nob_vx.c (VxWorks)

(See libgpl for the complete source, including support routines and build files.)

Alex Measday  /  E-mail