Friday, November 27, 2009

15.3 POSIX:XSI Shared Memory



[ Team LiB ]






15.3 POSIX:XSI Shared Memory


Shared memory allows processes to read and write from the same memory segment. The sys/shm.h header file defines the data structures for shared memory, including shmid_ds, which has the following members.



struct ipc_perm shm_perm; /* operation permission structure */
size_t shm_segsz; /* size of segment in bytes */
pid_t shm_lpid; /* process ID of last operation */
pid_t shm_cpid; /* process ID of creator */
shmatt_t shm_nattch; /* number of current attaches */
time_t shm_atime; /* time of last shmat */
time_t shm_dtime; /* time of last shmdt */
time_t shm_ctime; /* time of last shctl */

The shmatt_t data type is an unsigned integer data type used to hold the number of times the memory segment is attached. This type must be at least as large as an unsigned short.



15.3.1 Accessing a shared memory segment


The shmget function returns an identifier for the shared memory segment associated with the key parameter. It creates the segment if either the key is IPC_PRIVATE or shmflg & IPC_CREAT is nonzero and no shared memory segment or identifier is already associated with key. Shared memory segments are initialized to zero.



SYNOPSIS

#include <sys/shm.h>

int shmget(key_t key, size_t size, int shmflg);
POSIX:XSI

If successful, shmget returns a nonnegative integer corresponding to the shared memory segment identifier. If unsuccessful, shmget returns �1 and sets errno. The following table lists the mandatory errors for shmget.


errno

cause

EACCES

shared memory identifier exists for key but permissions are not granted

EEXIST

shared memory identifier exists for key but ((shmflg & IPC_CREAT) && (shmflg & IPC_EXCL)) != 0

EINVAL

shared memory segment is to be created but size is invalid

EINVAL

no shared memory segment is to be created but size is inconsistent with system-imposed limits or with the segment size of key

ENOENT

shared memory identifier does not exist for key but (shmflg & IPC_CREAT) == 0

ENOMEM

not enough memory to create the specified shared memory segment

ENOSPC

systemwide limit on shared memory identifiers would be exceeded




15.3.2 Attaching and detaching a shared memory segment


The shmat function attaches the shared memory segment specified by shmid to the address space of the calling process and increments the value of shm_nattch for shmid. The shmat function returns a void * pointer, so a program can use the return value like an ordinary memory pointer obtained from malloc. Use a shmaddr value of NULL. On some systems it may be necessary to set shmflg so that the memory segment is properly aligned.



SYNOPSIS

#include <sys/shm.h>

void *shmat(int shmid, const void *shmaddr, int shmflg);
POSIX:XSI

If successful, shmat returns the starting address of the segment. If unsuccessful, shmat returns �1 and sets errno. The following table lists the mandatory errors for shmat.


errno

cause

EACCES

operation permission denied to caller

EINVAL

value of shmid or shmaddr is invalid

EMFILE

number of shared memory segments attached to process would exceed limit

ENOMEM

process data space is not large enough to accommodate the shared memory segment


When finished with a shared memory segment, a program calls shmdt to detach the shared memory segment and to decrement shm_nattch. The shmaddr parameter is the starting address of the shared memory segment.



SYNOPSIS

#include <sys/shm.h>

int shmdt(const void *shmaddr);
POSIX:XSI

If successful, shmdt returns 0. If unsuccessful, shmdt returns �1 and sets errno. The shmdt function sets errno to EINVAL when shmaddr does not correspond to the starting address of a shared memory segment.


The last process to detach the segment should deallocate the shared memory segment by calling shmctl.




15.3.3 Controlling shared memory


The shmctl function provides a variety of control operations on the shared memory segment shmid as specified by the cmd parameter. The interpretation of the buf parameter depends on the value of cmd, as described below.



SYNOPSIS

#include <sys/shm.h>

int shmctl(int shmid, int cmd, struct shmid_ds *buf);
POSIX:XSI

If successful, shmctl returns 0. If unsuccessful, shmctl returns �1 and sets errno. The following table lists the mandatory errors for shmctl.


errno

cause

EACCES

cmd is IPC_STAT and caller does not have read permission

EINVAL

value of shmid or cmd is invalid

EPERM

cmd is IPC_RMID or IPC_SET and caller does not have correct permissions


Table 15.3 gives the POSIX:XSI values of cmd for shmctl.


Table 15.3. POSIX:XSI values of cmd for shmctl.

cmd

description

IPC_RMID

remove shared memory segment shmid and destroy corresponding shmid_ds

IPC_SET

set values of fields for shared memory segment shmid from values found in buf

IPC_STAT

copy current values for shared memory segment shmid into buf



Example 15.11 detachandremove.c

The detachandremove function detaches the shared memory segment shmaddr and then removes the shared memory segment specified by semid.



#include <stdio.h>
#include <errno.h>
#include <sys/shm.h>

int detachandremove(int shmid, void *shmaddr) {
int error = 0;

if (shmdt(shmaddr) == -1)
error = errno;
if ((shmctl(shmid, IPC_RMID, NULL) == -1) && !error)
error = errno;
if (!error)
return 0;
errno = error;
return -1;
}




15.3.4 Shared memory examples


Program 4.11 on page 108 monitors two file descriptors by using a parent and a child. Each process echoes the contents of the files to standard output and then writes to standard error the total number of bytes received. There is no simple way for this program to report the total number of bytes received by the two processes without using a communication mechanism such as a pipe.


Program 15.5 modifies Program 4.11 so that the parent and child share a small memory segment. The child stores its byte count in the shared memory. The parent waits for the child to finish and then outputs the number of bytes received by each process along with the sum of these values. The parent creates the shared memory segment by using the key IPC_PRIVATE, which allows the memory to be shared among its children. The synchronization of the shared memory is provided by the wait function. The parent does not access the shared memory until it has detected the termination of the child. Program 15.5 calls detachandremove of Example 15.11 when it must both detach and remove the shared memory segment.



Program 15.5 monitorshared.c

A program to monitor two file descriptors and keep information in shared memory. The parent waits for the child, to ensure mutual exclusion.



#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include "restart.h"
#define PERM (S_IRUSR | S_IWUSR)

int detachandremove(int shmid, void *shmaddr);

int main(int argc, char *argv[]) {
int bytesread;
int childpid;
int fd, fd1, fd2;
int id;
int *sharedtotal;
int totalbytes = 0;

if (argc != 3) {
fprintf(stderr, "Usage: %s file1 file2\n", argv[0]);
return 1;
}
if (((fd1 = open(argv[1], O_RDONLY)) == -1) ||
((fd2 = open(argv[2], O_RDONLY)) == -1)) {
perror("Failed to open file");
return 1;
}
if ((id = shmget(IPC_PRIVATE, sizeof(int), PERM)) == -1) {
perror("Failed to create shared memory segment");
return 1;
}
if ((sharedtotal = (int *)shmat(id, NULL, 0)) == (void *)-1) {
perror("Failed to attach shared memory segment");
if (shmctl(id, IPC_RMID, NULL) == -1)
perror("Failed to remove memory segment");
return 1;
}
if ((childpid = fork()) == -1) {
perror("Failed to create child process");
if (detachandremove(id, sharedtotal) == -1)
perror("Failed to destroy shared memory segment");
return 1;
}
if (childpid > 0) /* parent code */
fd = fd1;
else
fd = fd2;
while ((bytesread = readwrite(fd, STDOUT_FILENO)) > 0)
totalbytes += bytesread;
if (childpid == 0) { /* child code */
*sharedtotal = totalbytes;
return 0;
}
if (r_wait(NULL) == -1)
perror("Failed to wait for child");
else {
fprintf(stderr, "Bytes copied: %8d by parent\n", totalbytes);
fprintf(stderr, " %8d by child\n", *sharedtotal);
fprintf(stderr, " %8d total\n", totalbytes + *sharedtotal);
}
if (detachandremove(id, sharedtotal) == -1) {
perror("Failed to destroy shared memory segment");
return 1;
}
return 0;
}


Using shared memory between processes that do not have a common ancestor requires the processes to agree on a key, either directly or with ftok and a pathname.


Program 13.5 on page 456 used mutex locks to keep a sum and count for threads of a given process. This was particularly simple because the threads automatically share the mutex and the mutex could be initialized statically. Implementing synchronized shared memory for independent processes is more difficult because you must set up the sharing of the synchronization mechanism as well as the memory for the sum and the count.


Program 15.6 uses a semaphore and a small shared memory segment to keep a sum and count. Each process must first call the initshared function with an agreed-on key. This function first tries to create a shared memory segment with the given key. If successful, initshared initializes the sum and count. Otherwise, initshared just accesses the shared memory segment. In either case, initshared calls initsemset with the ready flag in shared memory to access a semaphore set containing a single semaphore initialized to 1. This semaphore element protects the shared memory segment. The add and getcountandsum functions behave as in Program 13.5, this time using the semaphore, rather than a mutex, for protection.



Program 15.6 sharedmemsum.c

A function that keeps a synchronized sum and count in shared memory.



#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <sys/stat.h>
#define PERM (S_IRUSR | S_IWUSR)

int initsemset(key_t mykey, int value, sig_atomic_t *readyp);
void setsembuf(struct sembuf *s, int num, int op, int flg);

typedef struct {
int count;
double sum;
sig_atomic_t ready;
} shared_sum_t;

static int semid;
static struct sembuf semlock;
static struct sembuf semunlock;
static shared_sum_t *sharedsum;

int initshared(int key) { /* initialize shared memory segment */
int shid;

setsembuf(&semlock, 0, -1, 0); /* setting for locking semaphore */
setsembuf(&semunlock, 0, 1, 0); /* setting for unlocking semaphore */
/* get attached memory, creating it if necessary */
shid = shmget(key, sizeof(shared_sum_t), PERM | IPC_CREAT | IPC_EXCL);
if ((shid == -1) && (errno != EEXIST)) /* real error */
return -1;
if (shid == -1) { /* already created, access and attach it */
if (((shid = shmget(key, sizeof(shared_sum_t), PERM)) == -1) ||
((sharedsum = (shared_sum_t *)shmat(shid, NULL, 0)) == (void *)-1) )
return -1;
}
else { /* successfully created, must attach and initialize variables */
sharedsum = (shared_sum_t *)shmat(shid, NULL, 0);
if (sharedsum == (void *)-1)
return -1;
sharedsum -> count = 0;
sharedsum -> sum = 0.0;
}
semid = initsemset(key, 1, &sharedsum->ready);
if (semid == -1)
return -1;
return 0;
}

int add(double x) { /* add x to sum */
if (semop(semid, &semlock, 1) == -1)
return -1;
sharedsum -> sum += x;
sharedsum -> count++;
if (semop(semid, &semunlock, 1) == -1)
return -1;
return 0;
}

int getcountandsum(int *countp, double *sum) { /* return sum and count */
if (semop(semid, &semlock, 1) == -1)
return -1;
*countp = sharedsum -> count;
*sum = sharedsum -> sum;
if (semop(semid, &semunlock, 1) == -1)
return -1;
return 0;
}


Each process must call initshared at least once before calling add or getcountandsum. A process may call initshared more than once, but one thread of the process should not call initshared while another thread of the same process is calling add or getcountandsum.



Example 15.12

In Program 15.6, the three fields of the shared memory segment are treated differently. The sum and count are explicitly initialized to 0 whereas the function relies on the fact that ready is initialized to 0 when the shared memory segment is created. Why is it done this way?


Answer:


All three fields are initialized to 0 when the shared memory segment is created, so in this case the explicit initialization is not necessary. The program relies on the atomic nature of the creation and initialization of ready to 0, but sum and count can be initialized to any values.



Program 15.7 displays the shared count and sum when it receives a SIGUSR1 signal. The signal handler is allowed to use fprintf for output, even though it might not be async-signal safe, since no output is done by the main program after the signal handler is set up and the signal is unblocked.


Program 15.8 modifies Program 15.5 by copying information from a single file to standard output and saving the number of bytes copied in a shared sum implemented by Program 15.6. Program 15.8 has two command-line arguments: the name of the file; and the key identifying the shared memory and its protecting semaphore. You can run multiple copies of Program 15.8 simultaneously with different filenames and the same key. The common shared memory stores the total number of bytes copied.



Program 15.7 showshared.c

A program to display the shared count and sum when it receives a SIGUSR1 signal.



#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int getcountandsum(int *countp, double *sump);
int initshared(int key);

/* ARGSUSED */
static void showit(int signo) {
int count;
double sum;
if (getcountandsum(&count, &sum) == -1)
printf("Failed to get count and sum\n");
else
printf("Sum is %f and count is %d\n", sum, count);
}

int main(int argc, char *argv[]) {
struct sigaction act;
int key;
sigset_t mask, oldmask;

if (argc != 2) {
fprintf(stderr, "Usage: %s key\n", argv[0]);
return 1;
}
key = atoi(argv[1]);
if (initshared(key) == -1) {
perror("Failed to initialize shared memory");
return 1;
}
if ((sigfillset(&mask) == -1) ||
(sigprocmask(SIG_SETMASK, &mask, &oldmask) == -1)) {
perror("Failed to block signals to set up handlers");
return 1;
}
printf("This is process %ld waiting for SIGUSR1 (%d)\n",
(long)getpid(), SIGUSR1);

act.sa_handler = showit;
act.sa_flags = 0;
if ((sigemptyset(&act.sa_mask) == -1) ||
(sigaction(SIGUSR1, &act, NULL) == -1)) {
perror("Failed to set up signal handler");
return 1;
}
if (sigprocmask(SIG_SETMASK, &oldmask, NULL) == -1) {
perror("Failed to unblock signals");
return 1;
}
for ( ; ; )
pause();
}



Program 15.8 monitoroneshared.c

A program to monitor one file and send the output to standard output. It keeps track of the number of bytes received by calling add from Program 15.6.



#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "restart.h"

int add(double x);
int initshared(int key);

int main(int argc, char *argv[]) {
int bytesread;
int fd;
int key;

if (argc != 3) {
fprintf(stderr,"Usage: %s file key\n",argv[0]);
return 1;
}
if ((fd = open(argv[1],O_RDONLY)) == -1) {
perror("Failed to open file");
return 1;
}
key = atoi(argv[2]);
if (initshared(key) == -1) {
perror("Failed to initialize shared sum");
return 1;
}
while ((bytesread = readwrite(fd, STDOUT_FILENO)) > 0)
if (add((double)bytesread) == -1) {
perror("Failed to add to count");
return 1;
}
return 0;
}



Example 15.13

Start Program 15.7 in one window, using key 12345, with the following command.



showshared 12345

Create a few named pipes, say, pipe1 and pipe2. Start copies of monitoroneshared in different windows with the following commands.



monitoroneshared pipe1 12345
monitoroneshared pipe2 12345

In other windows, send characters to the pipes (e.g., cat > pipe1). Periodically send SIGUSR1 signals to showshared to monitor the progress.








    [ Team LiB ]



    No comments: