UNIX Shared Memory

advertisement
System V Shared Memory
(References: Stevens Chap. 14, Gray Chapter 8)
Network Programming Lecture Notes by
Turhan TUNALI
• Shared memory allows multiple processes to
share virtual memory space.
• In general, one process creates/allocates the
shared memory segment. The size and access
permissions for the segment are set when it is
created.
• The process then attaches the shared segment,
causing it to be mapped into its current data
space. If needed, the creating process then
initializes the shared memory.
• Once created, and if permissions permit, other
processes can gain access to the shared memory
segment and map it into their data space.
• Each process accesses the shared memory
relative to its attachment address. While the data
that these processes are referencing is common,
each process will use different attachment
address values.
• When a process is finished with the shared
memory segment, it can detach from it.
• The creator of the segment may grant ownership
of the segment to another process.
• When all processes are finished with the shared
memory segment, the process that created the
segment is usually responsible for removing it.
The shmget system call is used to create the shared
memory segment and generate the associated data
structure, or to gain access to an existing segment.
The shared memory segment and the system data
structure are identified by a unique shared memory
identifier that the shmget call returns.
int shmget(key_t key,int size,int
shmflg);
New shared memory segment is created if
• key = IPC_PRIVATE
• key is not associated with an already existing
shared memory identifier and IPC_CREAT flag
is set (if key is associated, the call will return the
id of that shared memory segment)
• key is not associated with an already existing
shared memory identifier, IPC_CREAT and
•
•
•
•
IPC_EXCL flags are set (if key is associated the
call will return error)
key is as above
size is the size of the segment to be created
shmflg is flags as above
At creation time, the system data structure,
shmid_ds is generated and initialized
The following program demonstrates creation of
shared memory
Program 8.1
#include
#include
#include
#include
#include
<stdio.h>
<unistd.h>
<stdlib.h>
<sys/types.h>
<sys/ipc.h>
#include <sys/shm.h>
main(void) {
key_t
int
key = 15;
shmid_1, shmid_2;
if ((shmid_1=shmget(key, 1000, 0644|IPC_CREAT)) == -1){
perror("shmget shmid_1");
exit(1);
}
printf("First shared memory identifier is %d \n", shmid_1);
}
if ((shmid_2=shmget(IPC_PRIVATE, 20, 0644)) == -1){
perror("shmget shmid_2");
exit(2);
}
printf("Second shared memory identifier is %d \n", shmid_2);
exit(0);
The shmctl system call permits the user to perform a
number of generalized control operations on an
existing shared memory segment, and on the system
shared memory data structure.
int shmctl(int shmid,int cmd,struct
shmid_ds *buf);
• shmid is a valid shared memory segment
identifier
• cmd specifies the operation
• buf reference to a structure of type shmid_ds
• operations are
o IPC_STAT: return the current values of
shmid_ds
o IPC_SET: modify permissions
o IPC_RMID: remove shared memory
o SHM_LOCK: lock shared memory
o SHM_UNLOCK: unlock shared memory
The system call shmat is used to attach the
referenced shared memory segment into the calling
process’s data segment:
void *shmat(int shmid,void *shmaddr,int
shmflg);
• shmid is a valid shared memory identifier
• shmaddr is the location of the shared memory
segment (0 will let system do it)
• shmflg specifies access permissions and
alignments (default is read/write permitted)
• if successful, it will return the address of the
actual attachment
The system call shmdt detaches the calling process’s
data segment from the shared memory segment:
int shmdt(void *shmaddr);
• shmaddr is the reference to a shared memory
segment
• returns a value 0 if successful
Example 1:
• a private shared memory segment, 30 bytes in
length is created
• the attachment address and addresses of etext,
edata and end are displayed for reference
• uppercase alphabetic characters are written to
shared memory
• a child is forked to display contents of shared
memory again
• child modifies the contents to lowercase letters
• parent displays contents again
• note that the child does not have to make shmget
and shmat
/*
Program 8.2
*/
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#define SHM_SIZE 30
extern etext, edata, end;
main(void) {
pid_t
int
char
pid;
shmid;
c, *shm, *s;
if ((shmid = shmget(IPC_PRIVATE, SHM_SIZE, IPC_CREAT | 0666)) < 0)
perror("shmget fail");
exit(1);
}
if ((shm = (char *) shmat(shmid, 0, 0)) == (char *) -1) {
perror("shmat : parent ");
exit(2);
}
printf("Addresses in parent\n\n");
printf("shared mem: %X etext: %X edata: %X end: %X\n\n",
shm, &etext, &edata, &end);
s = shm;
/* s now references shared mem
for (c = 'A'; c <= 'Z'; ++c)
/* put some info there
*s++ = c;
*s = NULL;
/* terminate the sequence
printf("In parent before fork, memory is : %s \n", shm);
pid = fork();
switch (pid) {
case -1:
perror("fork ");
exit(3);
default :
sleep(5);
printf("\nIn parent after fork, memory is : %s\n", shm);
printf("Parent removing shared memory\n");
shmdt(shm);
shmctl(shmid, IPC_RMID, 0);
exit(0);
case 0 :
printf("In child after fork, memory is
: %s \n", shm);
{
*/
*/
*/
}
}
for (; *shm; ++shm)
*shm += 32;
shmdt(shm);
exit(0);
/* modify shared memory
Example 2: Producer and Consumer
*/
• An array of six message buffers will be used in
shared memory
• Producer and consumer may operate at different
rates
• New messages will be added to the tail of the list
and messages will be removed from the head of
the list
• Two semaphores will coordinate the access to
shared memory:
o The first semaphore will contain the number
of available slots
o The second semaphore will contain the
number of full slots
• The shared memory will have 6 messages of 50
characters each and the values of head and tail
/*
local.h - common header file for parent.c, producer.c and consumer.c
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
<stdio.h>
<unistd.h>
<stdlib.h>
<string.h>
<sys/types.h>
<sys/ipc.h>
<sys/sem.h>
<sys/shm.h>
<wait.h>
<signal.h>
#define ROWS 5
#define COLS 3
#define SLOT_LEN 50
#define N_SLOTS 6
/*
This declaration is *MISSING* in many Solaris environments.
It should be in the <sys/sem.h> file but often is not! If you
receive a duplicate definition error message for semun then
comment out the union declaration.
*/
union semun {
int
val;
struct semid_ds *buf;
ushort
*array;
};
struct MEMORY {
char buffer[N_SLOTS][SLOT_LEN];
int head, tail;
};
struct sembuf acquire = { 0, -1, SEM_UNDO},
release = { 0, 1, SEM_UNDO};
enum {AVAIL_SLOTS, TO_CONSUME};
• The parent creates and initializes shared memory
• It creates and initializes the two counting
semaphores
• It the forks two processes: the producer and the
consumer
#include "local.h"
/*
The PARENT
*/
main(int argc, char *argv[ ]) {
static
struct MEMORY
static ushort
int
char
pid_t
union semun
memory;
start_val[2] = {N_SLOTS, 0};
semid, shmid, croaker;
*shmptr;
p_id, c_id, pid = getpid( );
arg;
memory.head = memory.tail = 0;
if ( argc != 3 ) {
fprintf(stderr, "%s producer_time consumer_time\n", argv[0]);
exit(-1);
}
/*
Create, attach, and initialize the memory segment
*/
if ((shmid=shmget((int)pid, sizeof(memory), IPC_CREAT | 0600 )) != 1){
if ((shmptr=(char *)shmat(shmid, 0, 0)) == (char *) -1){
perror("shmptr -- parent -- attach ");
exit(1);
}
memcpy(shmptr, (char *)&memory, sizeof(memory)); /* initialize */
} else {
perror("shmid -- parent -- creation ");
exit(2);
}
/*
Create and initialize the semaphores
*/
if ((semid=semget((int)pid, 2, IPC_CREAT | 0666)) != -1) {
arg.array = start_val;
if (semctl(semid, 0, SETALL, arg) == -1) {
perror("semctl -- parent -- initialization");
exit(3);
}
} else {
perror("semget -- parent -- creation ");
exit(4);
}
/*
Fork the producer process
*/
if ( (p_id=fork( )) == -1) {
perror("fork -- producer ");
exit(5);
} else if ( p_id == 0 ) {
execl( "producer", "producer", argv[1], (char *) 0);
perror("execl -- producer ");
exit(6);
}
}
/*
Fork the consumer process
*/
if ( (c_id =fork( )) == -1) {
perror("fork -- consumer ");
exit(7);
} else if ( c_id == 0 ) {
execl( "consumer", "consumer", argv[2], (char *) 0);
perror("execl -- consumer ");
exit(8);
}
croaker = (int) wait( (int *) 0 );
/* wait for one to die */
kill( (croaker == p_id ) ? c_id : p_id, SIGKILL); /* remove other */
shmdt( shmptr );
/* detach
*/
shmctl( shmid, IPC_RMID, (struct shmid_ds *) 0);
/* remove
*/
semctl( semid, 0, IPC_RMID, 0);
exit(0);
• The parent expects two integers to be passed via
the command line to indicate the time to sleep in
each execution cycle
• The first value is passed to the producer and the
second is passed to the consumer
• The memcpy function copies n bytes from the
location referenced by its second argument to
location referenced by its first argument
• Parent waits for one of the children terminate. It
then kills the other child
#include "local.h"
/*
The PRODUCER ...
*/
main(int argc, char *argv[]) {
static char
*source[ROWS][COLS] = {
{"A", "The", "One"},
{" red", " polka-dot", " yellow"},
{" spider", " dump truck", " tree"},
{" broke", " ran", " fell"},
{" down", " away", " out"}
};
static char
local_buffer[50];
int
i, r, c, sleep_limit, semid, shmid;
pid_t
ppid = getppid( );
char
*shmptr;
struct MEMORY
*memptr;
if ( argc != 2 ) {
fprintf(stderr, "%s sleep_time", argv[0]);
exit(-1);
}
/*
Access, attach and reference the shared memory
*/
if ((shmid=shmget((int) ppid, 0, 0)) != -1 ){
if ( (shmptr=(char *)shmat(shmid, (char *)0, 0)) == (char *) -1){
perror("shmat -- producer -- attach ");
exit(1);
}
memptr = (struct MEMORY *) shmptr;
} else {
perror("shmget -- producer -- access ");
exit(2);
}
/*
Access the semaphore set
*/
if ( (semid=semget((int) ppid, 2, 0)) == -1 ) {
perror("semget -- producer -- access ");
exit(3);
}
sleep_limit = atoi(argv[1]) % 20;
i = 20 - sleep_limit;
srand((unsigned)getpid());
while( i-- ) {
memset(local_buffer, '\0', sizeof(local_buffer));
for (r = 0; r < ROWS; ++r) {
/* Make a random string */
c = rand() % COLS;
strcat(local_buffer, source[r][c]);
}
acquire.sem_num = AVAIL_SLOTS;
if (semop(semid, &acquire, 1 ) == -1 ){
perror("semop -- producer -- acquire ");
exit(4);
}
strcpy(memptr->buffer[memptr->tail], local_buffer);
printf("P: [%d] %s.\n", memptr->tail, memptr->buffer[memptr>tail]);
memptr->tail = (memptr->tail +1) % N_SLOTS;
release.sem_num = TO_CONSUME;
if (semop( semid, &release, 1 ) == -1 ) {
perror("semop -- producer -- release ");
exit(5);
}
sleep( rand( ) % sleep_limit + 1 );
}
}
exit(0);
• The producer obtains the pid of the parent to use
it as a key for shmget to access to the shared
memory
• Another way would be to pass the shmid on the
command line
• It repeats the same method for semaphores
#include "local.h"
/*
The CONSUMER
*/
main(int argc, char *argv[]) {
static char
int
pid_t
char
struct MEMORY
1){
local_buffer[50];
i, sleep_limit, semid, shmid;
ppid = getppid( );
*shmptr;
*memptr;
if ( argc != 2 ) {
fprintf(stderr, "%s sleep_time", argv[0]);
exit(-1);
}
/*
Access, attach and reference the shared memory
*/
if ((shmid=shmget((int) ppid, 0, 0)) != -1 ){
if ( (shmptr=(char *)shmat(shmid, (char *)0, 0)) == (char *) perror("shmat -- consumer -- attach ");
exit(1);
}
memptr = (struct MEMORY *) shmptr;
} else {
perror("shmget -- consumer -- access ");
exit(2);
}
/*
Access the semaphore set
*/
if ( (semid=semget((int) ppid, 2, 0)) == -1 ) {
perror("semget -- consumer -- access ");
exit(3);
}
sleep_limit = atoi(argv[1]) % 20;
}
i = 20 - sleep_limit;
srand((unsigned)getpid());
while( i ) {
acquire.sem_num = TO_CONSUME;
if (semop(semid, &acquire, 1 ) == -1 ){
perror("semop -- consumer -- acquire ");
exit(4);
}
memset(local_buffer, '\0', sizeof(local_buffer));
strcpy(local_buffer, memptr->buffer[memptr->head]);
printf("C: [%d] %s.\n", memptr->head,local_buffer);
memptr->head = (memptr->head +1) % N_SLOTS;
release.sem_num = AVAIL_SLOTS;
if (semop( semid, &release, 1 ) == -1 ) {
perror("semop -- consumer -- release ");
exit(5);
}
sleep( rand( ) % sleep_limit + 1 );
}
exit(0);
Most versions of UNIX also support the mmap
system call which can be used to map a file to a
process’s virtual memory address space. This call is
particularly useful because, unlike memory, the
contents of files are non-volatile and will remain
available even after a system has been shut down
(rebooted).
caddr_t mmap ( caddr_t addr, size_t
len, int prot, int flags, int
fildes, off_t off);
addr
address for attachment (use 0 to let
system manage it)
len
number of bytes to be attached
prot
type of access for the segment
flags
specify type of mapping
fildes
valid open file descriptor
off
starting (offset) position of the mapping
The msync library function can be used in
conjunction with mmap to synchronize the contents
of mapped memory with physical storage. A call to
msync will cause the system to write all modified
memory locations to their associated physical storage
locations.
int msync (caddr_t addr, size_t
len, int flags );
addr
len
flags
address of the mapped memory
size (in bytes) of the memory
direct system to take actions
The following program uses a parent/two-child
arrangement to demonstrate the use of mmap.
Program 8.6
/*
*
Using the mmap system call
*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
main(int argc, char *argv[])
{
int
fd, changes, i, random_spot, kids[2];
struct stat
buf;
char
*the_file,
*starting_string = "Using mmap( ) can be fun\nand
informative!";
if (argc != 3) {
fprintf(stderr, "Usage %s file_name #_of_changes\n", *argv);
exit(1);
}
if ((changes = atoi(argv[2])) < 1) {
fprintf(stderr, "# of changes < 1 \n");
exit(2);
}
if ((fd = open(argv[1], O_CREAT | O_RDWR, 0666)) < 0) {
fprintf(stderr, "open error on file %s\n", *argv);
exit(3);
}
write(fd, starting_string, strlen(starting_string));
/*
*
Obtain size of file to be mapped
*/
if (fstat(fd, &buf) < 0) {
fprintf(stderr, "fstat error on file %s\n", *argv);
exit(4);
}
/*
*
Establish the mapping
*/
if ((the_file = mmap(0, (size_t) buf.st_size, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0)) == (caddr_t) - 1) {
fprintf(stderr, "mmap failure\n");
exit(5);
}
}
for (i = 0; i < 2; ++i)
if ((kids[i] = (int) fork()) == 0)
while (1) {
printf("Child %d finds:\n%s\n", getpid(), the_file);
sleep(1);
}
srand((unsigned) getpid());
for (i = 0; i < changes; ++i) {
random_spot = (int) (rand() % buf.st_size);
*(the_file + random_spot) = '*';
sleep(1);
}
printf("Parent done with changes\n");
for (i = 0; i < 2; ++i)
kill(kids[i], 9);
printf("The file now contains:\n%s\n", the_file);
exit( 0 );
Download