Using a File as Shared Memory
Most versions of Linux-UNIX also support the mmap system call, which can be used to map a file to a process's virtual memory address space. In many ways mmap is more flexible than its shared memory system call counterpart . Once a mapping has been established, standard system calls rather than specialized system calls can be used to manipulate the shared memory object (Table 8.11). Unlike memory, the contents of a file are nonvolatile and will remain available even after a system has been shut down (and rebooted).
Table 8.11. Summary of the mmap System Call.
Include File(s) |
|
Manual Section |
2 |
|
Summary |
#ifdef _POSIX_MAPPED_FILES <-- 1 void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset); #endif (1) If _POSIX_MAPPED_FILES has been defined. |
|||
Return |
Success |
Failure |
Sets errno |
|
A pointer to the mapped area |
MAP_FAILED ((void *) -1) |
Yes |
The mmap system call requires six arguments. The first, start , is the address for attachment. As with the shmat system call, this argument is most often set to 0, which directs the system to choose a valid attachment address. The number of bytes to be attached is indicated by the second argument, length . While the call will allow the user to specify a number of bytes for length that will extend beyond the end of the mapped file, an actual reference to these locations will generate an error (a SIGBUS signal). The third argument, prot , is used to set the type of access (protection) for the segment. The specified access should not be in conflict with the access permissions for the associated file descriptor. The prot argument uses the defined constants found in the include file . These constants are shown in Table 8.12.
Table 8.12. Defined Protection Constants.
Defined Constant |
Access |
---|---|
PROT_READ |
Read access to specified region. |
PROT_WRITE |
Write access to specified region. |
PROT_EXEC |
Execute access to specified region. |
PROT_NONE |
No access. |
Constants can be OR ed to provide different combinations of access. The manual page for mmap notes that on some systems PROT_WRITE is implemented as PROT_READ PROT_WRITE, and PROT_EXEC as PROT_READ PROT_EXEC. In any case, PROT_WRITE must be set if the process is to write to the mapped segment. The fourth argument, flags , specifies the type of mapping. Mapping types are also indicated using defined constants from the include file. These constants are shown in Table 8.13.
Table 8.13. Defined Mapping Type Constants.
Defined Constant |
Mapping Type |
---|---|
MAP_SHARED |
Share all changes. |
MAP_PRIVATE |
Do not share changes. |
MAP_FIXED |
Interpret the value for the start argument exactly. |
The first two constants specify whether write s to the shared memory will be shared with other processes or be private. MAP_SHARED and MAP_PRIVATE are exclusionary. When specifying MAP_PRIVATE, a private copy is not generated until the first write to the mapped object has occurred. These specifications are retained across a fork system call but not across a call to exec . MAP_FIXED directs the system to explicitly use the address value in start . When MAP_FIXED is indicated, the values for start and length should be a multiple of the system's page size . Specifying MAP_FIXED greatly reduces the portability of a program, and its use is discouraged. When specifying the flags argument, either MAP_SHARED or MAP_PRIVATE must be indicated. Linux also supports the flags shown in Table 8.14.
Table 8.14. Linux-Specific Defined Mapping Type Constants.
Defined Constant |
Mapping Type |
---|---|
MAP_GROWSDOWN |
Treat the segment as a stack. |
MAP_EXECUTABLE |
Mark the segment as executable. |
MAP_DENYWRITE |
Do not allow writing. |
MAP_NORESERVE |
Do not check for reservations . |
MAP_LOCKED |
Lock the mapped segment. |
The fifth argument, fd , is a valid open file descriptor. Once the mapping is established, the file can be closed. The sixth argument, offset , is used to set the starting position for the mapping.
If the mmap system call is successful, it returns a reference to the mapped memory object. If the call fails, it returns the defined constant MAP_FAILED (which is actually the value -1 cast to a void * ). A failed call will set the value in errno to reflect the error encountered . The errors for mmap are shown in Table 8.15.
Table 8.15. mmap Error Messages.
# |
Constant |
perror Message |
Explanation |
---|---|---|---|
6 |
ENXIO |
No such device or address |
The values for off or off + len are illegal for the specified device. |
9 |
EBADF |
Bad file descriptor |
The file referenced by fd is invalid. |
11 |
EAGAIN |
Resource temporarily unavailable |
|
12 |
ENOMEM |
Cannot allocate memory |
Insufficient address space to implement the mapping. |
13 |
EACCES |
Permission denied |
|
19 |
ENODEV |
No such device |
fd references an invalid device (such as a terminal). |
22 |
EINVAL |
Invalid argument |
|
26 |
ETXTBSY |
Text file busy |
MAP_DENYWRITE was set but fd is open for writing. |
While the system will automatically unmap a region when a process terminates, the system call munmap , shown in Table 8.16, can be used to explicitly unmap pages of memory.
Table 8.16. Summary of the munmap System Call.
Include File(s) |
|
Manual Section |
2 |
|
Summary |
#ifdef _POSIX_MAPPED_FILES int munmap(void *start, size_t length); #endif |
|||
Return |
Success |
Failure |
Sets errno |
|
-1 |
Yes |
The munmap system call is passed the starting address of the memory mapping (argument start ) and the size of the mapping (argument length ). If the call is successful, it returns a value of 0. Future references to unmapped addresses generate a SIGVEGV signal. If the munmap system call fails, it returns the value -1 and sets the value in errno to EINVAL. The interpretation of munmap - related error is given in Table 8.17.
Table 8.17. munmap Error Messages.
# |
Constant |
perror Message |
Explanation |
---|---|---|---|
22 |
EINVAL |
Invalid argument |
|
The msync system call is used in conjunction with mmap to synchronize the contents of mapped memory with physical storage (Table 8.18). A call to msync will cause the system to write all modified memory locations to their as sociated physical storage locations. If MAP_SHARED is specified with mmap , the storage location is a file. If MAP_PRIVATE is specified, then the storage location is the swap area.
Table 8.18. Summary of the msync Library Function.
Include File(s) |
|
Manual Section |
2 |
|
Summary |
#ifdef _POSIX_MAPPED_FILES #ifdef _POSIX_SYNCHRONIZED_IO int msync(const void *start, size_t length, int flags); #endif #endif |
|||
Return |
Success |
Failure |
Sets errno |
|
-1 |
Yes |
The start argument for msync specifies the address of the mapped memory; the length argument specifies the size (in bytes) of the memory. The flags argument directs the system to take the actions shown in Table 8.19.
Table 8.19. Defined Flag Constants for msync .
Defined Constant |
Action |
---|---|
MS_ASYNC |
Return immediately once all writes have been scheduled. |
MS_SYNC |
Return once all writes have been performed. |
MS_INVALIDATE |
Invalidate cached copies of memorysystem reloads memory from the associated storage location. |
If msync fails, it returns a -1 and sets errno (Table 8.20). If the call is successful, it returns a value of 0.
Table 8.20. mmap Error Messages.
# |
Constant |
perror Message |
Explanation |
---|---|---|---|
1 |
EPERM |
Operation not permitted |
MS_INVALIDATE indicated but some of the referenced locations are locked in memory. |
14 |
EFAULT |
Bad address |
Invalid address reference. |
16 |
EBUSY |
Device or resource busy |
MS_SYNC and MS_INVALIDATE specified but some of the referenced addresses are currently locked. |
22 |
EINVAL |
Invalid argument |
|
Program 8.6 demonstrates the use of the mmap system call.
Program 8.6 Using mmap .
File : p8.6.cxx /* Using the mmap system call */ #define _GNU_SOURCE + #include #include #include #include #include 10 #include #include #include #include #include + using namespace std; int main(int argc, char *argv[]){ int fd, changes, i, random_spot, kids[2]; struct stat buf; 20 char *the_file, *starting_string="ABCDEFGHIJKLMNOPQRSTUVWXYZ"; if (argc != 3) { cerr << "Usage " << *argv << " file_name #_of_changes" << endl; return 1; } + if ((changes = atoi(argv[2])) < 1) { cerr << "# of changes < 1" << endl; return 2; } if ((fd = open(argv[1], O_CREAT O_RDWR, 0666)) < 0) { 30 perror("file open"); return 3; } write(fd, starting_string, strlen(starting_string)); // Obtain size of file + if (fstat(fd, &buf) < 0) { perror("fstat error"); return 4; } // Establish the mapping 40 if ((the_file = (char *) mmap(0, (size_t) buf.st_size, PROT_READ PROT_WRITE, MAP_SHARED, fd, 0)) == (void *) - 1) { perror("mmap failure"); exit(5); + } for (i = 0; i < 2; ++i) if ((kids[i] = (int) fork()) == 0) while (1) { cout << "Child " << getpid() << " finds: " << the_file << endl; 50 sleep(1); } srand((unsigned) getpid()); for (i = 0; i < changes; ++i) { random_spot = (int) (rand() % buf.st_size); + *(the_file + random_spot) = '*'; sleep(1); } cout << "In parent, done with changes" << endl; for (i = 0; i < 2; ++i) 60 kill(kids[i], 9); cout << "The file now contains: " << the_file << endl; return 0; }
Program 8.6 uses a parent/two-child process arrangement to demonstrate the use of mmap . The parent process modifies the contents of a memory-mapped file. Each child process repetitively displays the contents of the mapped files to allow verification of the changes. The program is passed two command-line arguments. The first argument is the name of a file that it will use for mapping. The second argument indicates the number of modifications that should be made to the file. Upon execution of the program, the validity of the command-line arguments is checked. If problems are encountered, an appropriate error message is generated and the program exits. If the command-line arguments are good, the program opens, for reading and writing, the file whose name was passed as the first command-line argument. As the O_CREAT flag is specified, if the file does not exist, it will be created. Next, the string "ABCDEFGHIJKLMNOPQRSTUVWXYZ" is written to the first part of the file. Following this, the fstat call is used to determine the size of the file.
In our example, if we start with an empty file, the size of the file is actually the length of the string that is written to the file. However, this would not be true if the file contained previous data. In many cases we will want to know the full size of the file to be mapped fstat provides us with a handy way of determining the file's size (it is returned as part of the stat structure). The call to mmap (line 40) establishes the actual mapping. We allow the system to pick the address and indicate that we want to be able to read from and write to the mapped memory region. We also specify the region be marked as shared, be associated with the open file descriptor fd , and have an offset (starting position within the file) of 0. Two child processes are then generated. Each child process displays the contents of the memory-mapped file using the the_file reference which was returned from the initial call to mmap . It is important to note that a call to read was not needed. The child process then sleep s one second and repeats the same sequence of activities until a terminating signal is received. The parent process loops for the number of times specified by the second command-line argument. Within this loop the parent process randomly picks a memory-mapped location and changes it to an asterisk ( * ). Again, this is done by direct reference to the location using the the_file reference; notice no write function is used. Between changes, the parent sleeps one second to slow down the processing sequence. Once the parent process is done, it displays the final contents of the memory-mapped file, removes the child processes, and exits. A sample run of the program is shown in Figure 8.10.
Figure 8.10 A sample run of Program 8.6.
linux$ p8.6 demo 7 Child 16592 finds: ABCDEFGHIJKLMNOPQRSTUVWXYZ Child 16593 finds: ABCDEFGHIJKLMNOPQRSTUVWXYZ Child 16592 finds: ABCDEFG*IJKLMNOPQRSTUVWXYZ Child 16593 finds: ABCDEFG*IJKLMNOPQRSTUVWX*Z Child 16592 finds: ABCDEFG*IJKLMNOPQRSTUVWX*Z Child 16593 finds: ABCDEF**IJKLMNOPQRSTUVWX*Z Child 16592 finds: ABCDEF**IJKLMNOPQRSTUVWX*Z Child 16593 finds: ABCDEF**IJ*LMNOPQRSTUVWX*Z Child 16592 finds: ABCDEF**IJ*LMNOPQRSTUVWX*Z Child 16593 finds: ABCDEF**I**LMNOPQRSTUVWX*Z Child 16592 finds: ABCDEF**I**LMNOPQRSTUVWX*Z Child 16593 finds: ABCDEF**I**LMNOPQRS*UVWX*Z Child 16592 finds: ABCDEF**I**LMNOPQRS*UVWX*Z Child 16593 finds: ABCDEF**I**L*NOPQRS*UVWX*Z Child 16592 finds: ABCDEF**I**L*NOPQRS*UVWX*Z In parent, done with changes The file now contains: ABCDEF**I**L*NOPQRS*UVWX*Z
In this invocation the child processes, PIDs 16592 and 16593, initially find the mapped location to contain the unmodified starting string. A second check of the mapped location shows that each child now sees the string with a single ' * ' replacing the letter H . Additional passes reveal further modifications. When all of the processes have terminated , we will find that the file demo will contain the fully modified string.