This section provides a description of the API exported by the Persistent Memory Manager. In particular, it covers the following topics:
How persistent memory is managed by the system.
Allocating and retrieving blocks of persistent memory with the Persistent Memory Manager API
Freeing blocks of persistent memory with the Persistent Memory Manager API
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.
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:
Persistent memory blocks, by definition, persist across a process or site restart. The API provided for manipulating traditional ChorusOS memory regions does not support memory recovery after a restart.
Persistent memory blocks, unlike traditional memory regions, are named. This name is used to retrieve a block of memory allocated in the persistent memory bank.
Persistent memory blocks, unlike traditional memory regions, can be grouped, for the purposes of simultaneous deallocation. In other words, a single API call can free multiple blocks of persistent memory (which may have been allocated by different processes in the ChorusOS system).
The Persistent Memory Manager API is available to all ChorusOS processes (not just restartable processes). This section describes the use of this API.
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:
The restartable process begins at the start of its main() program -- initialization of program data.
The process uses the pmmAllocate(2RESTART) function to allocate a block of persistent memory. This block is used to store a status counter (which the process will set to zero).
The first message is displayed, and the counter is incremented by one.
The process attempts to access an invalid pointer value, thereby causing a crash that will require the process to be restarted. Note that the ChorusOS VIRTUAL_ADDRESS_SPACE optional feature must be set to true for this crash to be invoked.
The restarting process recommences execution at the start of its main() program, and will call pmmAllocate() a second time to retrieve the value of the status counter.
Because the counter is no longer set to zero, the process will display the second message.
The process calls pmmFree(2RESTART) to free the persistent memory block where the counter is stored, and then exits cleanly.
#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.
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.
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".
This section describes the API calls used to free persistent memory blocks.
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:
Processes can free blocks of persistent memory explicitly, using the API function pmmFree() or pmmFreeAll(). This is the only solution available for non-restartable processes that use persistent memory. For these processes, freeing persistent memory is entirely the programmer's responsibility.
If persistent memory needs to survive beyond the persistent lifetime of the allocating process (that is, even after the process has terminated cleanly), implementing this solution will require careful application design or the presence of a garbage collection process.
Explicit freeing of persistent memory blocks is described in the following section.
Hot restartable processes can benefit by using the automatic clean up mechanism provided by the Hot Restart Controller. This mechanism is described in more detail in Freeing Persistent Memory "Freeing Persistent Memory".
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.
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) );