Queues Continued: Difference between revisions
mNo edit summary |
|||
(3 intermediate revisions by the same user not shown) | |||
Line 1: | Line 1: | ||
Written by [[Gordon Zeglinski]] | ''Written by [[Gordon Zeglinski]]'' | ||
==Queues Continued== | ==Queues Continued== | ||
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 | #include <os2.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 : "< | cout << "Alloc Return : " << ret << endl; | ||
cout<<"Base Adr : "< | 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 : "< | cout << "SubSet Return : " << ret << endl; | ||
cout<<"Press Return"< | 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 : "< | cout << "SubAlloc Return : " << ret << endl; | ||
cout<<"SubAlloc Adr : "< | cout << "SubAlloc Adr : " << SubAdr << endl; | ||
cout<<"Press Return"< | 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 : "< | cout << "SubFree Return : " << ret << endl; | ||
cout<<"Press Return"< | cout << "Press Return" << endl; | ||
getch(); | getch(); | ||
//Uninitialze suballocation struvtures | //Uninitialze suballocation struvtures | ||
ret=DosSubUnsetMem(BaseAddr); | ret=DosSubUnsetMem(BaseAddr); | ||
cout<<"SubUnSet Return : "< | 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 : "< | 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> | |||
#define INCL_DOSMEMMGR | #define INCL_DOSMEMMGR | ||
#include | #include <os2.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 : "< | cout << "Open Return : " << ret << endl; | ||
cout<<"Base Adr : "< | 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 : "< | cout << "SubSet Return : " << ret << endl; | ||
cout<<"Press Return"< | cout << "Press Return" << endl; | ||
getch(); | getch(); | ||
ret=DosSubAllocMem(BaseAddr,&SubAdr,1024); | ret=DosSubAllocMem(BaseAddr,&SubAdr,1024); | ||
cout<<"SubAlloc Return : "< | cout << "SubAlloc Return : " << ret << endl; | ||
cout<<"SubAlloc Adr : "< | cout << "SubAlloc Adr : " << SubAdr << endl; | ||
cout<<"Press Return"< | cout << "Press Return" << endl; | ||
getch(); | getch(); | ||
ret=DosSubFreeMem(BaseAddr,SubAdr,1024); | ret=DosSubFreeMem(BaseAddr,SubAdr,1024); | ||
cout<<"SubFree Return : "< | cout << "SubFree Return : " << ret << endl; | ||
cout<<"Press Return"< | cout << "Press Return" << endl; | ||
getch(); | getch(); | ||
ret=DosSubUnsetMem(BaseAddr); | ret=DosSubUnsetMem(BaseAddr); | ||
cout<<"SubUnSet Return : "< | cout << "SubUnSet Return : " << ret << endl; | ||
ret=DosFreeMem(BaseAddr); | ret=DosFreeMem(BaseAddr); | ||
cout<<"FreeBase Return : "< | cout << "FreeBase Return : " << ret << endl; | ||
} | } | ||
</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 | |||
|- | |||
|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. | 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, 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. | |||
Although we won't develop the PrivateMemoryObj here, | |||
===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 !"< | |||
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"< | cout << "Terminating" << endl; | ||
} | } | ||
else{ | else{ | ||
cout<<"Number= "< | 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:C++ Articles]] | |||
Latest revision as of 15:18, 22 October 2022
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.
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.