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 );