Add Book to My BookshelfPurchase This Book Online

Chapter 13 - Interprocess Communication

UNIX Systems Programming for SVR4
David A. Curry
 Copyright © 1996 O'Reilly & Associates, Inc.

System V IPC Functions
Three types of interprocess communication—message queues, shared memory, and semaphores—are usually referred to collectively as System V IPC. System V IPC originated in SVR2, but has since been made available by most vendors, and is also available in SVR4.
Each type of IPC structure (message queue, shared memory segment, or semaphore) is referred to by a non-negative integer identifier. To make use of a message queue, for example, all the processes using that message queue must know its identifier. When an IPC structure is being created, the program doing the creation provides a key of type key_t. The operating system will convert this key into an IPC identifier.
Keys can be specified in one of three ways:
 1.The server can create a new structure by specifying a key of IPC_PRIVATE. The creation procedure will return an identifier for the newly created structure. The problem with this is that in order for client programs to make use of the structure, they must know the identifier. Thus, the server has to place the identifier in a file somewhere for the clients to read it.
 2.The server and clients can agree on a key value, for example, by defining it in a common header file. The server creates a new IPC structure with this key, and the clients use the key to access the structure. The problem with this is that the key may already be in use by some other group of programs, in which case the IPC structure cannot be created.
 3.The server and clients can agree on a pathname to an existing file in the filesystem, and a project ID (a value between 0 and 255), and then call the ftok function to convert these two values into a key:
    #include <sys/types.h>
    #include <sys/ipc.h>
    key_t ftok(const char *path, int projectid);
This key is then used as in step 2, above.
To create a new IPC structure, the server (usually) calls the appropriate “get” function, either with the key argument equal to IPC_PRIVATE, or with the key argument equal to some key and the IPC_CREAT bit set in the flag argument. A client accesses an existing IPC structure (created by the server) by calling the appropriate “get” function with the key argument equal to the appropriate key and with the IPC_CREAT bit cleared in the flag argument. To be sure that a new IPC structure is created, rather than referencing an existing one with the same identifier, the IPC_EXCL bit can be set in the flag argument to the “get” function. This causes the “get” function to return an error if the IPC structure already exists.
Each IPC structure has a permissions structure associated with it, defined in the include file sys/ipc.h:
    struct ipc_perm {
        uid_t     uid;
        gid_t     gid;
        uid_t     cuid;
        gid_t     cgid;
        mode_t    mode;
        ulong     seq;
        key_t     key;
        long      pad[4];
    };
The cuid and cgid elements identify the user who created the object, the uid and gid elements identify the owner of the object. The mode element is a set of read/write permission bits identical to those for files, that specify owner, group, and world permissions to examine and change the object. The “control” function for each type of IPC can be used to examine and change this structure.
The System V IPC mechanisms have one major problem. All of the IPC structures are global to the system, and do not have a reference count. This means that if a program creates one of these structures, and then exits without destroying it, the operating system has no way of knowing whether any other programs are using it. Thus, the operating system has no choice but to leave the structure there; it cannot delete it. These structures remain in the system until someone comes along and removes them, or until the system is rebooted. This can be a serious problem, because the system places a limit on how many of these structures may exist at any point in time. Aside from consuming space that could be used by other programs, the structures left around by improperly-behaving programs can eventually consume all available IPC resources.
Message Queues
A message queue is a linked list of messages, each of a fixed maximum size. Messages are added to the end of the queue so that the order in which they were sent is preserved. However, each message may have a type, allowing multiple message streams to be processed in the same queue.
Before using a message queue, a process must obtain the queue identifier for it. This is done using the msgget function:
    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/msg.h>
    int msgget(key_t key, int msgflg);
The key parameter specifies the key to use for this message queue; it may either be the value IPC_PRIVATE, in which case a new message queue will always be created, or a non-zero value. If key contains a non-zero value, msgget will either create a new message queue or return the identifier of an existing message queue, depending on whether or not the IPC_CREAT bit is set in the msgflg argument. The msgflg parameter is also used to specify the read/write permissions on the message queue, in the same manner as with open and creat. Upon successful completion, a message queue identifier is returned. If the queue does not exist or cannot be created, -1 is returned and errno will describe the error that occurred.
The msgctl function allows several different control operations to be performed on a message queue:
    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/msg.h>
    int msgctl(int msqid, int cmd, struct msqid_ds *buf);
The msqid parameter contains the message queue identifier of interest. The buf parameter points to a structure of type struct msqid_ds, which describes the message queue:
    struct msqid_ds {
        struct ipc_perm    msg_perm;
        struct msg        *msg_first;
        struct msg        *msg_last;
        ulong              msg_cbytes;
        ulong              msg_qnum;
        ulong              msg_qbytes;
        pid_t              msg_lspid;
        pid_t              msg_lrpid;
        time_t             msg_stime;
        long               msg_pad1;
        time_t             msg_rtime;
        long               msg_pad2;
        time_t             msg_ctime;
        long               msg_pad3;
        kcondvar_t         msg_cv;
        kcondvar_t         msg_qnum_cv;
        long               msg_pad4[3];
    };
The msg_perm element of this structure describes the permission bits on the queue, as described in the introduction to this section. The msg_qnum, msg_cbytes, and msg_qbytes elements contain the number of messages on the queue, number of bytes on the queue, and maximum number of bytes on the queue, respectively. The msg_lspid and msg_lrpid elements contain the process ID of the last process to send and receive a message on the queue, respectively. Finally, the msg_stime, msg_rtime, and msg_ctime elements contain the time of the last send on the queue, time of the last receive on the queue, and time of the last permissions change on the queue, respectively.
The cmd parameter to msgctl may be one of the following values:
IPC_STAT
Place the current contents of the struct msqid_ds structure into the area pointed to by buf.
IPC_SET
Change the msg_perm.uid, msg_perm.gid, msg_perm.mode, and msg_qbytes elements of the struct msqid_ds structure to the values found in the area pointed to by buf. This operation is restricted to processes with an effective user ID of the superuser, or one that is equal to either msg_perm.cuid or msg_perm.uid. The msg_qbytes element may be changed only by the superuser.
IPC_RMID
Remove the message queue identifier specified by msqid from the system, and destroy the message queue and data structure. This command may be executed only by a process with an effective user ID of the superuser, or one that is equal to either msg_perm.cuid or msg_perm.uid.
If successful, msgctl returns 0. If an error occurs, msgctl returns -1 and stores the reason for failure in errno.
To send and receive messages on a message queue, use the msgsnd and msgrcv functions:
    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/msg.h>
    int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
    int msgrcv(int msqid, void *msgp, size_t msgsz, long msgtype, int msgflg);
The msgsnd function sends a message, pointed to by msgp and of size msgsz, on the message queue identified by msqid. A message has the following structure:
    struct msgbuf {
        long    mtype;
        char    mtext[];
    };
The mtype element of this structure is a positive integer that can be used by the receiving process for message selection. The mtext element of the structure is a buffer of msgsz bytes; msgsz may be any value from 0 to some system-imposed maximum (usually 2048). If successful, msgsnd returns 0; otherwise it returns -1 and places an error code in errno.
The msgrcv function retrieves a message from the message queue specified by msqid, and stores it in the area pointed to by msgp, which is large enough to hold a message of msgsz bytes. The message retrieved is controlled by the msgtype parameter:
 If msgtype is 0, the next message on the queue is returned.
 If msgtype is greater than 0, the next message on the queue with mtype equal to msgtype is returned.
 If msgtype is less than 0, the next message on the queue with mtype less than or equal to the absolute value of msgtype is returned.
If a message is successfully received, msgrcv returns the number of bytes stored in msgp. If an error occurs, -1 is returned and errno will indicate the error.
For both msgsnd and msgrcv, the msgflg argument may contain the constant IPC_NOWAIT. This causes msgsnd to return an error immediately if the message queue is full, instead of blocking until space is available. It causes msgrcv to return an error immediately if no message of the specified type is available, instead of blocking until one arrives.
Examples 13-8 and 13-9 show a small server and a client program that transfers data using message queues.
Example 13-8:  msq-srvr
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define MSQKEY      34856
#define MSQSIZE     32
struct mymsgbuf {
    long    mtype;
    char    mtext[MSQSIZE];
};
int
main(void)
{
    key_t key;
    int n, msqid;
    struct mymsgbuf mb;
    /*
     * Create a new message queue.  We use IPC_CREAT to create it,
     * and IPC_EXCL to make sure it does not exist already.  If
     * you get an error on this, something on your system is using
     * the same key - change MSQKEY to something else.
     */
    key = MSQKEY;
    if ((msqid = msgget(key, IPC_CREAT | IPC_EXCL | 0666)) < 0) {
        perror("msgget");
        exit(1);
    }
    /*
     * Receive messages.  Messages of type 1 are to be printed
     * on the standard output; a message of type 2 indicates that
     * we're done.
     */
    while ((n = msgrcv(msqid, &mb, MSQSIZE, 0, 0)) > 0) {
        switch (mb.mtype) {
        case 1:
            write(1, mb.mtext, n);
            break;
        case 2:
            goto out;
        }
    }
out:
    /*
     * Remove the message queue from the system.
     */
    if (msgctl(msqid, IPC_RMID, (struct msqid_ds *) 0) < 0) {
        perror("msgctl");
        exit(1);
    }
    exit(0);
}
Example 13-9:  msq-clnt
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define MSQKEY      34856
#define MSQSIZE     32
struct mymsgbuf {
    long    mtype;
    char    mtext[MSQSIZE];
};
int
main(void)
{
    key_t key;
    int n, msqid;
    struct mymsgbuf mb;
    /*
     * Get a message queue.  The server must have created it
     * already.
     */
    key = MSQKEY;
    if ((msqid = msgget(key, 0666)) < 0) {
        perror("msgget");
        exit(1);
    }
    /*
     * Read data from standard input and send it in
     * messages of type 1.
     */
    mb.mtype = 1;
    while ((n = read(0, mb.mtext, MSQSIZE)) > 0) {
        if (msgsnd(msqid, &mb, n, 0) < 0) {
            perror("msgsnd");
            exit(1);
        }
    }
    /*
     * Send a message of type 2 to indicate we're done.
     */
    mb.mtype = 2;
    memset(mb.mtext, 0, MSQSIZE);
    if (msgsnd(msqid, &mb, MSQSIZE, 0) < 0) {
        perror("msgsnd");
        exit(1);
    }
    exit(0);
}
    % msq-srvr &
    % msq-clnt < /etc/motd
    Sun Microsystems Inc.   SunOS 5.3       Generic September 1993
The server creates a new message queue that may be read and written by anyone. (We use IPC_EXCL here to insure that nothing else in the system is using this key value—if you get an error when you try to start the server, use a different key value.) The server then receives messages from the queue. Messages of type 1 are data, and are printed on the standard output. Since there is no concept of end-of-file on a message queue, we use a message of type 2 to tell the server there is no more data. The client simply obtains the message queue identifier, and then reads from its standard input, sending the data in messages of type 1. It sends a final message of type 2 to tell the server there is no more data.
Shared Memory
Shared memory allows two or more processes to share a region of memory, so that they may all examine and change its contents. Obviously, some type of synchronization between the processes is required, to ensure that one process does not change the memory while another is accessing it.
Before using a shared memory segment, a process must obtain the queue identifier for it. This is done using the shmget function:
    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/shm.h>
    int shmget(key_t key, int size, int shmflg);
The size parameter specifies the size of the desired segment, in bytes. The key parameter specifies the key to use for this memory segment; it may either be the value IPC_PRIVATE, in which case a new segment will always be created, or a non-zero value. If key contains a non-zero value, msgget will either create a new memory segment or return the identifier of an existing segment, depending on whether or not the IPC_CREAT bit is set in the shmflg argument. The shmflg parameter is also used to specify the read/write permissions on the memory segment, in the same manner as with open and creat. Upon successful completion, the function returns a shared memory segment identifier. If the segment does not exist or cannot be created, -1 is returned and errno indicates the error that occurred.
The shmctl function allows several different control operations to be performed on a shared memory segment:
    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/shm.h>
    int shmctl(int shmid, int cmd, struct shmid_ds *buf);
The shmid parameter contains the shared memory segment identifier of interest. The buf parameter points to a structure of type struct shmid_ds, which describes the memory segment:
    struct shmid_ds {
        struct ipc_perm     shm_perm;
        int                 shm_segsz;
        struct anon_map    *shm_amp;
        ushort              shm_lkcnt;
        pid_t               shm_lpid;
        pid_t               shm_cpid;
        ulong               shm_nattch;
        ulong               shm_cnattch;
        time_t              shm_atime;
        long                shm_pad1;
        time_t              shm_dtime;
        long                shm_pad2;
        time_t              shm_ctime;
        long                shm_pad3;
        kcondvar_t          shm_cv;
        char                shm_pad4[2];
        struct as          *shm_sptas;
        long                shm_pad5[2];
    };
The shm_perm element of this structure describes the permission bits on the segment, as described in the introduction to this section. The shm_segsz element contains the size of the segment, in bytes. The shm_lpid and shm_cpid elements contain the process ID of the last process to modify the segment, and the process ID that created the segment, respectively. The shm_lkcnt element contains the number of locks on this segment. The shm_nattch element contains the number of processes that currently have this memory segment attached. Finally, the shm_atime, shm_dtime, and shm_ctime elements contain the time of the last attachment of the segment, time of the last detachment of the segment, and time of the last permissions change on the segment, respectively.
The cmd parameter to shmctl may have one of the following values:
IPC_STAT
Place the current contents of the struct shmid_ds structure into the area pointed to by buf.
IPC_SET
Change the shm_perm.uid, shm_perm.gid, and shm_perm.mode elements of the struct shmid_ds structure to the values found in the area pointed to by buf. This operation is restricted to processes with an effective user ID of the superuser, or one that is equal to either shm_perm.cuid or shm_perm.uid.
IPC_RMID
Remove the shared memory identifier specified by shmid from the system, and destroy the memory segment and data structure associated with it. This command may only be executed by a process with an effective user ID of the superuser, or one that is equal to either shm_perm.cuid or shm_perm.uid.
SHM_LOCK
Lock the shared memory segment specified by shmid into memory. This requires superuser status.
SHM_UNLOCK
Unlock the shared memory segment specified by shmid. This requires superuser status.
If successful, shmctl returns 0. If an error occurs, shmctl returns -1 and stores the reason for failure in errno.
Before a process may use a shared memory segment, it must attach that segment; that is, it must map the segment into the process' address space. The function to attach a shared memory segment is called shmat:
    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/shm.h>
    void *shmat(int shmid, void *shmaddr, int shmflg);
The shmid parameter specifies the identifier of the segment to be attached. The shmaddr parameter specifies the address at which the memory should be attached; normally this is specified as 0 (allowing the system to choose) unless special circumstances prevail. If shmflg contains the constant SHM_RDONLY the memory segment is attached read-only, otherwise it is attached read-write. If the memory segment is successfully attached, shmat will return the address at which it starts. Otherwise, it returns (void *) -1 and the reason for failure is stored in errno.
Once a program is done using a shared memory segment, it may call shmdt to detach it:
    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/shm.h>
    int shmdt(void *shmaddr);
The shmaddr parameter should contain the value returned by shmat.
Semaphores
Semaphores do not exchange data between processes. They are counters that are used to provide synchronized access to a shared data object among multiple processes. To obtain access to a shared resource, a process:
 1.Tests the value of the semaphore that controls access to the resource.
 2.If the value is greater than 0, the process can use the resource. It decrements the semaphore by 1, indicating that it is using one unit of the resource.
 3.If the value of the semaphore is 0, the process goes to sleep until the value is greater than 0. When the process wakes up, it returns to step 1.
When a process is done using a shared resource controlled by a semaphore, the semaphore's value is incremented by 1. If any processes are stuck in step 3 above, one of them is awakened. Most semaphores are binary, and their values are initialized to 1. However, any positive value can be used, with the value indicating how many units of the resource are available for sharing.
For semaphores to work properly, you must be able to test the value of a semaphore and decrement it in a single operation. For this reason, semaphores are usually implemented in the kernel.
The System V IPC version of semaphores operates on semaphore sets, rather than individual semaphores. Before using a semaphore set, a process must obtain the identifier for it. This is done using the semget function:
    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/sem.h>
    int semget(key_t key, int nsems, int semflg);
The nsems parameter specifies the number of semaphores in the set. The key parameter specifies the key to use for this semaphore set; it may either be the value IPC_PRIVATE, in which case a new set will always be created, or a non-zero value. If key contains a non-zero value, msgget will either create a new semaphore set or return the identifier of an existing set, depending on whether or not the IPC_CREAT bit is set in the semflg argument. The semflg parameter is also used to specify the read/write permissions on the semaphores in the set, in the same manner as with open and creat. Upon successful completion, a semaphore set identifier is returned. If the set does not exist or cannot be created, -1 is returned and errno will describe the error that occurred.
The semctl function allows several different control operations to be performed on a semaphore set:
    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/sem.h>
    int semctl(int semid, int semnum, int cmd, union semun arg);
    union semun {
        int                 val;
        struct semid_ds    *buf;
        ushort             *array;
    };
The semid parameter contains the identifier of the semaphore set, while the semnum parameter contains the number of the specific semaphore of interest. The arg parameter is a union of type union semun; its use is described below. A structure of type struct semid_ds describes the semaphore set:
    struct semid_ds {
        struct ipc_perm     sem_perm;
        struct sem         *sem_base;
        ushort              sem_nsems;
        time_t              sem_otime;
        long                sem_pad1;
        time_t              sem_ctime;
        long                sem_pad2;
        long                sem_pad3[4];
    };
The sem_perm element of this structure describes the permission bits on the set, as described in the introduction to this section. The sem_nsems element contains the number of semaphores in the set. The sem_otime and shm_ctime elements contain the time of the last semaphore operation and the time of the last permissions change on the set, respectively.
Each semaphore in the set is described by a structure of type struct sem:
    struct sem {
        ushort        semval;
        pid_t         sempid;
        ushort        semncnt;
        ushort        semzcnt;
        kcondvar_t    semncnt_cv;
        kcondvar_t    semzcnt_cv;
    };
The semval element contains the semaphore's current value. The sempid element contains the process ID of the last process to operate on this semaphore. The semncnt and semzcnt elements contain the number of processes waiting for the semaphore's value to become greater than its current value, and to become zero, respectively.
The cmd parameter to semctl may have one of the following values:
IPC_STAT
Place the current contents of the struct semid_ds structure into the area pointed to by arg.buf.
IPC_SET
Change the sem_perm.uid, sem_perm.gid, and sem_perm.mode elements of the struct semid_ds structure to the values found in the area pointed to by arg.buf. This operation is restricted to processes with an effective user ID of the superuser, or one that is equal to either sem_perm.cuid or sem_perm.uid.
IPC_RMID
Remove the semaphore set identifier specified by semid from the system, and destroy the set of semaphores and data structure associated with it. This command may only be executed by a process with an effective user ID of the superuser, or one that is equal to either sem_perm.cuid or sem_perm.uid.
GETVAL
Return the value of semval for the specified semaphore.
SETVAL
Set the value of semval for the specified semaphore to arg.val.
GETPID
Return the value of sempid for the specified semaphore.
GETNCNT
Return the value of semncnt for the specified semaphore.
GETZCNT
Return the value of semzcnt for the specified semaphore.
GETALL
Store the value of semval for all semaphores in the set in the array pointed to by arg.array.
SETALL
Set the value of semval for all semaphores in the set to the values in the array pointed to by arg.array.
If successful, semctl returns a positive value for the GETVAL, GETPID, GETNCNT, and GETZCNT commands, and 0 otherwise. If an error occurs, semctl returns -1 and stores the reason for failure in errno.
Semaphores are operated on with the semop function:
    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/sem.h>
    int semop(int semid, struct sembuf *ops, size_t nops);
    struct sembuf {
        ushort    sem_num;
        short     sem_op;
        short     sem_flg;
    };
The semid argument specifies the semaphore set of interest, and ops points to a list of nops structures of type struct sembuf. Within each structure, sem_num specifies the number of the semaphore to be manipulated, sem_op specifies the operation to be performed, and sem_flg specifies any flags for the operation:
 If sem_op is positive, its value is added to the semaphore's value. This corresponds to releasing a shared resource the program was using.
 If sem_op is negative, this indicates the program wants to obtain resources controlled by the semaphore.
If the semaphore's value is greater than or equal to the absolute value of sem_op (the resources are available), the absolute value of sem_op is subtracted from the semaphore's value.
If the semaphore's value is less than the absolute value of sem_op (the resources are not available), semop either returns immediately with an error (if IPC_NOWAIT was specified in sem_flg), or puts the process to sleep until the semaphore's value becomes greater than or equal to the absolute value of sem_op.
 If sem_op is 0, semop blocks until the semaphore's value becomes 0 (unless IPC_NOWAIT is specificed in sem_flg).

Previous SectionNext Section
Books24x7.com, Inc © 2000 –  Feedback