Shared Memory Applications in C

As explained in the Shared Memory Applications page, eXtremeDB allows two or more processes to share a common database. General instructions are given there explaining how shared memory segments must be created on different operating systems. Further implementation details for C applications are provided in the following sections.

Important note on multi-process shared memory applications

In multi-process database applications using shared memory it is necessary to call mco_runtime_start() first just by a single process (the primary process). This allows the eXtremeDB runtime to correctly create and initialize the database registry. After the primary process initializes, all other (secondary) processes can call mco_runtime_start() simultaneously.

Primary Process Example

The following code snippet demonstrates how a “primary” process might determine that the correct shared-memory library is linked, then initialize memory devices and open the database in shared-memory:

 
    MCO_RET open_shared_db(
        const char * db_name,  /* name of the database */
        mco_dictionary_h dict, /* pointer to schema */
        mco_size_t db_sz,      /* size of memory segment for in-mem part
                    * of the db */
        uint2 mem_pg_sz,       /* size of memory page */
        uint2 max_conn_no      /* max. number of connections */
    )
    {
        mco_runtime_info_t info;
        mco_db_params_t    db_params;
        mco_device_t dev;
 
        /* get runtime info */
        mco_get_runtime_info(&info);
        if (info.mco_shm_supported) 
        {
            /* set the device as a shared named memory device */
            dev.type       = MCO_MEMORY_NAMED;
            sprintf( dev.named.name, "%s-db", db_name ); /* set memory name */
            dev.named.flags = 0;       /* zero flags */
            dev.named.hint  = 0;       /* set mapping address or null it */
        } 
        else 
        {
            /* Wrong libraries for shared-memory */
            return MCO_E_UNSUPPORTED;
        }
 
        dev.assignment = MCO_MEMORY_ASSIGN_DATABASE;   /* main database memory */
        dev.size       = db_sz;                        /* set the device size */
 
        /* initialize and customize the database parameters */
        mco_db_params_init ( &db_params );    /* initialize the params with default values */
        db_params.mem_page_size      = mem_pg_sz;
        db_params.disk_page_size     = 0;
        db_params.db_max_connections = max_conn_no;
 
        /* open a database on the device with given params */
        return mco_db_open_dev(db_name, dict, dev, 1, &db_params );
    }
 
    void StartDB()
    {
        MCO_RET       rc;
        mco_db_h      db;
        void* start_mem = 0;
 
        /* set fatal error handler *
        mco_error_set_handler( &errhandler );
        mco_runtime_start();
        rc = open_shared_db( dbname, DemoShm1_get_dictionary(),
                    SEGSZ, PAGESIZE, MAX_CONNECTIONS );
        if ( MCO_S_OK == rc )
        {
            rc = mco_db_connect( dbname, &db );
            /* normal processing goes here */
            ...
            rc = mco_db_disconnect( db );
            rc = mco_db_close( dbname );
        }
        mco_runtime_stop();
    }
     

Note that it is important to set the shared memory flags and hint fields to zero or a valid value in the mco_device_t structure passed to mco_db_open_dev() as done in the example above:

             
            dev.named.flags = 0;       /* zero flags */
            dev.named.hint  = 0;       /* set mapping address or null it */
             

Secondary Process Example

Once the shared memory for the database has been set up by open_shared_db() in the primary process, subsequent processes will simply connect to the database as demonstrated below:

 
    void DbAttach()
    {
        MCO_RET       rc;
        mco_db_h      db;
         
        /* set fatal error handler */
        mco_error_set_handler( &errhandler );
        mco_runtime_start();
 
        /* connect to a database by name "dbname" */
        rc = mco_db_connect( dbname, &db );
        if ( MCO_S_OK != rc )
        {
            printf("\n Could not attach to instance: %d\n", rc);
            exit( 1 );
        }
 
        /* normal database processing goes here */
        ...
        rc = mco_db_disconnect( db );
        rc = mco_db_close( dbname );
 
        mco_runtime_stop();
    }
     

See sample 03-connect_multiprocess for a complete example of a shared memory application.

 

Shared Memory Runtime Options

The eXtremeDB runtime can be informed of shared memory configuration through runtime options. The shared memory options are OS specific. To set runtime options that application may call the mco_runtime_setoption() before mco_runtime_start(). For example:

 
    mco_runtime_setoption(MCO_RT_OPTION_UNIX_SHM_MASK, 0600 );
     
    rc = mco_runtime_start();
    ...
     

Applications can determine at run-time whether or not the correct eXtremeDB runtime libraries are linked to support shared memory by examining the output of the mco_get_runtime_info() function.

For Windows applications

There are two groups of runtime options: one for determining the scope of the shared memory block name (local or global), and one for determining the security level applied to the shared memory block. The combination of options are applied to the name specified in the shared (named) memory block device.

For example, the following code snippet creates a shared memory block with the name “shared-db”:

     
    if (info.mco_shm_supported) 
    {
        /* set the device as a shared named memory device */
        dev.type       = MCO_MEMORY_NAMED;
        sprintf( dev.named.name, "%s-db", db_name ); /* set memory name */
        dev.named.flags = 0;       /* zero flags */
        dev.named.hint  = 0;       /* set mapping address or null it */
    }
     

The default runtime options are as follows:

 
    MCO_RT_WINDOWS_SHM_PREFIX_NONE | MCO_RT_WINDOWS_SHM_SEC_DESCR_NULL
     

This combination makes the database visible and accessible from only local sessions. To make the database accessible from all sessions of the same user, for example a service and a desktop program running under the same user, the following runtime options are necessary:

 
    MCO_RT_WINDOWS_SHM_PREFIX_GLOBAL | MCO_RT_WINDOWS_SHM_SEC_DESCR_SAMEUSER
     

To setup the shared memory block with these options the following call must be made prior to calling mco_runtime_start():

     
    mco_runtime_setoption( MCO_RT_WINDOWS_SHM_OPT,
                    MCO_RT_WINDOWS_SHM_PREFIX_GLOBAL |
                    MCO_RT_WINDOWS_SHM_SEC_DESCR_SAMEUSER );
 
    ...
    rc = mco_runtime_start();
    ...
     

Please note that the value MCO_RT_WINDOWS_SHM_PREFIX_GLOBAL is applicable for Services only; desktops sessions are able to create databases using modes

MCO_RT_WINDOWS_SHM_PREFIX_LOCAL or MCO_RT_WINDOWS_SHM_PREFIX_NONE; and they can connect to databases opened with MCO_RT_WINDOWS_SHM_PREFIX_GLOBAL by using matching security settings.

For Linux applications

Linux shared memory applications use the POSIX function mmap() to provide mapping of virtual shared memory to a current process when the eXtremeDB library libmcompsx is used. The option MCO_RT_POSIX_SHM_ANONYMOUS enables the option MAP_ANOUNYMOUS of function mmap(). The option MCO_RT_POSIX_SHM_SHARED specifies option MAP_SHARED of function mmap(), otherwise MAP_PRIVATE is used. The default combination of options is

 
    MCO_RT_POSIX_SHM_ANONYMOUS | MCO_RT_POSIX_SHM_SHARED.
     

To specify different options for the shared memory block call mco_runtime_setoption() prior to calling mco_runtime_start(). For example to specify mmap() option MAP_PRIVATE:

     
    mco_runtime_setoption( MCO_RT_POSIX_SHM_OPT,
                    MCO_RT_POSIX_SHM_ANONYMOUS );
    ...
    rc = mco_runtime_start();
    ...
     

Note that the Linux AIO library mcfu98aio is not designed for shared memory between processes. It may be used for multi-threaded access but not for multi-process sharing of a database.

For Unix applications

On Unix systems the shared memory access mode is specified by the Unix specific value of file system access rights. Shared memory and semaphores have the same system of permissions as ordinary files to restrict access by processes of different users and groups.

The default value is 0666 for MCO_RT_OPTION_UNIX_SHM_MASK means that user, group and others can read and write this memory or semaphore. A value of 0600 means that only user can access (read and write) this memory or semaphore. To change the default to 0600 the following call must be made prior to calling mco_runtime_start():

 
    mco_runtime_setoption(MCO_RT_OPTION_UNIX_SHM_MASK, 0600);
    ...
    rc = mco_runtime_start();
    ...
     

 

Shared memory and the Direct Pointers library

When using the eXtremeDB Direct Pointer Arithmetic library (DPTR) it is necessary to map the shared memory segment to the same virtual memory address in every process because in the DPTR implementation eXtremeDB uses actual memory addresses (i.e. it performs pointer arithmetic) to calculate the locations of objects in an eXtremeDB database. The pointers must be the same in every running instance of an eXtremeDB-based application, or pointer arithmetic just doesn’t work. Setting the hint parameter to zero causes eXtremeDB to determine the actual shared memory segment address. But this could fail when called from a second process attempting to open the shared database. In this case it is the applications responsibility to provide a valid hint address.

There are several ways to determine where the runtime should map the shared memory database. Developers can use a utility provided by the operating system to gather memory usage information (the process memory map). These utilities will usually display the code, data and stack memory usage of each process running on the system and the libraries it is using. It is necessary to examine the output and pick an address outside any address space.

In any event, the memory address is an address in each process’s virtual memory. An address should be chosen that is sufficiently far away from the data, stack and heap segment of every process that will share the database. (Hence the default of 0x20000000 for MAP_ADDRESS; it is assumed unlikely, but not guaranteed, that any single process will have data, stack and heap that stretch out to 0x20000000 bytes.)

The above potential issues with respect to the MAP_ADDRESS can be avoided by using the Offset library instead of the Direct Pointer Arithmetic library. The Offset approach calculates an offset from the beginning address of the in-memory database, to locate objects. Therefore, it does not depend on the in-memory database starting at a common (and known) location for all processes. However, the DPTR pointer arithmetic is about 5%–15% faster than calculating offsets.