Queues Continued
Written by Gordon Zeglinski
Contents
Queues Continued
We start by looking at what we have accomplished last time. In the last issue, OS/2 queues were encapsulated and a very simple shared memory management scheme was developed to manage the memory used by the queues. It was found that this scheme was next to useless for real applications. In the last issue, we concluded that in order to have a more usable queue object, we need better memory management. So now the goal is to develop a new method for using the shared memory. In this issue we start by looking for a new memory management scheme. Fortunately, OS/2 already provides a mechanism that fits our needs - suballocation! Yeah that's the ticket.
The Saga Begins
After a day of frustration, a short email is sent. Following is a paraphrased transcript:
"Hey Lar, U ever use DosSubSetMem with shared Memory?" "No, but try a simple example and work from there ..." "Sounds like a good idea to me..."
Off to the Races
Something simple... hmmm... Okay, lets try this:
#define INCL_DOSMEMMGR #include <os2.h> #include <iostream.h> #include <conio.h> void main() { int ret; void *BaseAddr,*SubAdr; char *MemoryName="\\SHAREMEM\\Mem.Mem"; //Allocate some shared memory ret=DosAllocSharedMem(&BaseAddr,MemoryName,1024*30, PAG_EXECUTE|PAG_READ|PAG_WRITE); cout << "Alloc Return : " << ret << endl; cout << "Base Adr : " << BaseAddr << endl; //Prepare shared Memory for sub allocation ret=DosSubSetMem(BaseAddr, DOSSUB_SPARSE_OBJ|DOSSUB_SERIALIZE|DOSSUB_INIT,1024*30); cout << "SubSet Return : " << ret << endl; cout << "Press Return" << endl; getch(); //suballocate a bit of memory ret=DosSubAllocMem(BaseAddr,&SubAdr,1024); cout << "SubAlloc Return : " << ret << endl; cout << "SubAlloc Adr : " << SubAdr << endl; cout << "Press Return" << endl; getch(); //free the suballocated ram ret=DosSubFreeMem(BaseAddr,SubAdr,1024); cout << "SubFree Return : " << ret << endl; cout << "Press Return" << endl; getch(); //Uninitialze suballocation struvtures ret=DosSubUnsetMem(BaseAddr); cout << "SubUnSet Return : " << ret << endl; //Free up the main memory block ret=DosFreeMem(BaseAddr); cout << "FreeBase Return : " << ret << endl; }
The above program creates a heap in shared memory, prepares it for suballocation, suballocates a chunk of the heap, and frees everything up. That's simple enough, and it even works!
Time to make it a bit more interesting. The following program is to be executed after the program above. The reason the program above has the "Press Return" messages in it is so that it can be paused long enough for this following program to be run.
#define INCL_DOSMEMMGR #include <os2.h> #include <iostream.h> #include <conio.h> void main() { int ret; void *BaseAddr,*SubAdr; char *MemoryName="\\SHAREMEM\\Mem.Mem"; ret=DosGetNamedSharedMem(&BaseAddr,MemoryName, PAG_READ|PAG_WRITE|PAG_EXECUTE); cout << "Open Return : " << ret << endl; cout << "Base Adr : " << BaseAddr << endl; //Get access to the named shared memory ret=DosSubSetMem(BaseAddr,DOSSUB_SPARSE_OBJ|DOSSUB_SERIALIZE, 1024*30); cout << "SubSet Return : " << ret << endl; cout << "Press Return" << endl; getch(); ret=DosSubAllocMem(BaseAddr,&SubAdr,1024); cout << "SubAlloc Return : " << ret << endl; cout << "SubAlloc Adr : " << SubAdr << endl; cout << "Press Return" << endl; getch(); ret=DosSubFreeMem(BaseAddr,SubAdr,1024); cout << "SubFree Return : " << ret << endl; cout << "Press Return" << endl; getch(); ret=DosSubUnsetMem(BaseAddr); cout << "SubUnSet Return : " << ret << endl; ret=DosFreeMem(BaseAddr); cout << "FreeBase Return : " << ret << endl; }
This one works too! So what was the big deal? As it turns out, the size parameter in the call to DosSubSetMem() must be the same in both processes. This doesn't really make sense to me, as one would think that the second process would be able to know the size used by the first process. After all, the memory block is already initialized by the first process. The thing that bothered me the most about the DosSubSetMem() call, is that it returns error codes which aren't even documented as being returned by this function! This certainly didn't make life any easier the first time.
Included Files
Compiled versions of the above code are included as MEMCREATE and MEMOPEN. When executing these programs, MEMCREATE must be executed first and be paused at one of the "Press Enter" requests before MEMOPEN can be started. For fun, try running these programs while stopping them at different "Press Enter" requests. What you will notice, is that the allocated pointers offset will vary depending on where the other program is stopped. This indicates that the memory block is being properly shared by the two processes.
The Memory API
We've used many of the memory management function found in the Dos* API. A quick overview follows:
API Call | Function |
---|---|
DosAllocSharedMem | Allocate a block of named or unnamed shared memory. |
DosGetNamedSharedMem | Get access to a named block of shared memory. |
DosSubSetMem | Initialize memory for suballocation if DOSSUB_INIT is used; otherwise, it attaches to an existing block of memory (provided DOSSUB_GROW is not used) |
DosSubAllocMem | Allocate a chunk of ram from the heap created by DosSubSetMem. |
DosSubFreeMem | Return memory to the heap. |
DosSubUnsetMem | Undo the effects of DosSubSetMem. |
DosFreeMem | Free the memory allocated by DosAllocSharedMem. |
More details on these functions can be found in Control Program Guide and Reference.
To emphasize an important detail, when DosSubSetMem() is used with shared memory, the length parameter must be the same in all processes using the shared memory block.
Back to OOP
Memory Objects
The key to our new and improved Queue objects is better use of shared memory. Shared memory uses the same API as private memory. This gives us an opportunity to flex our OOP muscles a bit by creating a hierarchy that has the potential to encapsulate both shared and non-shared memory.
MemoryObj | | SharedMemoryObj PrivateMemoryObj
Memory Object Hierarchy
Although we won't develop the PrivateMemoryObj here, its prototype is included in the header file for the memory objects. The difference between the shared and private memory objects, is that the private memory object would use DosAllocMem() instead of DosAllocSharedMem() to allocate memory for the heap.
MemoryObj
As the base of this hierarchy, this class provides methods and data members which encapsulate properties which are common to both private and shared memory. These properties are: a base address, methods for suballocation, and a method to free the base memory.
These classes provide the methods necessary to create or gain access to the base memory block. In the case of shared memory, the name of the memory block is also stored.
Included Files
The files MEMOBJ.H and MEMOBJ.CPP contain all the source code necessary to compile the MemoryObj and SharedMemoryObj objects.
New and Improved Queues
One of the nice things about OOP is that even though the internals of an object are changed, code external to the object can use it without modification providing the objects interface remains the same. This is almost the case here. The queue client remains the same. However, because memory is now "officially allocated" in the client, the queue server now has to deallocate the memory the queue client allocates. The new version of the server code follows.
int main() { int loop; QueueMessage *Mesg; EventSemaphore Sem((char*)NULL,Semaphore::SemCreate,DC_SEM_SHARED); ServerQueueObj InQueue(SMemName,NemSize,QName,0,&Sem); loop=1; Sem.Reset(); cout << "Queue Server Active !" << endl; while(loop){ Mesg=(QueueMessage *) InQueue.Read(0,1); while(InQueue.GetError() ){ Sem.Wait(); Mesg=(QueueMessage *) InQueue.Read(0,1); } if(Mesg->Number == -1){ loop=0; cout << "Terminating" << endl; } else{ cout << "Number= " << Mesg->Number << endl; } InQueue.FreeLastReadMem(); } return 0; }
We now have queue objects which are not limited by the way they use shared memory. Our goal has been accomplished.
Included Files
Only the files QUEUEOBJ.CPP, QUEUEOBJ.H, and QSERVE.CPP are different than those included in part one of this column. However, for convenience, the complete set of files will be included.
Summary
Building upon our experience with shared memory from last issue, we developed a new memory hierarchy. The memory hierarchy was developed only far enough as to be useful to manipulate the shared memory pool used by the queue objects. Extending it further is left as an exercise to the reader. The new queue objects are not limited by the shared memory allocation scheme as were the ones developed last time.