ChorusOS 5.0 Application Developer's Guide

Using Persistent Memory

This section provides a description of the API exported by the Persistent Memory Manager. In particular, it covers the following topics:

In this section, an example "hello world" program is used to illustrate different aspects of the Persistent Memory Manager interface. The code for this example is provided in "A Basic Application".

To run the example, compile the code and copy it to a directory which is mounted on the target machine. See "Compiling and Running the Examples" for information about compiling and running the hot restart examples.

Introduction to Persistent Memory Programming

Within a running ChorusOS system, access to persistent memory is provided by a ChorusOS process known as the Persistent Memory Manager. The Persistent Memory Manager exports a specific API for allocating and freeing blocks of memory in the persistent memory bank. This API differs from the API used for allocating and deallocating traditional ChorusOS memory regions (rgnAllocate(2K), rgnFree(2K), svPagesAllocate(2K) and svPagesFree(2K)) for the following reasons:

The Persistent Memory Manager API is available to all ChorusOS processes (not just restartable processes). This section describes the use of this API.

A Basic Application

Before proceeding with a description of the different functions in the Persistent Memory Manager API, consider the following basic restartable application, an implementation of the "hello world" example. When the process is run for the first time, it displays the following message on the host console:


Hello world!

When the process is restarted, it will display the following message on the target console:


Hello again! I have been restarted.

The basic flow of execution is as follows:


Example 15-1 The "Hello world" Restartable process

#include <stdio.h>
#include <pmm/chPmm.h>
#include <hr/hr.h>
#define HR_GROUP "HELLO_GROUP"
    int 
main()
{
  int res;
 int any = 1;
 int* counter_p;  /* It will be stored in persistent memory */
 long *p;
 PmmName name; 
 KnRgnDesc rgn;
             /* 
              * Initialize the name and medium fields 
              * to identify the persistent memory block in the system.
              */
 bzero(&name, sizeof(name));
 strcpy(name.medium,"RAM");
 strcpy(name.name,"PM1");
             /* 
              * Initialize the block fields
              */ 
 bzero(&rgn, sizeof(rgn));
 rgn.options = K_ANYWHERE | K_RESERVED;
 rgn.size    = vmPageSize();
 res = rgnAllocate(K_MYprocess, &rgn);
 if (res != K_OK) {
            printf("rgnAllocate() failed res=%d\n", res);
            HR_EXIT_HDL();
            exit(-1);
 }
 p = (long*) rgn.startAddr;
            /*
             * From now on p is a bad pointer, since 
             * VIRTUAL_ADDRESS_SPACE is true.
             */
            /* 
             * Allocate the persistent memory block that stores
             * counter_p.
             */
 res=pmmAllocate((VmAddr *)&counter_p,
                        &name,sizeof(int),
                        HR_GROUP,
                        sizeof(HR_GROUP));
  if (res != K_OK) {
      printf("Cannot allocate or map the persistent memory block called %s."
             " Error = %d\n", name.name, res);
        HR_EXIT_HDL();
        exit(-1);
 }
             /* 
              * From the value of *counter_p the process detects  
              * whether it has been hot restarted or not.
              */
 if ( *counter_p==0 ) {
             /*
              * This is the first time the process is run.
              */
            printf("Hello world!\n");
               /* 
                * Increment the counter
                */
            (*counter_p)++;
               /*
                * Normally the next instruction causes a core dump and
                * a hot restart of the process
                */
            *p = 0xDeadBeef;
 } else {
               /* 
                * The process has been restarted
                * NOTE: this message will appear on the console!
                */
            printf("The process has been restarted.\n");
               /*
                * Free the persistent memory block before exiting
                */
            res = pmmFree(&name);
            if (res != K_OK) {
                printf(" pmmFree failed, res=%d. Exit\n", res);
                HR_EXIT_HDL();
                exit(-1);
            } 
               /* 
                * Terminate cleanly. 
                */ 
            printf("Example finished. Exit.\n");
            HR_EXIT_HDL();
            exit(0);
 }
            /* Never reached */
}

The aspects of this program that are of interest to users of the Persistent Memory Manager API are discussed in the rest of this section.

Allocating and Retrieving a Persistent Memory Block

The "hello world" application uses a block of persistent memory to store a counter indicating whether it has been restarted. The value of the counter controls the program's flow of execution. This is a common use of persistent memory. A counter or flag such as this is usually necessary because it is the only way a process can know whether it has been restarted.

A block of persistent memory is described in the system by a structure of the following type:

#include <pmm/chPmm.h>
typedef	struct {
			 	PmmMedium   medium;
         PmmMemName  name;
} PmmName;
PmmName MyPmmName = { "RAM", "myname" };

Within the structure, medium is a character string which identifies the memory bank to be used. In the current implementation, it must always be set to RAM. The name parameter is a user-defined, NULL-terminated character string that uniquely identifies the block of memory in the memory bank. The lifetime of a block name is identical to the lifetime of the block itself in persistent memory. The system parameter, pmm.maxBlocks, defines the number of distinct persistent memory blocks (and therefore names) that can be allocated at any one time. The default value is 30.


Caution - Caution -

Sharing persistent memory blocks between user processes, or between user and supervisor processes is not supported. Persistent memory blocks can only be shared between supervisor processes.


To allocate or retrieve a block of persistent memory, use the pmmAllocate() function call, defined as follows:

#include <pmm/chPmm.h>
KnError pmmAllocate(  VmAddr     *addr,
                      PmmName    *name,
                      size_t      size,
                      PmmDelKey   delKey,
                      size_t      delKeySize); 

If no memory block corresponding to the specified PmmName structure is found in persistent memory, pmmAllocate() allocates a block of size size in persistent memory, fills it with nulls, and returns the pointer *addr to the address of the block. The address is determined by the system and cannot be manually specified or changed.

If a block identified with the specified PmmName already exists in persistent memory, pmmAllocate() returns a pointer to the existing memory block as an address (*addr), and the size parameter is ignored. Persistent memory blocks are always mapped to the same address. In other words, the address returned by the first and subsequent calls to pmmAllocate() is always the same for a given block.

As a result of this dual functionality of the pmmAllocate() call, the difference between initially allocating and subsequently retrieving a persistent memory block is transparent at the programming level. The first time the code of the "hello world" example is run, the call to pmmAllocate() will allocate an integer-sized block of persistent memory that contains the initialized value of counter (0).

res=pmmAllocate((VmAddr *)&counter_p,
                          &name,sizeof(int),
                          HR_GROUP,
                          sizeof(HR_GROUP)); 

The second time the code is run, pmmAllocate() returns a pointer to the value of counter in persistent memory.

The delKey and delKeySize parameters passed to pmmAllocate() are used to define the deletion key associated with a memory block. A deletion key is a user-defined binary array, used to mark a set of persistent memory blocks which can be freed simultaneously using the pmmFreeAll(2RESTART) function, described in "Freeing a Persistent Memory Block Explicitly".

Freeing a Persistent Memory Block

This section describes the API calls used to free persistent memory blocks.

Responsibility

A persistent memory block can remain in memory beyond the lifetime of a run-time instance of the process that allocates the block. This immediately raises the question of responsibility for freeing blocks of persistent memory. When a traditional ChorusOS user process terminates, any memory regions it previously allocated (using rgnAllocate(2K) are freed automatically. Clearly, this basic rule makes little sense in the case of persistent memory blocks (which can survive beyond such a termination).

The hot restart feature provides two solutions to this problem:

In both cases, freeing a persistent memory block has the same effect -- the block is freed immediately and permanently and cannot be retrieved. The name of the block becomes available for reuse and can be used to identify a different memory block.

Freeing a Persistent Memory Block Explicitly

Use the pmmFree() or pmmFreeAll() function to free a persistent memory block explicitly. The explicit freeing of a given memory block can be performed by any process (not necessarily the process that originally allocated the block). It is the programmer's responsibility to ensure that any persistent memory block that has been freed is no longer in use.

Use pmmFree() to free a single memory block identified by a PmmName:

#include <pmm/chPmm.h>
int pmmFree( PmmName *name ) 

Use pmmFreeAll() to free a group of persistent memory blocks that were allocated with the same deletion key. The deletion key for a persistent memory block is specified when the block is allocated with pmmAllocate().

#include <pmm/chPmm.h>
int pmmFreeAll( PmmDelKey    delkey,
                size_t       delKeySize ); 

A typical use of a deletion key is to mark all persistent memory blocks used by a process or a group of processes with the same key, and then have a separate, independent process that frees all the blocks when a particular job is completed (or a specific event occurs). The "hello world" example uses pmmFree() to free the single memory block it allocated before terminating. If the "hello world" process did not free its own persistent memory block, the following call to pmmFreeAll() from another process would free the block, and also with any other blocks marked with the deletion key HR_GROUP:

pmmFreeAll( HR_GROUP, sizeof(HR_GROUP) );