Network Programming: Inter Process Communication

Download as pptx, pdf, or txt
Download as pptx, pdf, or txt
You are on page 1of 82

NETWORK PROGRAMMING

(Unit V)

Inter Process Communication

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

own address space, more details to consider.


For two processes to communicate with each other, they must both
agree to it, and the operating system must provide some facilities
for the IPC.

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

2) Message Passing---Used when processes wish to


exchange information. Several forms of message passing
are
Pipes
FIFOs
Message Queues
Shared Memory

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

correct knowledge of which files have been locked by which


processes, but it does not prevent some process from writing to a
file that is locked by another process.
A process can ignore an advisory lock and write to

a file that is

locked, if the process has adequate permissions.


Advisory locks are fine for what are called cooperating processes.
Ex:- Programming of daemons used by network programming
Mandatory locking- provided by some systems, mean that the

operating system checks every read and write request to verify


that the operation does not interfere with a lock held by a process.
System V Release 2 provides advisory locking, as does 4.3 BSD
System V Release 3 provides both advisory and mandatory
locking.
By default, System V Release 3 provides advisory locking.
To enable mandatory locking for a particular file, turn the groupexecute bit off and turn the set-group-ID bit on for the file.

possible values for the ai_flags member


If the hints argument is a null pointer, the function assumes

a value of 0 for ai_flags, ai_socktype, and ai_protocol, and a


value of AF_UNSPEC for ai_family.
If the function returns success (0), the variable pointed to
by the result argument is filled in with a pointer to a linked
list of addrinfo structures, linked through the ai_next
pointer.
There are two ways that multiple structures can be returned:
1. If there are multiple addresses associated with the
hostname, one structure is returned for each address that
is usable with the requested address family (the ai_family
hint, if specified).
2. If the service is provided for multiple socket types, one
structure can be returned for each socket type, depending
on the ai_socktype hint.

File Locking versus Record Locking


File locking Locks an entire file
Record locking- allows a process to lock a specified portion of a

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.

File Locking versus Record Locking


function:
F_ULOCK---unlock a previously locked region
F_LOCK ---lock a region(blocking)
F_TLOCK ---Test and lock a region(nonblocking)
F_TEST ---Test a region to see if it is locked.

Example: Use F_TLOCK instead of F_TEST and F_LOCK.

If (lockf(fd, F_TEST, size)= =0)


/* If the region is locked, -1 is returned and the process is in sleep
state*/
Re= lockf(fd, F_LOCK, size);
/*a small chance that another process locks between TEST and
LOCK*/

rc=lockf(fd, F_TLOCK, size)


/* Test + lock done as an atomic operation, If unsuccessful, lockf()
returns 1 and the calling process continues to do other things*/

File Locking versus Record Locking


System V Release 2 Advisory Locking:Original System V Release 2 did not provide any type of file
or record locking. Enhanced version of SVR2 provides
advisory file and record locking. With SVR3, mandatory file
and record locking are provided.
4.3 BSD Advisory Locking:flock system call is provided to lock and unlock a file.
#include <sys/file.h>
int flock(int fd, int operation);
fd- is file descriptor of an open file, and operation is built
from the following constants:

LOCK_SH Shared lock


LOCK_EXExclusive lock
LOCK_UN Unlock
LOCK_NB Dont block when locking

File Locking versus Record Locking


More than one share lock can be applied to a

file at any time, but a file cannot have both a


shared lock and an exclusive lock, or multiple
exclusive locks at any time.
Possible locking sequences
LOCK_SH
Shared lock (blocking)
LOCK_EX
Exclusive lock (blocking)
LOCK_SH | LOCK_NB Shared lock (nonblocking)
LOCK_EX | LOCK_NB Exclusive lock (blocking)
LOCK_UN
Unlock
If the lock was successful, zero is returned by flock,
otherwise -1 is returned.

Other Unix Locking Techniques


Three features of Unix file system implementation

that can be exploited to provide a type of locking.


Create and use an ancillary file as an indicator that
the process has the shared resource locked.
If the ancillary file exist- resource is locked by some
other process, if not exist - the calling process must
create the ancillary file to lock the resource.
1. The link system call fails if the name of the new
link to the file already exist.
2. The create system call fails if the file already
exists and if the caller does not have write
permission for the file.
3. Options to open system call that cause it to fail if
the file already exists.

Link system call


int link(char *existingpath, char *newpath);
Link system call fails if the name of the new link to
the file already exists.
Create a unique temporary file, whose name is
based on ID of the process. Once this file is
created, we try to form another link to it, under
the name of the ancillary lock file.
If the link succeeds, then the process has the lock,
and the lock file is pointed to by two directory
entries- temporary filename based on the process
ID and the well-known name of the lock file.
The temporary file name can now be removed,
leaving only a single link to the file.

Create system call


int create( char *pathname, int node);
Create system call returns an error if the file
exists and write permission is denied.
Create a temporary lock file with all write
permissions disabled, and if the create succeeds,
the calling process knows it has the lock, and the
sequence number file can be safely modified.
The unlock operation removes the temporary lock
file, since the unlink system call succeeds if the
caller has write permission in the directory.
The caller does not need either read or write
permission for the file being unlinked.

Open system call


#include <fcntl.h>
int open (char *pathname, int oflag, [, int
mode]);
<fcntl.h> header defines the option flag for the
open system call.
If both O_CREATE and O_EXCL are specified, the
open fails if the file already exists, otherwise
the file is created.

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.

multiple system calls and several file operations are required.


An ancillary file is required, in addition to the file containing
the resource that is shared.
Ancillary files such as a lock file might be left around after a
system crash and some technique must be devised to handle
this.
Link system call cannot create a link between files on different
logical file systems, so temporary file cannot normally be place
in the /tmp file system.
Second technique does not work if the processes competing
for the resource run with super user privileges.
When the ancillary lock is owned by another process, the
process wanting the lock doesnt know when to check again.
The process that obtains the lock can terminate, intentionally
or unintentionally, without releasing the lock.

A Simple Client-Server or IPC model:

Client
reads
filename
from sdtin,
write it to the IPC
channel, read the
file contents from the IPC
channel

Server reads filename


from the
IPC channel, opens file,
write
the contents on 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,

Program Example of Simple Client-Server


Model:
main()
{
int childpid, pipe1[2], pipe2[2];
if (pipe(pipe1) < 0 || pipe(pipe2) < 0) /*
step 1: create pipe1 and pipe2 */
err_sys("can't create pipes");
if ( (childpid = fork()) < 0)
{
/*step 2: fork a child process */
err_sys("can't fork");
}
else if (childpid > 0) /* parent */
{ /* step 3: parent closes read end of pipe
1 and write end of pipe 2*/
close(pipe1[0]);
close(pipe2[1]);
client(pipe2[0], pipe1[1]); /*client runs
in the parent process*/
while (wait((int *) 0) != childpid) /* wait
for child */
close(pipe1[1]);
close(pipe2[0]);
exit(0);
}

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

} /* server function close */

Properties of pipes
1. Pipes do not have a name. For this reason, the

processes must share a parent process. This is the


main drawback to pipes.
2. Pipes are treated as file descriptors, so the pipes
remain open even after fork and exec.
3. Pipes do not distinguish between messages; they just
read a fixed number of bytes. Newline (\n) can be
used to separate messages. A structure with a length
field can be used for message containing binary data.
4. Pipes can also be used to get the output of a
command or to provide input to a command.
Ex: $ ls | wc l > countfile
$ who | sort > sortedlist

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.

Popen is also used to determine the current working directory of a process.

If opoen fails NULL is returned.

pclose() -Closes an I/O stream that was created by popen


int pclose(FILE *stream);

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

FIFO already exists, then an EEXIST error is


returned.
To open an existing FIFO, use open(), fopen() or
freopen()
Close: to close an open FIFO, use close().
To delete a created FIFO, use unlink().

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.

Examples: /* simple client-server model using FIFOs */


use FIFOs not PIPEs to implement the IPC channels so that any two
processes/programs are capable of IPC as long as they reside on the same host.
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/errno.h>
Extern int errno;
#define FIFO1 "/tmp/fifo.1"
#define FIFO2 "/tmp/fifo.2"
#define PERMS 0666 /* octal value*/
main()
{
int childpid, readfd, writefd;
if ( (mkfifo(FIFO1, PERMS) < 0) && (errno != EEXIST))
err_sys("can't create fifo 1: %s", FIFO1);
if ( (mkfifo(FIFO2, PERMS) < 0) && (errno != EEXIST))
{
unlink(FIFO1);
err_sys("can't create fifo 2: %s", FIFO2);
}
if ( (childpid = fork()) < 0)
{
err_sys("can't fork");
}

else if (childpid > 0) { /* parent */


if ( (writefd = open(FIFO1, 1)) < 0)
err_sys("parent: can't open write fifo");
if ( (readfd = open(FIFO2, 0)) < 0)
err_sys("parent: can't open read fifo");
client(readfd, writefd);
while (wait((int *) 0) != childpid); /* wait for child */
close(readfd);
close(writefd);
if (unlink(FIFO1) < 0)
err_sys("parent: can't unlink %s", FIFO1);
if (unlink(FIFO2) < 0)
err_sys("parent: can't unlink %s", FIFO2);
exit(0);
} else { /* child */
if ( (readfd = open(FIFO1, 0)) < 0)
err_sys("child: can't open read fifo");
if ( (writefd = open(FIFO2, 1)) < 0)
err_sys("child: can't open write fifo");
server(readfd, writefd);
close(readfd);
close(writefd);
exit(0);
}
}

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

} /* server function close */

/*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);
}

Program for server */


Server main function
#include "fifo.h"
main()
{
int readfd, writefd;
/* Create the FIFOs, then open them -- one for reading and one for writing. */
if ( (mknod(FIFO1, S_IFIFO | PERMS, 0) < 0) && (errno != EEXIST))
printf("can't create fifo: %s", FIFO1);
if ( (mknod(FIFO2, S_IFIFO | PERMS, 0) < 0) && (errno != EEXIST))
{
unlink(FIFO1);
printf("can't create fifo: %s", FIFO2);
}
if ( (readfd = open(FIFO1, 0)) < 0)
printf("server: can't open read fifo: %s", FIFO1);
if ( (writefd = open(FIFO2, 1)) < 0)
printf("server: can't open write fifo: %s", FIFO2);
server(readfd, writefd);
close(readfd);
close(writefd);
exit(0);
}

Streams and Messages


Pipes and FIFOs uses stream I/O model.
No record boundaries- reads and writes do not examine the data at all.
Ex:- a process reading 100 bytes from a pipe, cannot tell if the process that

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.

Streams and Messages


We define a message structure in our mesg.h header file as
/* Definition our message */
#define MAXMESGDATA
#define MESGHDRSIZE

(4096-16)
( sizeof(Mesg)- MAXMESGDATA )

/* length of mesg_len and mesg_type */


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];
}Mesg;
Each message is preceded with Mesg definition instead of using newlines to separate messages.
Adv:- if binary messages are being exchanged- with the length preceding the message the
actual content of the message does not have to be scanned to find the end.

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

server connect to exchange messages.

Naming conventions used by the different


forms of IPC
IPC Type
Name Space
Identificatio
n
Pipe
(no name)
FIFO
Pathname
Message queue
key_t key
Shared memory
key_t key
Semaphore
key_t key
Socket-Unix
Pathname
domain
(domain
Socket-other
Last
column specifies dependent)
how a process
domains

particular form of IPC.

File
descriptor
File
descriptor
Identifier
Identifier
Identifier
File
accesses
a
descriptor
File
descriptor

key_t keys: Function ftok is provided by the System V standard C library.


Converts a pathname and project identifier into a System V IPC key.
key_t keys are used to identify message queues, shared memory and semaphores
File <sys/types.h> defines the key_t data type, which is a 32-bit integer.

#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

file used by the server or some other pathname on the system.


proj- if client server only need a single IPC channel between them, a proj of one can be used.

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

Collectively known as System V IPC.


Share some similarities in system calls that access them,

and in the information that the kernel maintains on


them.

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

take a key value, whose type is key_t, and return an integer


identifier.

Figure: Generating IPC ids using ftok


get system calls also take a flag argument - specifies the low-

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

msgflag is combination of the constant

#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

MSG_R>> Read by group


3

0020

MSG_W>
>3

0004

MSG_R>> Read by others


6

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.

Example: Use 2 message queues to implement the C/S model.


// mainserver.c
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/errno.h>
extern int errno;
#define MKEY1 1234L
#define MKEY2 2345L /* Long integer */
#define PERMS 0666 /* octal value */
main() /*main function for the server*/
{
int readid, writeid;
/* Create the message queues, if required. */
if ( (readid = msgget(MKEY1, PERMS | IPC_CREAT)) < 0)
printf("server: can't get message queue 1");
if ( (writeid = msgget(MKEY2, PERMS | IPC_CREAT)) < 0)
printf("server: can't get message queue 2");
server(readid, writeid);
exit(0);
}

Example: Use 2 message queues to implement the C/S model.

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

Example: Use 2 message queues to implement the C/S model.

// 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");
}

Example: Use 2 message queues to implement the C/S model.

// 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 */
}

Example: Use 2 message queues to implement the C/S model.


// client.c
* #include <stdio.h>
#include "mesg.h"
Mesg mesg;
client(int ipcreadfd, int ipcwritefd)
{
int n;
/* Read the filename from standard input, write it as a message to the IPC
descriptor. */
if (fgets(mesg.mesg_data, MAXMESGDATA, stdin) == NULL)
printf("filename read error");
n = strlen(mesg.mesg_data);
if (mesg.mesg_data[n-1] == '\n')
n--; /* ignore newline from fgets() */
mesg.mesg_len = n;
mesg.mesg_type = 1L;
mesg_send(ipcwritefd, &mesg);
/* Receive the message from the IPC descriptor and write the data to the
standard output. */
while ( (n = mesg_recv(ipcreadfd, &mesg)) > 0)
if (write(1, mesg.mesg_data, n) != n)
printf("data write error");
if (n < 0) printf("data read error");

Example: Use 2 message queues to implement the C/S model.

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

Example: Use 2 message queues to implement the C/S model.

// 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

read/write the queue.

Type of each message


signifying the message
is from the client to the
server, or vice versa.

Ability of the receiver to


read the messages in an
order other than FIFO.

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

to acquire it, that process blocks.


When a process waits for a semaphore, the kernel puts the
process to sleep until the semaphore is available.
The kernel maintains information on each semaphore
internally, using a data structure struct semid_ds that
keeps track of permission, number of semaphores, etc.
Semaphore in Unix is not a single binary value, but a set of
nonnegative integer values

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

Kernel data structures for a


semaphore set

Semaphores
Semaphore

is created, or an existing semaphore


accessed with the semget system call.
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflag);
retruns semid- semaphore identifier, or -1 on error.
nsems number of semaphores in the set.

is

If we are not creating a new semaphore set, but only


accessing an existing set (i.e., if we did not specify the
IPC_CREAT flag for the semflag argument), specify this
argument must be zero.
We can not change the number of semaphores in a set once it
is created.

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

are performed on one or more of the semaphore values in


the set using the semop system call.
int semop(int semid, struct sembuf
nops);

**opsptr, unsigned int

opsptr points to an array of sembuf structures


struct sembuf {
ushort sem_num;
*/
short sem_op;
short sem_flg;
for nonblocking
call,
automatically
prematurely.*/
};

/* semaphore #, numbered from 0, 1, 2


/* semaphore operation */
/*operations flags, such as 0, IPC_NOWAIT
or

SEM_UNDO to have the semaphore


released when the process is terminated

nops specifies the number of elements in the array of

Semaphores
The array of operations passed to the semop system call

are guaranteed to be done atomically by the kernel.


sem_op specifies each particular operation which can be
either negative, zero, or positive.
1. If sem_op is positive, the value of sem_val is added to the
semaphores current value. This corresponds to the
release of resources that a semaphore controls.
2. If sem_op is zero, the caller wants to wait until the
semaphores value becomes zero.
3. If sem_op is negative, the caller wants to wait until the
semaphores value becomes greater than or equal to the
absolute value of sem_op. This corresponds to the resource
allocation.
semop returns zero if all is ok, or -1 if an error occurred.

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;

cmd- command can be


IPC_RMID removes semaphore from the system.
GETVAL and SETVAL to fetch and set a specific semaphore
value.
these two commands use semnum argument to specify one
member of the semaphore set.
GETVAL command returns the semaphores value as the
value of the system call.

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

Using a pipe or a message queue requires multiple exchanges of data


through the kernel. Shared memory can be used to bypass the kernel
for faster processing.

Typical movement of data between client and server


The problem with these forms of IPC- is that for two

processes to exchange information, the information has to


go through the kernel.
Shared memory provides a way around this by letting two
or more processes share a memory segment. the
processes have to coordinate the use of memory among

Shared
Memory

Movement of data between client and server using shared memory

Data is copied only twice- from the input

file to shared memory and from shared


memory to the output file.

Shared
Memory
The kernel maintains information about each shared memory

segment, including permission, size, access time, etc in


struct shmid_ds .
struct shmid_ds
{
struct ipc_perm shm_perm; /* operation permission struct*/
int
shm_segsz;/* segment size */
struct XXX shm_YYY; /* implementation dependent info */
ushort shm_lpid; /* pid of last operation */
ushort shm_cpid; /* creator pid */
ushort shm_nattch; /* current # attached */
ushort shm_cnattch;
/* in-core # attached */
time_t shm_atime; /* last attach time*/
time_t shm_dtime; /* last detach time*/
time_t shm_ctime; /* last change time*/
};

Shared
Memory
shmget :shared memory is created or an existing one is
accessed with the shmget system call.

int shmget(key_t key, int size, int shmflag);


size --- size of the shared memory segment in bytes.
shmflag --- same as for msgget() and semget(), see
Lecture 4s Comments 1.
Return value --- shmid, the shared memory identifier, -1
on error.

Shared
Memory
shmat :-

Attach to the shared memory segment using


char *shmat(int shmid, char *shmaddr, int shmflag)
shmid --- return value of shmget, that is, the id of the
created shared memory.
shmaddr--- 0: let the kernel select the address.
shmflag--- SHM_RDONLY for read_only access.

returns the starting address of the shared memory,


and thus we can read/write on the shared memory
after getting its starting address.

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

You might also like

pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy