Queues Continued

From EDM2
Jump to: navigation, search

Written by Gordon Zeglinski

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.

SharedMemoryObj and PrivateMemoryObj

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.