Actors within the ChorusOS operating system environment may extend their address space using the malloc() library call as illustrated earlier in this document. However, this is a rather inflexible way of allocating memory, as there is no way to control the attributes of the allocated memory; that is, whether it is a read only or a read/write memory area. The malloc() routine uses the ChorusOS operating system services described in this section. These services may also be used to share a region of memory between two or more actors.
This chapter explains the recommended way of allocating memory for an actor. It contains the following sections:
"Memory Region Descriptors" explains how a memory region is identified and described.
"Allocating and Freeing Memory Regions" explains how to allocate and free memory.
"Sharing Memory Between Two Actors" explains how to share memory between actors.
The ChorusOS hot restart feature provides support for using persistent memory; memory which can extend beyond the lifetime of the runtime instance of an actor. Hot restart is not covered in this chapter. For information about using hot restart and the persistent memory services it provides, see the ChorusOS 4.0 Hot Restart Programmer's Guide.
The ChorusOS operating system offers various services which enable an actor to extend its address space dynamically by allocating memory regions. An actor may also shrink its address space by freeing memory regions. An area of memory to be allocated or freed is described to the system through a region descriptor of the following type:
typedef struct { VmAddr startAddr; VmSize size; VmFlags options; VmAddr endAddr; void* opaque1; VmFlags opaque2; } KnRgnDesc;
The startAddr field defines the starting address of the memory region. The size field defines its length expressed in bytes. The options field enables a low level control on the attributes of the memory region to be allocated. The opaque1 and opaque2 fields should be set to NULL if they are not being used by the application.
The options field is a combination of flags, of which the following are the most important:
K_WRITABLE tells the system that the memory region to be created must be writable, otherwise, the memory region will be read only.
K_FILLZERO tells the system that the content of the memory region to be created must be filled with zeroes upon creation. If this flag is not set, the content of the memory region at creation time is unspecified.
K_ANYWHERE tells the system that the actual address used to allocate the region is not critical to the application. An appropriate address will be selected by the system and returned to the application. This avoids the need for the application to find out which addresses are already in use within the actor address space. It also permits memory to be allocated within a library without any possible conflict with an existing address space.
K_SUPERVISOR tells the system that the memory to be allocated will be part of the supervisor address space rather than of the user address space. This flag is usually set by actors running in supervisor mode.
On a ChorusOS operating system configured with the Virtual Memory feature, further options are available. The most important one allows control of the swapping policy to be applied to the pages of the created region:
K_NODEMAND tells the system that no page fault should ever occur on such a memory region. Physical pages are allocated to the region at creation time and they will never be swapped out. Thus the region is locked in memory.
A memory region is allocated through the following call:
#include <chorus.h> int rgnAllocate(KnCap* actorCap, KnRgnDesc* rgnDesc);
This call creates a memory region as described by the rgnDesc parameter within the address space of the actor defined by the actorCap parameter. Most applications set actorCap to K_MYACTOR to manage their own address space.
An unused part of an address space may be freed by the following call:
#include <chorus.h> int rgnFree(KnCap* actorCap, KnRgnDesc* rgnDesc);
This call frees a memory region as described by the rgnDesc parameter within the address space of the actor defined by the actorCap parameter.
Example 7-1 does the following:
Allocates a memory region with the K_ANYWHERE
option.
Retrieves the address of the allocated region and prints it.
Copies a string to the beginning of the region.
Creates a second region immediately preceding the first.
Copies the string from the beginning of the first region to the beginning of the second region.
Frees an area of memory spanning the junction between the two regions.
Copies the string from the beginning of the second region to the lowest memory address still accessible outside the freed memory area.
Ensures that the program is able to run in a user or supervisor actor.
The main steps of the example are illustrated in Figure 7-1. Refer to the rgnAllocate(2K) and rgnFree(2K) man pages.
(file: progov/rgnAlloc.c) #include <stdio.h> #include <stdlib.h> #include <string.h> #include <chorus.h> #define RGN_SIZE_1 (6 * vmPageSize()) #define RGN_SIZE_2 (3 * vmPageSize()) #define FREE_START (2 * vmPageSize()) #define FREE_SIZE (4 * vmPageSize()) #define STILL_ALLOC_START (FREE_SIZE - (RGN_SIZE_2 - FREE_START)) int main(int argc, char** argv, char**envp) { KnRgnDesc rgnDesc; KnActorPrivilege actorP; int res; VmFlags rgnOpt = 0; char* ptr1 = NULL; /* Avoids "uninited" warning */ char* ptr2 = NULL; /* Avoids "uninited" warning */ printf("Starting rgnAllocate example\n"); res = actorPrivilege(K_MYACTOR, &actorP, NULL); if (res != K_OK) { printf("Cannot get actor privilege, error %d\n", res); exit(1); } if (actorP == K_SUPACTOR) { rgnOpt = K_SUPERVISOR; } rgnDesc.size = RGN_SIZE_1; rgnDesc.options = rgnOpt | K_WRITABLE | K_FILLZERO | K_ANYWHERE; rgnDesc.opaque1 = NULL; rgnDesc.opaque2 = NULL; /* * No need to set rgnDesc.startAddr * since we set the K_ANYWHERE flag */ res = rgnAllocate(K_MYACTOR, &rgnDesc); if (res == K_OK) { printf("Successfully allocated memory starting at 0x%x\n", rgnDesc.startAddr); ptr1 = (char*) rgnDesc.startAddr; } else { printf("First rgnAllocate failed with error %d\n", res); exit(1); } strcpy(ptr1, "Fill the allocated memory with this string\n"); /* * Second allocate has a fixed address, such that * both memory areas will be contiguous. Hence * we do not want the K_ANYWHERE flag any more. */ rgnDesc.size = RGN_SIZE_2; rgnDesc.options &= ~K_ANYWHERE; rgnDesc.startAddr -= RGN_SIZE_2; res = rgnAllocate(K_MYACTOR, &rgnDesc); if (res == K_OK) { printf("Successfully allocated memory starting at 0x%x)\n", rgnDesc.startAddr); ptr2 = (char*) rgnDesc.startAddr; } else { printf("Second rgnAllocate failed with error %d\n", res); exit(1); } /* Copy from first allocated area to second one */ strcpy(ptr2, ptr1); /* * Free a memory area spanning both areas * previously created */ rgnDesc.options = NULL; rgnDesc.startAddr = (VmAddr) (ptr2 + FREE_START); rgnDesc.size = FREE_SIZE; res = rgnFree(K_MYACTOR, &rgnDesc); if (res != K_OK) { printf("Cannot free memory, error %d\n", res); exit(1); } /* * Access to ptr2: beginning of secondly allocated area * is still valid. * Access to ptr1 is now invalid: memory has been freed. */ printf("%s", ptr2); /* * Access to "end" of first allocated area * is still valid */ ptr1 += STILL_ALLOC_START; strcpy(ptr1, ptr2); /* * Remaining memory areas not yet freed will * effectively be freed at actor termination time. */ return 0; }
Region descriptors are uniquely used to describe a creation or deletion operation on the system. They are not kept by the system in the way they are given to the rgnAllocate() call. As an example, allocation of two contiguous areas with the same attributes (writable, fill zero) and the same opaque fields will result in the system recognizing a single region, the size of which is the sum of the sizes passed as part of the two region descriptors.
You cannot allocate a region on a range of addresses which are not free. No implicit deallocation of the address space is undertaken by the system, instead an error code K_EOVERLAP is returned to the caller.
A call to rgnFree() does not need to reuse a region descriptor that was used to allocate a memory area. A free operation may freely span over several regions which were allocated by separate operations. Similarly, a free operation may only free a chunk of memory in the middle of a large memory area which was allocated in a single operation. See Figure 7-1.
Only the precise region described in the region descriptor will be freed. The free operation is not extended to match the address range which was allocated at rgnAllocate() time.
The options field of the region descriptor must be set to 0 for a free operation. Otherwise, you may set it to K_FREEALL, in which case all memory regions of the actor will be freed: the code, the data, the stacks. The K_FREEALL option should therefore be used with care.
All memory areas which have been dynamically allocated are freed when the actor terminates.
The ChorusOS operating system offers the possibility of sharing an area of memory between two or more actors, regardless of whether these actors are user or supervisor actors. The memory area does not need to be located at the same address within the address space of each actor.
This mechanism is based on the following service:
#include <chorus.h> int rgnMapFromActor(KnCap* targetActor, KnRgnDesc*targetRgnDesc, KnCap* sourceActor, KnRgnDesc*sourceRgnDesc);
This call allows mapping of a memory area as defined by sourceActor and sourceRgnDesc in the address space of the actor defined by the targetActor parameter. The source memory area is explicitly defined by the startAddr and size fields of the sourceRgnDesc parameter. The region created within the address space of the target actor is defined by the targetRgnDesc parameter: the address may be fixed or undefined if the K_ANYWHERE flag is set. After the mapping has been established, both actors may freely use the shared memory area.
Figure 7-2 shows two actors before they share memory and after sharing has been established. Usually some synchronization mechanism is required in order to get a consistent view of the memory: semaphores may be used in shared memory areas to synchronize threads from the various actors.
Example 7-2 does the following:
The actor started by an arun command allocates a memory region, then spawns an actor running the same executable file using the afexecve() call.
The spawned actor uses the parameters set up by the spawning actor to establish a sharing of memory.
Some data is passed through the shared memory from the spawning to the spawned actor. A semaphore allocated in the shared memory area, and initialized by the spawning actor, is used to synchronize both actors. Thus, the first actor will know when the spawned actor has changed the contents of the shared area.
Both actors then terminate.
The spawned actor should retrieve the information needed to establish the mapping from its arguments.
This example uses a call which enables an actor to get its own capability in order to pass it to another actor.
#include <chorus.h> int actorSelf(KnCap* myCap);
Refer to the rgnMapFromActor(2K) man page.
(file: progov/rgnMapFromActor.c) #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <chorus.h> #include <am/afexec.h> AcParam param; #define RGN_SIZE (3 * vmPageSize()) #define SHARED_RGN_SIZE (1 * vmPageSize()) typedef struct sampleSharedArea { KnSem sem; char data[1]; } sharea_t; char capString[80]; char sharedAddr[20]; int main(int argc, char** argv, char**envp) { KnRgnDesc rgnDesc; sharea_t* ptr; KnCap spawningCap; KnCap spawnedCap; int res; VmFlags rgnOpt = 0; KnActorPrivilege actorP; res = actorPrivilege(K_MYACTOR, &actorP, NULL); if (res != K_OK) { printf("Cannot get privilege, error %d\n", res); exit(1); } if (actorP == K_SUPACTOR) { rgnOpt = K_SUPERVISOR; } if (argc == 1) { /* * This is the first actor (or spawning actor): * Allocate a memory region * Initialize a semaphore within the region * Spawn the second actor * Wait on the semaphore * Get data written in shared mem by spawned actor * Terminate */ rgnDesc.size = RGN_SIZE; rgnDesc.options = rgnOpt | K_ANYWHERE | K_WRITABLE | K_FILLZERO; rgnDesc.opaque1 = NULL; rgnDesc.opaque2 = NULL; res = rgnAllocate(K_MYACTOR, &rgnDesc); if (res != K_OK) { printf("Cannot allocate memory, error %d\n", res); exit(1); } ptr = (sharea_t*) rgnDesc.startAddr; strcpy(&ptr->data[0], "First actor initializing the shared mem\n"); res = semInit(&ptr->sem, 0); /* * Get my own capability and pass it as a string argument * to spawned actor, so that it may use it to share memory */ actorSelf(&spawningCap); sprintf(capString, "0x%x 0x%x 0x%x 0x%x", spawningCap.ui.uiHead, spawningCap.ui.uiTail, spawningCap.key.keyHead, spawningCap.key.keyTail); /* * Pass address of memory to be shared as a string argument * to spawned actor. */ sprintf(sharedAddr, "0x%x", ptr); param.acFlags = (actorP == K_SUPACTOR)? AFX_SUPERVISOR_SPACE : AFX_USER_SPACE; res = afexeclp(argv[0], &spawnedCap, ¶m , argv[0], capString, sharedAddr, NULL); if (res == -1) { printf("cannot spawn second actor, error %d\n", errno); exit(1); } semP(&ptr->sem, K_NOTIMEOUT); printf("%s", &ptr->data[0]); } else { KnRgnDesc srcRgn; KnRgnDesc tgtRgn; unsigned long uHead, uTail, kHead, kTail; /* * This is the spawned actor: * Get arguments * Set up the memory sharing * Write some string in shared memory * Wake up spawning actor * Terminate */ sscanf(argv[1], "0x%x 0x%x 0x%x 0x%x", &uHead, &uTail, &kHead, &kTail); spawningCap.ui.uiHead = uHead; spawningCap.ui.uiTail = uTail; spawningCap.key.keyHead = kHead; spawningCap.key.keyTail = kTail; sscanf(argv[2], "0x%x", &srcRgn.startAddr); if (actorP != K_SUPACTOR) { srcRgn.size = SHARED_RGN_SIZE; tgtRgn.startAddr = srcRgn.startAddr; tgtRgn.size = SHARED_RGN_SIZE; tgtRgn.options = rgnOpt | K_WRITABLE; tgtRgn.opaque1 = NULL; tgtRgn.opaque2 = NULL; res = rgnMapFromActor(K_MYACTOR, &tgtRgn, &spawningCap, &srcRgn); if (res != K_OK) { printf("Cannot share memory, error %d\n", res); exit(1); } ptr = (sharea_t*) tgtRgn.startAddr; } else { /* * Both actors are running in supervisor space, * There is no need to perform the rgnMapFromActor. * One may use the received shared address. */ ptr = (sharea_t*) srcRgn.startAddr; } /* Get data from spawning actor */ printf("Spawning actor sent: %s", &ptr->data[0]); /* Modify contents of shared memory */ sprintf(&ptr->data[0], "Spawned actor mapped shared memory at 0x%x\n", ptr); res = semV(&ptr->sem); if (res != K_OK) { printf("Spawned actor failed on semV, error %d\n", res); exit(1); } } return 0; }
Semaphores have been tuned to be highly optimized. There are thus some constraints on their use: they may be used freely in a region of shared memory between two user actors, even though the shared area is not mapped at the same address in each actor. In supervisor space, a semaphore cannot be accessed using two different addresses. As supervisor address space is partitioned common space, there is no real need to invoke the rgnMapFromActor() service. Finally, it is not possible to use a semaphore in a memory region shared between a user actor and a supervisor actor.
The above example would work in a similar fashion if the spawned actor did not impose the address of the created region, but used the K_ANYWHERE flag instead.
The spawned actor maps only one page from the region created by the spawning actor, although this region is three pages long. Access to an address beyond the shared page would result in access to private data for the spawning actor, and in a memory fault for the spawned actor.
The above example does not invoke the rgnFree() call. The region in the spawned actor will be freed at exit time. This does not mean that the physical memory will be freed as soon as the target actor disappears. Physical memory will be effectively freed when both actors have exited, regardless of the order in which they terminate.