99
5.1 Shared Memory
If the call succeeds, it returns the address of the attached shared segment. Children cre-
ated by calls to fork inherit attached shared segments; they can detach the shared
memory segments, if desired.
When you’re finished with a shared memory segment, the segment should be
detached using shmdt (“SHared Memory DeTach”). Pass it the address returned by
shmat. If the segment has been deallocated and this was the last process using it, it is
removed. Calls to exit and any of the exec family automatically detach segments.
5.1.5 Controlling and Deallocating Shared Memory
The shmctl (“SHared Memory ConTroL”) call returns information about a shared
memory segment and can modify it.The first parameter is a shared memory segment
identifier.
To obtain information about a shared memory segment, pass IPC_STAT as the
second argument and a pointer to a struct shmid_ds.
To remove a segment, pass IPC_RMID as the second argument, and pass NULL as the
third argument.The segment is removed when the last process that has attached it
finally detaches it.
Each shared memory segment should be explicitly deallocated using shmctl when
you’re finished with it, to avoid violating the systemwide limit on the total number of
shared memory segments. Invoking exit and exec detaches memory segments but
does not deallocate them.
See the shmctl man page for a description of other operations you can perform on
shared memory segments.
5.1.6 An Example Program
The program in Listing 5.1 illustrates the use of shared memory.
Listing 5.1 (shm.c) Exercise Shared Memory
#include <stdio.h>
#include <sys/shm.h>
#include <sys/stat.h>
int main ()
{
int segment_id;
char* shared_memory;
struct shmid_ds shmbuffer;
int segment_size;
const int shared_segment_size = 0x6400;
/* Allocate a shared memory segment. */
segment_id = shmget (IPC_PRIVATE, shared_segment_size,
IPC_CREAT
| IPC_EXCL | S_IRUSR | S_IWUSR);
continues
06 0430 CH05 5/22/01 10:22 AM Page 99
100
Chapter 5 Interprocess Communication
/* Attach the shared memory segment. */
shared_memory = (char*) shmat (segment_id, 0, 0);
printf (“shared memory attached at address %p\n”, shared_memory);
/* Determine the segment’s size. */
shmctl (segment_id, IPC_STAT, &shmbuffer);
segment_size = shmbuffer.shm_segsz;
printf (“segment size: %d\n”, segment_size);
/* Write a string to the shared memory segment. */
sprintf (shared_memory, “Hello, world.”);
/* Detach the shared memory segment. */
shmdt (shared_memory);
/* Reattach the shared memory segment, at a different address. */
shared_memory = (char*) shmat (segment_id, (void*) 0x5000000, 0);
printf (“shared memory reattached at address %p\n”, shared_memory);
/* Print out the string from shared memory. */
printf (“%s\n”, shared_memory);
/* Detach the shared memory segment. */
shmdt (shared_memory);
/* Deallocate the shared memory segment. */
shmctl (segment_id, IPC_RMID, 0);
return 0;
}
5.1.7 Debugging
The ipcs command provides information on interprocess communication facilities,
including shared segments. Use the -m flag to obtain information about shared
memory. For example, this code illustrates that one shared memory segment,
numbered 1627649, is in use:
% ipcs -m
Shared Memory Segments
key shmid owner perms bytes nattch status
0x00000000 1627649 user 640 25600 0
If this memory segment was erroneously left behind by a program, you can use the
ipcrm command to remove it.
% ipcrm shm 1627649
Listing 5.1 Continued
06 0430 CH05 5/22/01 10:22 AM Page 100
101
5.2 Processes Semaphores
5.1.8 Pros and Cons
Shared memory segments permit fast bidirectional communication among any number
of processes. Each user can both read and write, but a program must establish and fol-
low some protocol for preventing race conditions such as overwriting information
before it is read. Unfortunately, Linux does not strictly guarantee exclusive access even
if you create a new shared segment with IPC_PRIVATE.
Also, for multiple processes to use a shared segment, they must make arrangements
to use the same key.
5.2 Processes Semaphores
As noted in the previous section, processes must coordinate access to shared memory.
As we discussed in Section 4.4.5,“Semaphores for Threads,” in Chapter 4,“Threads,”
semaphores are counters that permit synchronizing multiple threads. Linux provides a
distinct alternate implementation of semaphores that can be used for synchronizing
processes (called process semaphores or sometimes System V semaphores). Process sem-
aphores are allocated, used, and deallocated like shared memory segments. Although a
single semaphore is sufficient for almost all uses, process semaphores come in sets.
Throughout this section, we present system calls for process semaphores, showing how
to implement single binary semaphores using them.
5.2.1 Allocation and Deallocation
The calls semget and semctl allocate and deallocate semaphores, which is analogous to
shmget and shmctl for shared memory. Invoke semget with a key specifying a sema-
phore set, the number of semaphores in the set, and permission flags as for shmget; the
return value is a semaphore set identifier.You can obtain the identifier of an existing
semaphore set by specifying the right key value; in this case, the number of sema-
phores can be zero.
Semaphores continue to exist even after all processes using them have terminated.
The last process to use a semaphore set must explicitly remove it to ensure that the
operating system does not run out of semaphores.To do so, invoke
semctl with the
semaphore identifier, the number of semaphores in the set, IPC_RMID as the third argu-
ment, and any union semun value as the fourth argument (which is ignored).The
effective user ID of the calling process must match that of the semaphore’s allocator
(or the caller must be root). Unlike shared memory segments, removing a semaphore
set causes Linux to deallocate immediately.
Listing 5.2 presents functions to allocate and deallocate a binary semaphore.
06 0430 CH05 5/22/01 10:22 AM Page 101
102
Chapter 5 Interprocess Communication
Listing 5.2 (sem_all_deall.c) Allocating and Deallocating a Binary Semaphore
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/types.h>
/* We must define union semun ourselves. */
union semun {
int val;
struct semid_ds *buf;
unsigned short int *array;
struct seminfo *__buf;
};
/* Obtain a binary semaphore’s ID, allocating if necessary. */
int binary_semaphore_allocation (key_t key, int sem_flags)
{
return semget (key, 1, sem_flags);
}
/* Deallocate a binary semaphore. All users must have finished their
use. Returns -1 on failure. */
int binary_semaphore_deallocate (int semid)
{
union semun ignored_argument;
return semctl (semid, 1, IPC_RMID, ignored_argument);
}
5.2.2 Initializing Semaphores
Allocating and initializing semaphores are two separate operations.To initialize a sema-
phore, use
semctl with zero as the second argument and SETALL as the third argument.
For the fourth argument, you must create a union semun object and point its array
field at an array of unsigned short values. Each value is used to initialize one sema-
phore in the set.
Listing 5.3 presents a function that initializes a binary semaphore.
Listing 5.3 (sem_init.c) Initializing a Binary Semaphore
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
06 0430 CH05 5/22/01 10:22 AM Page 102
103
5.2 Processes Semaphores
/* We must define union semun ourselves. */
union semun {
int val;
struct semid_ds *buf;
unsigned short int *array;
struct seminfo *__buf;
};
/* Initialize a binary semaphore with a value of 1. */
int binary_semaphore_initialize (int semid)
{
union semun argument;
unsigned short values[1];
values[0] = 1;
argument.array = values;
return semctl (semid, 0, SETALL, argument);
}
5.2.3 Wait and Post Operations
Each semaphore has a non-negative value and supports wait and post operations.The
semop system call implements both operations. Its first parameter specifies a semaphore
set identifier. Its second parameter is an array of struct sembuf elements, which specify
the operations you want to perform.The third parameter is the length of this array.
The fields of struct sembuf are listed here:
n
sem_num is the semaphore number in the semaphore set on which the operation
is performed.
n
sem_op is an integer that specifies the semaphore operation.
If sem_op is a positive number, that number is added to the semaphore value
immediately.
If
sem_op is a negative number, the absolute value of that number is subtracted
from the semaphore value. If this would make the semaphore value negative, the
call blocks until the semaphore value becomes as large as the absolute value of
sem_op (because some other process increments it).
If sem_op is zero, the operation blocks until the semaphore value becomes zero.
n
sem_flg is a flag value. Specify IPC_NOWAIT to prevent the operation from
blocking; if the operation would have blocked, the call to semop fails instead.
If you specify SEM_UNDO, Linux automatically undoes the operation on the
semaphore when the process exits.
06 0430 CH05 5/22/01 10:22 AM Page 103
104
Chapter 5 Interprocess Communication
Listing 5.4 illustrates wait and post operations for a binary semaphore.
Listing 5.4 (sem_pv.c) Wait and Post Operations for a Binary Semaphore
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
/* Wait on a binary semaphore. Block until the semaphore value is positive, then
decrement it by 1. */
int binary_semaphore_wait (int semid)
{
struct sembuf operations[1];
/* Use the first (and only) semaphore. */
operations[0].sem_num = 0;
/* Decrement by 1. */
operations[0].sem_op = -1;
/* Permit undo’ing. */
operations[0].sem_flg = SEM_UNDO;
return semop (semid, operations, 1);
}
/* Post to a binary semaphore: increment its value by 1.
This returns immediately. */
int binary_semaphore_post (int semid)
{
struct sembuf operations[1];
/* Use the first (and only) semaphore. */
operations[0].sem_num = 0;
/* Increment by 1. */
operations[0].sem_op = 1;
/* Permit undo’ing. */
operations[0].sem_flg = SEM_UNDO;
return semop (semid, operations, 1);
}
Specifying the SEM_UNDO flag permits dealing with the problem of terminating a
process while it has resources allocated through a semaphore.When a process termi-
nates, either voluntarily or involuntarily, the semaphore’s values are automatically
adjusted to “undo” the process’s effects on the semaphore. For example, if a process
that has decremented a semaphore is killed, the semaphore’s value is incremented.
06 0430 CH05 5/22/01 10:22 AM Page 104
105
5.3 Mapped Memory
5.2.4 Debugging Semaphores
Use the command ipcs -s to display information about existing semaphore sets. Use
the ipcrm sem command to remove a semaphore set from the command line. For
example, to remove the semaphore set with identifier 5790517, use this line:
% ipcrm sem 5790517
5.3 Mapped Memory
Mapped memory permits different processes to communicate via a shared file.
Although you can think of mapped memory as using a shared memory segment
with a name, you should be aware that there are technical differences. Mapped
memory can be used for interprocess communication or as an easy way to access
the contents of a file.
Mapped memory forms an association between a file and a process’s memory.
Linux splits the file into page-sized chunks and then copies them into virtual memory
pages so that they can be made available in a process’s address space.Thus, the process
can read the file’s contents with ordinary memory access. It can also modify the file’s
contents by writing to memory.This permits fast access to files.
You can think of mapped memory as allocating a buffer to hold a file’s entire con-
tents, and then reading the file into the buffer and (if the buffer is modified) writing
the buffer back out to the file afterward. Linux handles the file reading and writing
operations for you.
There are uses for memory-mapped files other than interprocess communication.
Some of these are discussed in Section 5.3.5,“Other Uses for
mmap.”
5.3.1 Mapping an Ordinary File
To map an ordinary file to a process’s memory, use the mmap (“Memory MAPped,”
pronounced “em-map”) call.The first argument is the address at which you would like
Linux to map the file into your process’s address space; the value NULL allows Linux
to choose an available start address.The second argument is the length of the map in
bytes.The third argument specifies the protection on the mapped address range.The
protection consists of a bitwise “or” of
PROT_READ, PROT_WRITE, and PROT_EXEC, corre-
sponding to read, write, and execution permission, respectively.The fourth argument is
a flag value that specifies additional options.The fifth argument is a file descriptor
opened to the file to be mapped.The last argument is the offset from the beginning of
the file from which to start the map.You can map all or part of the file into memory
by choosing the starting offset and length appropriately.
The flag value is a bitwise “or” of these constraints:
n
MAP_FIXED—If you specify this flag, Linux uses the address you request to map
the file rather than treating it as a hint.This address must be page-aligned.
n
MAP_PRIVATE—Writes to the memory range should not be written back to the
attached file, but to a private copy of the file. No other process sees these writes.
This mode may not be used with MAP_SHARED.
06 0430 CH05 5/22/01 10:22 AM Page 105
106
Chapter 5 Interprocess Communication
n
MAP_SHARED—Writes are immediately reflected in the underlying file rather than
buffering writes. Use this mode when using mapped memory for IPC.This
mode may not be used with MAP_PRIVATE.
If the call succeeds, it returns a pointer to the beginning of the memory. On failure, it
returns MAP_FAILED.
When you’re finished with a memory mapping, release it by using munmap. Pass it
the start address and length of the mapped memory region. Linux automatically
unmaps mapped regions when a process terminates.
5.3.2 Example Programs
Let’s look at two programs to illustrate using memory-mapped regions to read and
write to files.The first program, Listing 5.5, generates a random number and writes it
to a memory-mapped file.The second program, Listing 5.6, reads the number, prints
it, and replaces it in the memory-mapped file with double the value. Both take a
command-line argument of the file to map.
Listing 5.5 (mmap-write.c) Write a Random Number to a Memory-Mapped File
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>
#define FILE_LENGTH 0x100
/* Return a uniformly random number in the range [low,high]. */
int random_range (unsigned const low, unsigned const high)
{
unsigned const range = high - low + 1;
return low + (int) (((double) range) * rand () / (RAND_MAX + 1.0));
}
int main (int argc, char* const argv[])
{
int fd;
void* file_memory;
/* Seed the random number generator. */
srand (time (NULL));
/* Prepare a file large enough to hold an unsigned integer. */
fd = open (argv[1], O_RDWR
| O_CREAT, S_IRUSR | S_IWUSR);
lseek (fd, FILE_LENGTH+1, SEEK_SET);
06 0430 CH05 5/22/01 10:22 AM Page 106
107
5.3 Mapped Memory
write (fd, “”, 1);
lseek (fd, 0, SEEK_SET);
/* Create the memory mapping. */
file_memory = mmap (0, FILE_LENGTH, PROT_WRITE, MAP_SHARED, fd, 0);
close (fd);
/* Write a random integer to memory-mapped area. */
sprintf((char*) file_memory, “%d\n”, random_range (-100, 100));
/* Release the memory (unnecessary because the program exits). */
munmap (file_memory, FILE_LENGTH);
return 0;
}
The mmap-write program opens the file, creating it if it did not previously exist.The
third argument to open specifies that the file is opened for reading and writing.
Because we do not know the file’s length, we use lseek to ensure that the file is large
enough to store an integer and then move back the file position to its beginning.
The program maps the file and then closes the file descriptor because it’s no longer
needed.The program then writes a random integer to the mapped memory, and thus
the file, and unmaps the memory.The munmap call is unnecessary because Linux would
automatically unmap the file when the program terminates.
Listing 5.6 (mmap-read.c) Read an Integer from a Memory-Mapped File, and
Double It
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#define FILE_LENGTH 0x100
int main (int argc, char* const argv[])
{
int fd;
void* file_memory;
int integer;
/* Open the file. */
fd = open (argv[1], O_RDWR, S_IRUSR
| S_IWUSR);
/* Create the memory mapping. */
file_memory = mmap (0, FILE_LENGTH, PROT_READ
| PROT_WRITE,
MAP_SHARED, fd, 0);
close (fd);
continues
06 0430 CH05 5/22/01 10:22 AM Page 107
108
Chapter 5 Interprocess Communication
/* Read the integer, print it out, and double it. */
scanf (file_memory, “%d”, &integer);
printf (“value: %d\n”, integer);
sprintf ((char*) file_memory, “%d\n”, 2 * integer);
/* Release the memory (unnecessary because the program exits). */
munmap (file_memory, FILE_LENGTH);
return 0;
}
The mmap-read program reads the number out of the file and then writes the doubled
value to the file. First, it opens the file and maps it for reading and writing. Because
we can assume that the file is large enough to store an unsigned integer, we need not
use lseek, as in the previous program.The program reads and parses the value out
of memory using sscanf and then formats and writes the double value using sprintf.
Here’s an example of running these example programs. It maps the file
/tmp/integer-file.
% ./mmap-write /tmp/integer-file
% cat /tmp/integer-file
42
% ./mmap-read /tmp/integer-file
value: 42
% cat /tmp/integer-file
84
Observe that the text 42 was written to the disk file without ever calling write, and
was read back in again without calling read. Note that these sample programs write
and read the integer as a string (using sprintf and sscanf) for demonstration purposes
only—there’s no need for the contents of a memory-mapped file to be text.You can
store and retrieve arbitrary binary in a memory-mapped file.
5.3.3 Shared Access to a File
Different processes can communicate using memory-mapped regions associated with
the same file. Specify the MAP_SHARED flag so that any writes to these regions are
immediately transferred to the underlying file and made visible to other processes.
If you don’t specify this flag, Linux may buffer writes before transferring them to
the file.
Alternatively, you can force Linux to incorporate buffered writes into the disk file
by calling msync. Its first two parameters specify a memory-mapped region, as for
munmap.The third parameter can take these flag values:
n
MS_ASYNC—The update is scheduled but not necessarily run before the call
returns.
n
MS_SYNC—The update is immediate; the call to msync blocks until it’s done.
MS_SYNC and MS_ASYNC may not both be used.
Listing 5.6 Continued
06 0430 CH05 5/22/01 10:22 AM Page 108
Không có nhận xét nào:
Đăng nhận xét