Jump to content

Queues Continued: Difference between revisions

From EDM2
mNo edit summary
Ak120 (talk | contribs)
mNo edit summary
Line 5: Line 5:


==The Saga Begins==
==The Saga Begins==
After a day of frustration, a short email is sent. Following is a paraphrased transcript:
After a day of frustration, a short email is sent. Following is a paraphrased transcript:
  "Hey Lar, U ever use DosSubSetMem with shared Memory?"
  "Hey Lar, U ever use DosSubSetMem with shared Memory?"
   
   
Line 16: Line 13:


==Off to the Races==
==Off to the Races==
 
Something simple... hmmm... Okay, lets try this:
Something simple...hmmm...Okay, lets try this:
<code>
 
  #define INCL_DOSMEMMGR
  #define INCL_DOSMEMMGR
  #include &ltos2.h>
  #include <os2.h>
#include &ltiostream.h>
#include &ltconio.h>
   
   
#include <iostream.h>
#include <conio.h>
   
   
  void main() {
  void main() {
Line 34: Line 29:
     ret=DosAllocSharedMem(&BaseAddr,MemoryName,1024*30,
     ret=DosAllocSharedMem(&BaseAddr,MemoryName,1024*30,
           PAG_EXECUTE|PAG_READ|PAG_WRITE);
           PAG_EXECUTE|PAG_READ|PAG_WRITE);
     cout<<"Alloc Return : "<&ltret<&ltendl;
     cout << "Alloc Return : " << ret << endl;
     cout<<"Base Adr : "<&ltBaseAddr<&ltendl;
     cout << "Base Adr : " << BaseAddr << endl;
   
   
  //Prepare shared Memory for sub allocation
  //Prepare shared Memory for sub allocation
Line 41: Line 36:
     ret=DosSubSetMem(BaseAddr,
     ret=DosSubSetMem(BaseAddr,
           DOSSUB_SPARSE_OBJ|DOSSUB_SERIALIZE|DOSSUB_INIT,1024*30);
           DOSSUB_SPARSE_OBJ|DOSSUB_SERIALIZE|DOSSUB_INIT,1024*30);
     cout<<"SubSet Return : "<&ltret<&ltendl;
     cout << "SubSet Return : " << ret << endl;
   
   
     cout<<"Press Return"<&ltendl;
     cout << "Press Return" << endl;
     getch();
     getch();
   
   
  //suballocate a bit of memory
  //suballocate a bit of memory
     ret=DosSubAllocMem(BaseAddr,&SubAdr,1024);
     ret=DosSubAllocMem(BaseAddr,&SubAdr,1024);
     cout<<"SubAlloc Return : "<&ltret<&ltendl;
     cout << "SubAlloc Return : " << ret << endl;
     cout<<"SubAlloc Adr : "<&ltSubAdr<&ltendl;
     cout << "SubAlloc Adr : " << SubAdr << endl;
   
   
     cout<<"Press Return"<&ltendl;
     cout << "Press Return" << endl;
     getch();
     getch();
   
   
  //free the suballocated ram
  //free the suballocated ram
     ret=DosSubFreeMem(BaseAddr,SubAdr,1024);
     ret=DosSubFreeMem(BaseAddr,SubAdr,1024);
     cout<<"SubFree Return : "<&ltret<&ltendl;
     cout << "SubFree Return : " << ret << endl;
   
   
     cout<<"Press Return"<&ltendl;
     cout << "Press Return" << endl;
     getch();
     getch();
   
   
  //Uninitialze suballocation struvtures
  //Uninitialze suballocation struvtures
     ret=DosSubUnsetMem(BaseAddr);
     ret=DosSubUnsetMem(BaseAddr);
     cout<<"SubUnSet Return : "<&ltret<&ltendl;
     cout << "SubUnSet Return : " << ret << endl;
   
   
  //Free up the main memory block
  //Free up the main memory block
     ret=DosFreeMem(BaseAddr);
     ret=DosFreeMem(BaseAddr);
     cout<<"FreeBase Return : "<&ltret<&ltendl;
     cout << "FreeBase Return : " << ret << endl;
  }
  }
 
</code>
 
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!
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.
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.
 
<code>
<nowiki>
  #define INCL_DOSMEMMGR
  #define INCL_DOSMEMMGR
  #include &ltos2.h>
  #include <os2.h>
#include &ltiostream.h>
#include &ltconio.h>
   
   
#include <iostream.h>
#include <conio.h>
   
   
  void main() {
  void main() {
Line 90: Line 82:
     ret=DosGetNamedSharedMem(&BaseAddr,MemoryName,
     ret=DosGetNamedSharedMem(&BaseAddr,MemoryName,
           PAG_READ|PAG_WRITE|PAG_EXECUTE);
           PAG_READ|PAG_WRITE|PAG_EXECUTE);
     cout<<"Open Return : "<&ltret<&ltendl;
     cout << "Open Return : " << ret << endl;
     cout<<"Base Adr : "<&ltBaseAddr<&ltendl;
     cout << "Base Adr : " << BaseAddr << endl;
   
   
  //Get access to the named shared memory
  //Get access to the named shared memory
     ret=DosSubSetMem(BaseAddr,DOSSUB_SPARSE_OBJ|DOSSUB_SERIALIZE,
     ret=DosSubSetMem(BaseAddr,DOSSUB_SPARSE_OBJ|DOSSUB_SERIALIZE,
                         1024*30);
                         1024*30);
     cout<<"SubSet Return : "<&ltret<&ltendl;
     cout << "SubSet Return : " << ret << endl;
   
   
     cout<<"Press Return"<&ltendl;
     cout << "Press Return" << endl;
     getch();
     getch();
   
   
     ret=DosSubAllocMem(BaseAddr,&SubAdr,1024);
     ret=DosSubAllocMem(BaseAddr,&SubAdr,1024);
     cout<<"SubAlloc Return : "<&ltret<&ltendl;
     cout << "SubAlloc Return : " << ret << endl;
     cout<<"SubAlloc Adr : "<&ltSubAdr<&ltendl;
     cout << "SubAlloc Adr : " << SubAdr << endl;
   
   
     cout<<"Press Return"<&ltendl;
     cout << "Press Return" << endl;
     getch();
     getch();
   
   
     ret=DosSubFreeMem(BaseAddr,SubAdr,1024);
     ret=DosSubFreeMem(BaseAddr,SubAdr,1024);
     cout<<"SubFree Return : "<&ltret<&ltendl;
     cout << "SubFree Return : " << ret << endl;
   
   
     cout<<"Press Return"<&ltendl;
     cout << "Press Return" << endl;
     getch();
     getch();
   
   
     ret=DosSubUnsetMem(BaseAddr);
     ret=DosSubUnsetMem(BaseAddr);
     cout<<"SubUnSet Return : "<&ltret<&ltendl;
     cout << "SubUnSet Return : " << ret << endl;
   
   
     ret=DosFreeMem(BaseAddr);
     ret=DosFreeMem(BaseAddr);
     cout<<"FreeBase Return : "<&ltret<&ltendl;
     cout << "FreeBase Return : " << ret << endl;
  }
  }
</nowiki>
</code>
 
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.
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==
==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.
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==
==The Memory API==
 
We've used many of the memory management function found in the Dos* API. A quick overview follows:
We've used many of the memory management function found in the Dos* API. A quick overview follows :
{|class="wikitable"
 
!API Call||Function
|-
  API Call                 Function
|DosAllocSharedMem||Allocate a block of named or unnamed shared memory.
|-
  DosAllocSharedMem       Allocate a block of named or
|DosGetNamedSharedMem||Get access to a named block of shared memory.
                          unnamed 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)
  DosGetNamedSharedMem     Get access to a named block of
|-
                          shared memory.
|DosSubAllocMem||Allocate a chunk of ram from the heap created by DosSubSetMem.
|-
  DosSubSetMem             Initialize memory for
|DosSubFreeMem||Return memory to the heap.
                          suballocation if DOSSUB_INIT
|-
                          is used; otherwise, it
|DosSubUnsetMem||Undo the effects of DosSubSetMem.
                          attaches to an existing block
|-
                          of memory (provided
|DosFreeMem||Free the memory allocated by DosAllocSharedMem.
                          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.
More details on these functions can be found in Control Program Guide and Reference.


Line 165: Line 142:


==Back to OOP==
==Back to OOP==
===Memory Objects===
===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.
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
               MemoryObj
                 |    |
                 |    |
   SharedMemoryObj  PrivateMemoryObj
   SharedMemoryObj  PrivateMemoryObj


===Memory Object Hierarchy===
===Memory Object Hierarchy===
Although we won't develop the PrivateMemoryObj here, it's 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.
Although we won't develop the PrivateMemoryObj here, it's 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===
===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.
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===
===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.
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===
===Included Files===
The files MEMOBJ.H and MEMOBJ.CPP contain all the source code necessary to compile the MemoryObj and SharedMemoryObj objects.
The files MEMOBJ.H and MEMOBJ.CPP contain all the source code necessary to compile the MemoryObj and SharedMemoryObj objects.


===New and Improved Queues===
===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.
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.
 
<code>
  int main() {
  int main() {
     int loop;
     int loop;
     QueueMessage *Mesg;
     QueueMessage *Mesg;
     EventSemaphore Sem((char*)NULL,Semaphore::SemCreate,DC_SEM_SHARED);
     EventSemaphore Sem((char*)NULL,Semaphore::SemCreate,DC_SEM_SHARED);
     ServerQueueObj InQueue(SMemName,NemSize,QName,0,&Sem);
     ServerQueueObj InQueue(SMemName,NemSize,QName,0,&Sem);
     loop=1;
     loop=1;
     Sem.Reset();
     Sem.Reset();
     cout << "Queue Server Active !" << endl;
     cout<<"Queue Server Active !"<&ltendl;
     while(loop){
     while(loop){
       Mesg=(QueueMessage *) InQueue.Read(0,1);
       Mesg=(QueueMessage *) InQueue.Read(0,1);
       while(InQueue.GetError() ){
       while(InQueue.GetError() ){
Line 220: Line 177:
           Mesg=(QueueMessage *) InQueue.Read(0,1);
           Mesg=(QueueMessage *) InQueue.Read(0,1);
       }
       }
       if(Mesg->Number == -1){
       if(Mesg->Number == -1){
           loop=0;
           loop=0;
           cout<<"Terminating"<&ltendl;
           cout << "Terminating" << endl;
       }
       }
       else{
       else{
           cout<<"Number= "<&ltMesg-&gtNumber<&ltendl;
           cout << "Number= " << Mesg->Number << endl;
       }
       }
       InQueue.FreeLastReadMem();
       InQueue.FreeLastReadMem();
Line 233: Line 189:
  return 0;
  return 0;
  }
  }
 
</code>
We now have queue objects which are not limited by the way they use shared memory. Our goal has been accomplished.
We now have queue objects which are not limited by the way they use shared memory. Our goal has been accomplished.


===Included Files===
===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.
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==
==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.
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.


[[Category:Languages Articles]][[Category:C++]]
[[Category:Languages Articles]][[Category:C++]]

Revision as of 16:59, 7 November 2016

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, it's 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.