A shared memory class is shown in Figure 8.11. As defined, this class can be used only with processes that have access to the same shared memory ID.
Figure 8.11 Header file for a basic shared memory class.
File : Shared_mem.h /* A VERY simplified shared memory class for use in a std UNIX environment. See the text for instructions on how to use this class. Copyright (c) 2002 J. S. Gray + Exit codes for class operations: 1 - Unable to allocate memory 2 - Unable map memory 3 - Could not remove shared memory 10 */ #pragma interface <-- 1 #ifndef Shared_mem_h #define Shared_mem_h #define _GNU_SOURCE + #include #include #include #include #include 20 #include #include using namespace std; template // Allow for different data types class Shared_mem { + public: Shared_mem ( ); // Constructor ~Shared_mem( ); // Destructor - remove shared memory void Put( const S_type ); // Assign value to shared memory S_type Get( ); // Return value from shared memory 30 private: int shmid; // ID of shared memory S_type *shm_ptr; // Reference to shared memory pid_t my_pid; // Hang on to originator PID + }; #endif
(1) This notifies the compiler that a template class is being declared.
The shared memory class is templated to allow the passing of a data type. The shared memory class generates a private shared memory segment of the appropriate size for the data type. There are four public methods and three private data members in the shared memory class. The public methods and their functionality are described in Table 8.21.
Table 8.21. Shared_mem Class Methods.
Method name |
Explanation |
---|---|
Shared_mem |
This is the class constructor. This method generates the shared memory segment. The size of the segment is set by the data type. Once created, the segment is attached. The creating PID is saved in the my_pid data member. |
~Shared_mem |
The class destructor. This method removes the shared memory segment from the system if the calling function is the process that created the segment. |
Put |
Put assigns a value to the shared memory segment. |
Get |
Get retrieves the current value stored in the memory segment. |
The C++ code that implements the shared memory methods is found in Program 8.7, Shared_mem.cxx . Again, as with the previously defined System V IPC classes ( Message_que and SSemaphore ), this is a very rudimentary implementation.
Program 8.7 Program code for the shared memory class.
File : Shared_mem.cxx #pragma implementation <-- 1 /* Shared memory implementation - Copyright (c) 2002 J. S. Gray Compile with: -fexternal-templates + */ #include "Shared_mem.h" // Generate private mem segment template // Generalize data type Shared_mem::Shared_mem( ){ 10 my_pid = getpid( ); // Save PID of creating process if ((shmid = shmget(IPC_PRIVATE, sizeof(S_type), IPC_CREAT 0660)) < 0) exit(1); if ((shm_ptr = (S_type *) shmat(shmid, NULL, 0)) == NULL) exit(2); + } // Remove memory if creator template Shared_mem::~Shared_mem( ) { if ( getpid( ) == my_pid ) { 20 shmdt( (char *) shm_ptr ); if ( shmctl(shmid, IPC_RMID, (struct shmid_ds *) 0) == -1 ) exit( 3 ); } } + // Assign value to this location template void Shared_mem::Put( const S_type stuff ){ *shm_ptr = stuff; 30 } // Retrieve value from location template S_type Shared_mem::Get( ){ + static S_type stuff; stuff = *shm_ptr; return stuff; } // Force instantiation 40 typedef Shared_mem Shared_int; typedef Shared_mem Shared_char; typedef Shared_mem Shared_float; typedef Shared_mem Shared_double;
(1) This notifies the compiler that a template class is being defined.
Note that since templates are involved, a few more gymnastics are called for if we want to keep our class declaration and definition code in separate files. To accomplish this, using the g++ compiler, the directive #pragma interface must be placed at the top of the code in the header file containing the class declaration, while the directive #pragma implementation is placed in the file with the class definition (the corresponding .cxx file). At the bottom of the class definition, a typedef is used to coerce the compiler into generating object code for each specified data type. Lastly, when we compile the shared memory class into object code, the command-line compile option -fexternal-templates (generate external templates) must be specified along with the -c option. As if this were not enough, newer versions of the compiler may notify the user that the external templates option is deprecated (may not be supported in future versions). The compiler switch: -Wno-deprecated can be used to silence these warnings. The compilation of code containing templates can be somewhat daunting. The latest information on the g++ compiler can be obtained from the site http://www.gnu.org.
To use the shared memory class, the files Shared_mem.h and Shared_mem.cxx should reside locally. The Shared_mem class is compiled into object code with the command line
linux$ g++ Shared_mem.cxx -c -fexternal-templates
At the top of the source file that uses a Shared_mem object, add the statement
#include "Shared_mem.h"
to make the class definition available to the compiler. When compiling the source file, include the message queue object code file
linux$ g++ your_file_name.cxx Shared_mem.o
Program 8.8 uses the Shared_mem class. This program is passed a small integer value (16) on the command line. It goes on to generate a number of child processes. The first process generated is considered to be process 0, the next 1, and so on. As each process is generated, it displays its number in the sequence. To make things a bit more interesting, the output is displayed in a tree-like format. The height of the tree being the value passed in on the command line. Common information, such as the process sequence number, the width of field for output, etc. are stored in a shared memory which is available to the parent and all child processes. The source for the program is shown as in Program 8.8.
Program 8.8 Using the shared memory class.
File : p8.8 .cxx #include #include #include #include + #include "Shared_mem.h" int main(int argc, char *argv[]) { int n; Shared_mem s[4]; <-- 1 10 if (argc < 2 (n = atoi(argv[1])) > 6 n < 1 ) { cerr << "Usage: " << argv[0] << " value 1-6" << endl; return 1; } setbuf(stdout, NULL); // Standard output is unbuffered + // Starting values s[0].Put(0); // Process counter s[1].Put(1); // Process # when @ end of line s[2].Put(64); // Output width s[3].Put(0); // Process # that starts new line 20 cout << " Tree of level " << n << endl << endl; for (int i=0; i < n; ++i) { if ( !fork() ) { // in the child int temp_width = s[2].Get(); // get output width if ((s[0].Get()) == s[3].Get()) // if @ start of line use 1/2 + temp_width /= 2; cout << setiosflags(ios::uppercase) << hex << setw(temp_width) << (s[0].Get()) % 16; s[0].Put(s[0].Get()+1); // count the process } 30 if ( s[0].Get() == s[1].Get() ){ // If at the end of line s[1].Put( s[1].Get() * 2 + 1 ); // update end of line process # s[2].Put( s[2].Get() / 2 ); // decrease output width s[3].Put( s[0].Get() ); // new sart of line process # cout << endl << endl; + } wait(0); // wait for the child to finish! } return 0; }
(1) Instantiate an array of shared memory objectseach to hold an integer value.
In line 9 of the program, a four-element array of shared memory objects is instantiated . In line 14 the setbuf library function is used to turn off line buffering of standard out. Data streams can be block buffered, line buffered, or unbuffered. With block buffering, the operating system saves data in a temporary location until it has a block, at which time it performs the I/O operation. File I/O is normally block buffered. With line buffering, data is saved until a newline is encountered , while unbuffered data is made available immediately. The fflush library function can also be used to force the system to transfer data, as will the closing of a stream ( fclose ). By default, standard output ( stdout , cout ) is line buffered, while standard error ( stderr , cerr ) is not. Setting I/O to unbuffered causes the standard library I/O functions to call the underlying I/O system call for each character of data, which in turn increases the amount of CPU time the process requires. Beneath the covers, setbuf is actually an alias to the setvbuf library call. As used in the program example, the first argument of setbuf references the file stream, while second argument references the buffer where data is to be stored. If the second argument is set to NULL (as is our case), only the mode of the stream is affected.
Lines 16 through 20 establish the initial contents of the shared memory segments. The for loop is driven by the value passed on the command line. Each pass through the loop generates one level of the output display. The call to fork generates a child process. Each child process announces its presence by displaying a hexadecimal sequence value at a specific location. Back in the parent, a check is made to determine if the current shared memory values need to be adjusted (such as when at the end of a line of output). The parent process waits for the child to terminate (line 36).
Figure 8.12 shows the output generated when the program is run and passed the value 4. Note the dotted lines were not produced by the program.
Figure 8.12. A run of program p8.7 when passed the value 4.
Programs and Processes
Processing Environment
Using Processes
Primitive Communications
Pipes
Message Queues
Semaphores
Shared Memory
Remote Procedure Calls
Sockets
Threads
Appendix A. Using Linux Manual Pages
Appendix B. UNIX Error Messages
Appendix C. RPC Syntax Diagrams
Appendix D. Profiling Programs