Network Programming: Inter Process Communication
Network Programming: Inter Process Communication
Network Programming: Inter Process Communication
(Unit V)
Introduction to IPC
Network programming involves the interaction of two or more
processes.
Different methods available for different processes to communicate.
In traditional single process programming different modules of a
program within the single process can communicate with each
other using
- global variables
- function calls
- arguments and results passed back and forth between functions their
callers.
When dealing with separate processes, each executing within its
Introduction to IPC
Introduction to IPC
IPC is used for 2 functions:
1) Synchronization---Used to coordinate access to
resources among processes and also to coordinate the
execution of these processes. They are
Record locking
Semaphores
Mutexes and Condition variables
File and Record Locking: There are some occasions when multiple processes want to share some
resource.
Some form of mutual exclusion be provided so that only one process at
a time can access the resource.
Consider the following scenario, which comes from the line printer
daemon. The process that places a job on the print queue(to be printed
at a later time by another process) has to assign a unique sequence
number to each print job.
The technique used by the Unix print spoolers is to have a file for each
printer that contains the next sequence number to be used. The file is
just a single line containing the sequence number in ASCII.
Each process that needs to assign a sequence number goes three steps
1. it reads the sequence number file
2. it uses the number
3. it increments the number and writes it back.
Problem is that in the time it takes a single process to execute these
three steps, another process can perform the same three steps. Chaos
can result.
What is need is for a process to be able to set a lock to say that no
other process can access the file until the first process is done.
Advisory Locking versus Mandatory Locking: Advisory locking means that the operating system maintains a
a file that is
file.
Record locking is given by specifying a starting byte offset in
the file and number of bytes from that position.
In Unix, record locking is better termed as range locking.
For System V:
#include <unistd.h>
int lockf(int fd, int function, long size);
fd---file descripter (not a file pointer)
size--- define the record size or lock area: [offset, offset +
size].
size=0 means the rest of the file.
Use lseek() to move the current offset.
When the offset position is set to the beginning and size=0
then lock the whole file.
Points to consider
1. First two techniques work under any version of Unix.
2. Take longer to execute than actual file locking system calls3.
4.
5.
6.
7.
8.
Client
reads
filename
from sdtin,
write it to the IPC
channel, read the
file contents from the IPC
channel
Pipes
A pipe provides a one-way flow of data. A pipe is created by pipe system
call.
int pipe (int * filedes);
Two file descriptors are returned filedes[0] which is open for reading,
and filedes[1] which is open for writing.
Example to show how to create and use a pipe:
main()
{
int pipefd[2], n;
char buff[100];
if (pipe(pipefd) < 0 )
err_sys(pipe error);
printf(read fd = %d, write fd = %d\n, pipefd[0], pipefd[1]);
if (write(pipefd[1], hello world\n, 12) != 12)
err_sys(write error);
if ( (n=read(pipefd[0], buff, sizeof(buff))) < =0) err_sys(read error);
write(1, buff, n); /*fd=1=stdout*/
exit(0);
}
Output: hello workd
read fd=3, write fd=4
Pipes
Has always finite size, at least 4096 bytes.
Pipes
Pipes
Steps :
1) opening a pipe
2) forking off another process
3) closing the appropriate pipes on
each end
Pipes
Steps :
1) create pipe1 + pipe2 : int pipe1[2], pipe2[2] -----must
be the first step
2) forking off a child process, executing another program
as a server
3) parent closes read end of pipe 1 + write end of pipe 2,
else /* child */
{/* step 4: child closes
write end of pipe 1 and
read end of pipe 2*/
close(pipe1[1]);
close(pipe2[0]);
server(pipe1[0],
pipe2[1]); /*server
runs in the child
process */
close(pipe1[0]);
close(pipe2[1]);
exit(0);
}
} /* end of main() */
Client function
#include <stdio.h>
#define MAXBUFF 1024
client(int readfd, int writefd)
{
char buff[MAXBUFF];
int n;
/* read the filename from standard input */
if (fgets(buff, MAXBUFF, stdin) == NULL)
err_sys("client: filename read error");
n = strlen(buff);
if (buff[n-1] == '\n')
n--; /* ignore newline from fgets() */
/* write it to the IPC descriptor, pipe1[1] */
if (write(writefd, buff, n) != n)
err_sys("client: filename write error");
/* read the data from the IPC descriptor, pipe2[0], and write to standard
output. */
while ( (n = read(readfd, buff, MAXBUFF)) > 0)
if (write(1, buff, n) != n) /* fd 1 = stdout */
err_sys("client: data write error");
if (n < 0)
err_sys("client: data read error");
} /* end of client functin */
server function
#include <stdio.h>
#define MAXBUFF 1024
server( int readfd, int writefd)
{
char buff[MAXBUFF];
char errmesg[256], *sys_err_str();
int n, fd;
extern int errno;
/* read the filename from the IPC descriptor, pipe1[0]*/
if ( (n = read(readfd, buff, MAXBUFF)) <= 0)
err_sys("server: filename read error");
buff[n] = '\0'; /* null terminate filename */
/* open the file from the IPC descriptor, pipe1[0]*/
if ( (fd = open(buff, 0)) < 0) {
/* Error. Format an error message and send it back to the client. */
sprintf(errmesg, ": can't open, %s\n", sys_err_str());
strcat(buff, errmesg);
n = strlen(buff);
if (write(writefd, buff, n) != n)
err_sys("server: errmesg write error");
} else {
/* Read the data from the file and write to the IPC descriptor. */
while ( (n = read(fd, buff, MAXBUFF)) > 0)
if (write(writefd, buff, n) != n)
err_sys("server: data write error");
if (n < 0)
err_sys("server: read error");
}
Properties of pipes
1. Pipes do not have a name. For this reason, the
Properties of pipes
Standard I/O library provides a function that creates a pipe and initiates another process that either
reads from the pipe or writes to the pipe.
#include<stdio.h>
FILE *popen(const char *command, const char *type);
command- is a shell command line
A pipe is created between the calling process and the specified command.
Value returned by popen is a standard I/O FILE pointer that is used for either input or output,
depending on the character string type.
r reads the standard output of the command
w- calling process writes to the standard input of the command.
Drawbacks of pipes
Can only be used between processes that have a parent process
in common.
A pipe is passed from one process to another by the fork
system call.
No way for two totally unrelated processes to create a pipe
between them and use it for IPC.
FIFOs
A FIFO is similar to a pipe. A FIFO (First In First Out) is a one-way flow of
data.
FIFOs have a name, so unrelated processes can share the FIFO.
FIFO is a named pipe. This is the main difference between pipes and FIFOs.
Creat: A FIFO is created by the mkfifo function
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
pathname a UNIX pathname (path and filename). The name of the
FIFO
mode the file permission bits.
FIFO can also be created by the mknod system call,
e.g., mknod(fifo1, S_IFIFO|0666, 0) is same as mkfifo(fifo1, 0666).
FIFOs
Open: mkfifo tries to create a new FIFO. If the
Properties of FIFOs
Properties: ( Some of them are also applicable to PIPES)
1) After a FIFO is created, it can be opened for read or write.
2) Normally, opening a FIFO for read or write, it blocks until another process
opens it for write or read.
3) A read gets as much data as it requests or as much data as the FIFO has,
whichever is less.
4) A write to a FIFO is atomic, as long as the write does not exceed the capacity of
the FIFO. The capacity is at least 4kb.
5) Blocked if read from an empty FIFO, or write to a full FIFO.
6) The O_NDELAY flag or O_NONBLOCK flag can be set for FIFO to affect the
behavior of various operations. This prevents accesses to the FIFO from
blocking.
Properties of FIFOs
Example: how to set the flags?
writefd=open(FIFO1, O_WRONLY | O_NONBLOCK, 0);
For a pipe, however, fcntl() must be used to set this option. This is because pipes are
opened as part of the pipe() system call:
int flags;
flags=fcntl(fd, F_GETFL,0); /* get the flags */
flag |= O_NONBLOCK;
fcntl(fd, F_SETFL, flags); /* set the new flagts */
7) If a process writes to a pipe of FIFO, but there are no processes in existence that have it
open for reading, the SIGPIPE signal is generated, and the write returns zero with errno
set to EPIPE. If the process has not called signal to handle SIGPIPE notification, the
default action is to terminate the process.
Client function
#include <stdio.h>
#define MAXBUFF 1024
client(int readfd, int writefd)
{
char buff[MAXBUFF];
int n;
/* read the filename from standard input */
if (fgets(buff, MAXBUFF, stdin) == NULL)
err_sys("client: filename read error");
n = strlen(buff);
if (buff[n-1] == '\n')
n--; /* ignore newline from fgets() */
/* write it to the IPC descriptor, pipe1[1] */
if (write(writefd, buff, n) != n)
err_sys("client: filename write error");
/* read the data from the IPC descriptor, pipe2[0], and write to standard
output. */
while ( (n = read(readfd, buff, MAXBUFF)) > 0)
if (write(1, buff, n) != n) /* fd 1 = stdout */
err_sys("client: data write error");
if (n < 0)
err_sys("client: data read error");
} /* end of client functin */
server function
#include <stdio.h>
#define MAXBUFF 1024
server( int readfd, int writefd)
{
char buff[MAXBUFF];
char errmesg[256], *sys_err_str();
int n, fd;
extern int errno;
/* read the filename from the IPC descriptor, pipe1[0]*/
if ( (n = read(readfd, buff, MAXBUFF)) <= 0)
err_sys("server: filename read error");
buff[n] = '\0'; /* null terminate filename */
/* open the file from the IPC descriptor, pipe1[0]*/
if ( (fd = open(buff, 0)) < 0) {
/* Error. Format an error message and send it back to the client. */
sprintf(errmesg, ": can't open, %s\n", sys_err_str());
strcat(buff, errmesg);
n = strlen(buff);
if (write(writefd, buff, n) != n)
err_sys("server: errmesg write error");
} else {
/* Read the data from the file and write to the IPC descriptor. */
while ( (n = read(fd, buff, MAXBUFF)) > 0)
if (write(writefd, buff, n) != n)
err_sys("server: data write error");
if (n < 0)
err_sys("server: read error");
}
/*Revise the above program into 2 separate programs, without using fork() . One
is for the client; the other is for the server.*/
Client main function
include "fifo.h"
main( )
{
int readfd, writefd;
/* Open the FIFOs. We assume the server has already created them. */
if ( (writefd = open(FIFO1, 1)) < 0)
printf("client: can't open write fifo: %s", FIFO1);
if ( (readfd = open(FIFO2, 0)) < 0)
printf("client: can't open read fifo: %s", FIFO2);
client(readfd, writefd);
close(readfd);
close(writefd);
/* Delete the FIFOs, now that we're finished. */
if (unlink(FIFO1) < 0)
printf("client: can't unlink %s", FIFO1);
if (unlink(FIFO2) < 0)
printf("client: can't unlink %s", FIFO2);
exit(0);
}
wrote the data into the pipe did a single write of 100 bytes, five writes of 20
bytes, or two writes of 50 bytes.
Data is a stream of bytes with no interpretation done by the system.
Writing process and the reading process must agree to it and do it themselves
for any interpretation.
There are times when a process wants to impose some structure on the data
being transferred- when data consists of variable length of messages and it is
required the reader to know where the message boundaries are so that it
knows when a single massage has been read.
New lines and length of messages are used.
More structured messages can also be built for pipe or FIFO.
(4096-16)
( sizeof(Mesg)- MAXMESGDATA )
Name spaces
Pipes do not have names but FIFOs have a Unix pathname to identify
them.
The set of possible names for a given type of IPC is called its name space.
All forms of IPC other than plain pipes, the name is how the client and
File
descriptor
File
descriptor
Identifier
Identifier
Identifier
File
accesses
a
descriptor
File
descriptor
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok ( char *pathname, char proj);
pathname - it could be the path name of the server daemon, pathname of the common data
If multiple IPC channels are needed, say one from client to the server and another from
server to client.
Ftok function converts pathname and proj into the same IPC key.
If pathname does not exist, or is not accessible to the calling process, -1 is returned by the
ftok function.
ftok combines the 8-bit proj value, along with the numeric i-node number of the disk file
corresponding to the specified pathname, along with the minor device number of the file
system on which the disk file resides. The combination of these three values produces a 32-bit
key.
System V IPC
Three types of IPC
Message queues
Semaphores
Shared memory
System V IPC
Kernel maintains a structure of information for each IPC channel.
#include <sys/ipc.h>
Struct ipc_perm{
ushort uid; /* owners user id */
ushort gid; /* owners group id */
ushort cuid; /* creators user id */
ushort cgid; /* creators group id */
ushort mode; /* access modes*/
ushort seq; /* slot usage sequence number*/
key_t key; /* key */
};
Three ctl system calls are used to access this structure and modify it.
System V IPC
Three get system calls that create or open an IPC channel, all
order 9 bits of the mode for the IPC channel, and whether a
new IPC channel is being created, or if an existing one is being
referenced.
System V IPC
Rules for whether a new IPC channel is created or whether an existing one is referred are
Specifying a key of IPC_PRIVATE guarantees that a unique IPC channel is created,
no combinations of pathname and proj that cause ftok to generate a key value of
IPC_PRIVATE.
Setting the IPC_CREAT bit of the flag word creates a new entry for the specified key,
if it does not already exist. If an existing entry is found, that entry is returned.
Setting both IPC_CREAT and IPC_EXCL bits of the flag word creates a new entry for
the specified key, only if the entry does not already exist. If existing entry is found, an
error occurs, since the IPC channel already exists.
Setting the IPC_EXCL bit with out setting the IPC_CREATE bit has no meaning.
Message Queues
Drawback of PIPE and FIFO:
1. Pipe or FIFO is an unformatted stream.
2. Message queue is a formatted stream consisted of
messages.
3. Pipe or FIFO has to be read in the same order as they are
written. Message queue can be accessed randomly.
4. Pipe or FIFO is unidirectional. Message queue is bidirectional.
5. Pipe or FIFO is simplex. Message queue is multiplex.
6. Message queue is faster since it is in kernel.
Message Queues
Some form of message passing between processes is part of
an operating system.
Some operating systems restrict the passing of messages a
process can only send a message to another specific process.
In System V all messages are stored in kernel and have an
associated message queue identifier- called as msqid.
Process can read and write messages to arbitrary queues.
No requirement that any process be waiting for a message to
arrive on a queue before some other process is allowed to
write a message to that queue.
Message Queues
Every message on a queue has following attributes
type
size
data
(long int),
(int), i.e., the length of data,
(if length > 0).
Message Queues
For every message queue in the system, the kernel maintains the msqid_ds
structure.
#include<sys/types.h>
#include<sys/ipc.h>
struct msqid_ds {
struct ipc_perm msg_perm; /*operation permission struct */
struct msg *msg_first; /* pointer to first message on q*/
struct msg *msg_last; /* pointer to last message on q */
ushort
msg_cbytes; /* current # bytes on q */
ushort
msg_qnum; /* # of message on q */
ushort
msg_qbytes; /* max # of bytes on q */
ushort
msg_lspid; /* pid of last msgsnd */
ushort
msg_lrpid; /* pid of last msgrcv */
time_t
msg_stime; /* time of last msgsnd */
time_t
msg_rtime; /* time of last msgrcv */
time_t
msg_ctime; /* time of last msgctl */
};
Message Queues
Message Queues
A message queue is created
or accessed using msgget
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int
msgflag);
returns message queue
identifier, msqid or
-1 if an error occurred.
Nume
ric
Symbolic
Description
0400
MSG_R
Read by owner
0200
MSG_W
Write by owner
0040
0020
MSG_W>
>3
0004
0002
MSG_W>
>6
IPC_CREA
TE
IPC_EXCL
Write by group
Write by others
Message Queues
once a message queue is opened with msgget, we put
a message on the queue using the msgsnd system call
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgsnd( int msqid, struct msgbuf *ptr, int length, int
flag);
ptr- is a pointer to a structure
struct msgbuf
{
long mtype; /* message type, must be >0 */
char mtext[1];
/* message data */
};
length length of the message in bytes.
flag can be either IPC_NOWAIT or as zero.
Message Queues
int msgrcv(int msgid, struct msgbuf *ptr, int length, long
msgtype, int flag);
return the length of the data received in bytes if
successful; -1 on error.
ptr specifies where the received message is stored.
length specify the size of the data buffer. Error if the message
is too long.
OK if the length message length which is determined by
msgsnd (length).
flag if set to MSG_NOERROR, long messages are truncated.
If set to IPC_NOWAIT, function return 1 if no matching
messages are found,
otherwise block until
A message with the appropriate type is placed on the queue.
The message queue is removed from the system.
The process receives a signal that is not ignored.
Message Queues
Control operations use:
#include <sys/types.h>
#include<sys/ipc.h>
#include <sys/msg.h>
int msgctl(int msgid, int cmd, struct msg-id-ds *buff);
To remove a msg queue, cmd = IPC_RMID; the 3rd
parameter is just a NULL pointer.
To get/set the queue information in buff, cmd =
IPC_STAT/IPC_SET.
// mainclient.c
#include "msgq.h"
main() /* main function for the client */
{
int readid, writeid;
/* Open the message queues. The server must have already created them. */
if ( (writeid = msgget(MKEY1, 0)) < 0)
printf("client: can't msgget message queue 1");
if ( (readid = msgget(MKEY2, 0)) < 0)
printf("client: can't msgget message queue 2");
client(readid, writeid);
/* Now we can delete the message queues. */
if (msgctl(readid, IPC_RMID, (struct msqid_ds *) 0) < 0)
printf("client: can't RMID message queue 1");
if (msgctl(writeid, IPC_RMID, (struct msqid_ds *) 0) < 0)
printf("client: can't RMID message queue 2");
exit(0);
}
// sendrecv.c
/* Definition of "our" message. */
#define MAXMESGDATA (4096-16) /* we don't want sizeof(Mesg) > 4096 */
#define MESGHDRSIZE (sizeof(Mesg) - MAXMESGDATA) /* length of mesgge head */
typedef struct {
int mesg_len; /* #bytes in mesg_data, can be 0 or > 0 */
long mesg_type; /* message type, must be > 0 */
char mesg_data[MAXMESGDATA];
/*a trick here: mesg_data must follow mesg_type immediately. */
} Mesg;
/* Send a message using a message queue.
The mesg_len, mesg_type and mesg_data fields must be filled * in by the caller. */
mesg_send(int id, Mesg *mesgptr)
{
/* Send the message - the type followed by the optional data. */
if (msgsnd(id, (char *) &(mesgptr->mesg_type), mesgptr->mesg_len, 0) != 0)
/*another trick here: where is the starting address of message */
printf("msgsnd error");
}
// sendrecv.c (contd..)
* Receive a message from a message queue.
* The caller must fill in the mesg_type field with the desired type.
* Return the number of bytes in the data portion of the message.
* A 0-length data message implies end-of-file. */
int mesg_recv(int id, Mesg *mesgptr)
{
int n;
/* Read the first message on the queue of the specified type. */
n = msgrcv(id, (char *) &(mesgptr->mesg_type), MAXMESGDATA,
mesgptr->mesg_type, 0);
if ( (mesgptr->mesg_len = n) < 0)
printf("msgrcv error");
return(n); /* n will be 0 at end of file */
}
// server.c
#include <stdio.h>
#include "mesg.h"
Mesg mesg;
server(int ipcreadfd, int ipcwritefd)
{
int n, filefd;
char errmesg[256];
/* Read the filename message from the IPC descriptor. */
mesg.mesg_type = 1L;
if ( (n = mesg_recv(ipcreadfd, &mesg)) <= 0) printf("server: filename read error");
mesg.mesg_data[n] = '\0'; /* null terminate filename */
if ( (filefd = open(mesg.mesg_data, 0)) < 0) { /* Error. Send an error message to the
client.*/
sprintf(errmesg, ": can't open the file.\n");
strcat(mesg.mesg_data, errmesg);
mesg.mesg_len = strlen(mesg.mesg_data);
mesg_send(ipcwritefd, &mesg);
}
// server.c
else {
/* Read the data from the file and send a message to the IPC descriptor. */
while ( (n = read(filefd, mesg.mesg_data, MAXMESGDATA)) > 0) {
mesg.mesg_len = n;
mesg_send(ipcwritefd, &mesg);
}
close(filefd);
if (n < 0) printf("server: read error");
}
/* Send a message with a length of 0 to signify the end.*/
mesg.mesg_len = 0;
mesg_send(ipcwritefd, &mesg);
}
Multiplexing Messages:
Message queues
Only one queue, but used for 2 directions, and multiple clients can
Semaphores
Synchronization primitive.
Semaphores are not used to exchange a large amount of
data.
Intended to let multiple processes synchronize their
operations.
Main use of semaphores is to synchronize the access to
shared memory segments.
Other synchronization mechanisms include record locking
and mutexes.
Examples :- shared washroom, common rail segment, and
common bank account.
Consider a semaphore as an integer valued variable that is
a resource counter. The value of the variable at any point in
time is the number of resource units available.
To obtain a resource that is controlled by a semaphore, a
process needs to test its current value, and if the current
value is greater than zero, decrement the value by one.
Semaphores
Further comments:
The semaphore is stored in the kernel:
Allows atomic operations on the semaphore.
Processes are prevented from indirectly modifying the value.
If the semaphore has non-zero value when a process tries
Semaphores
For every set of semaphores in the system, the kernel
maintains the following structure of information
#include <sys/types.h>
#include <sys/ipc.h>
struct shmid_ds
{
struct
ipc_perm
sem_perm;
/*
operation
permission struct*/
struct sem
*sem_base; /* ptr to first semaphore
struct*/
ushort
sem_nsems;/* # of semaphores in set*/
time_t
sem_otime; /* time of last semop*/
time_t
sem_ctime; /* time of last change */
};
Semaphores
sem structure is the internal data structure used by the kernel
to maintain the set of values for a given semaphore.
struct sem
{
ushort
semval;
/*
semaphore
value,
nonnegative */
short sempid;
/* pid of last operation*/
ushort semncnt; /* # awaiting semval > cval */
ushort semzcnt; /* #awaiting semval = 0 */
};
kernel also maintain three other pieces of information for
each value in the set- the process ID of process that did the
last operation on the value, a count of the number of
processes waiting for the value to increase, and a count of
number of processes waiting for the value to become zero.
Semaphores
Semaphores
Semaphore
is
Semaphores
semflag value is a combination of the constants
Nume
ric
Symbolic
Description
0400
SEM_R
Read by owner
0200
SEM_A
Alter by owner
0040
SEM_R>>
3
Read by group
0020
SEM_A>>
3
Alter by group
0004
SEM_R>>
6
Read by others
0002
SEM_A>>
6
Alter by others
IPC_CREA
TE
IPC_EXCL
Semaphores
semop: Once a semaphore set is opened with semget, operations
Semaphores
The array of operations passed to the semop system call
Semaphores
semctl:provides various control operations on a semaphore.
int semctl( int semid, int semnum, int cmd, union semun
arg);
union semun
{
int
val;
/* used for SETAL only */
struct semid_ds *buff; /* used for IPC_STAT and
IPC_SET */
ushort
*array; /* used for IPC_GETALL & IPC_SETALL
*/
}arg;
Semaphores
File Locking with Semaphores:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#define SEMKEY 123456L /* key value for semget() */
#define PERMS 0666
static struct sembuf op_lock[2]=
{
0, 0, 0,
/* wait for sem #0 to become 0 */
0, 1, SEM_UNDO /* then increment sem #0 by 1 */
};
static struct sembuf op_unlock[1]=
{
0, -1, (IPC_NOWAIT | SEM_UNDO) /* decrement sem #0 by 1
(sets it to 0) */
};
int semid = -1; /* semaphore id. Only the first time will create a
semaphore.*/
Semaphores
my_lock( int fd)
{
if (semid <0)
{
if ( ( semid=semget(SEMKEY, 1, IPC_CREAT | PERMS )) < 0 )
printf(semget error);
}
if (semop(semid, &op_lock[0], 2) < 0)
printf(semop lock error);
}
my_unlock(int fd)
{
if (semop(semid, &op_unlock[0], 1) < 0)
printf(semop unlock error);
}
Shared
Memory
Shared
Memory
Shared
Memory
The kernel maintains information about each shared memory
Shared
Memory
shmget :shared memory is created or an existing one is
accessed with the shmget system call.
Shared
Memory
shmat :-
Shared
Memory
Detach the shared memory segment using:
char *shmdt(char *shmaddr)
shmaddr --- the return value of shmat(), that is, the
starting address of the shared memory.
returns 1 on failure.
To remove a shared memory segment:
int shmctl(int shmid, int cmd, shmid_ds *buf);
cmd--- IPC_RMID to delete, e.g., shmctl(shmid,
IPC_RMID, 0) .