Sie sind auf Seite 1von 29

UNIT-VII

Contents:IPC: Introduction, File and Record locking, pipes, FIFOs,


streams and messages, Name Spaces, System V IPC, Message
Queues and Semaphores.

Inter Process Communication (IPC)

Introduction:-

UNIX is a multi-user environment. This means that at any point of time,


there will be a number of processes concurrently active. This gives rise to
need for processes to communicate with each other. There is always a need
for one process to be able to inform another process of the occurrence of an
event or to pass on data for processing.

Inter process communication is all about exchanging messages or data


between processes. Most of the large application programs are developed as
multi-process or multi-threaded applications All these processes (or threads)
of the application should co-ordinate among themselves. The co-ordination
among processes involves following three things.
 Communication
 Synchronization
 Mutual exclusion
First we will study communication between processes. The synchronization
and mutual exclusion between processes will be covered while studying
semaphores.

Characteristics of Inter process communication:-


In Unix, we have different methods for doing inter process communication.
Before studying them, let us see common characteristics or features of
communications. Understanding of these characteristics allows us the
compare various types of IPC.
 Connection Oriented or Connection less
 Message oriented or Stream oriented
 Half duplex or Full duplex
 Name or identifier of communication object

In the case of connection less, message oriented service, a process can


send data without caring for the connection between two processes. So
sender can send message, event if no receiver is present. This will call
connection less mechanism. In the connection oriented communication data
transfers between processes will happen in units of messages only. Each
write operation involves sending one message and each read operation
involved receiving one message. It is not possible to read half message at a
time or more than one message at a time. Even if we read half of messages,
the remaining half of the message will be lost. So in message oriented
communication, message boundaries are maintained.

In the case of connection oriented service, writing or reading is possible


only after connection is established between two processes. That means
both processes should open the connection before reading or writing.

File and Record Locking:

When a file can be accessed by more than one process, a synchronization


problem occurs: what happens if two processes try to write in the same file
location? Or again, what happens if a process reads from a location while
another process is writing into it?. Concurrent accesses to the same file
location produce unpredictable results.
Most unix systems provide a mechanism that allows the processes to lock a
file region so that concurrent accesses may be easily avoided.
There are two types locking mechanisms.
Mandatory Locking
Advisory locking

Posix record locking is called advisory locking. This means the kernel
maintains correct knowledge of all files that have been locked by each
process, but it does not prevent a process from writing to a file that is read-
locked by another process. Similarly, the kernel does not prevent a process
from reading from a file that is write-locked by another process. A process
can ignore an advisory lock and write to a file that is read-locked, or read
from a file that is write-locked, assuming the process has adequate
permissions to read or write the file. Advisory locks are fine for cooperating
processes.

Mandatory locking
Some systems provide another type of record locking, called
mandatory locking. With a mandatory lock, the kernel checks every read and
write request to verify that the operation does not interfere with a lock held
by a process. For a normal blocking descriptor, the read or write that
conflicts with a mandatory lock puts the process to sleep until the lock is
released. To enable mandatory locking for a particular file,
• the group-execute bit must be off, and
• the set-group-ID bit must be on.
File locking vs record locking

File locking locks entire file. Record locking is used to describe the ability of
a process to prevent other processes from modifying a region of a file, while
the first process is reading or modifying that portion of the file.
Unix kernel does not have a notion of records in a file. A better term is
“range locking “, since it is a range of a file that is locked

System V file and record locking:

System V provides the following locking function for file and record locking

# inclu de < unis td .h>


int lo ckf( int fi lde s, in t fu nct io n, o ff_ t siz e );

fildes is an open file descriptor.

function is a control value that specifies the action to be taken. Permissible


values for function are defined in <unistd.h> as follows:
#define F_ULOCK 0 /* unlock a region */
#define F_LOCK 1 /* lock a region */
#define F_TLOCK 2 /* test and lock a region */
#define F_TEST 3 /* test region for lock */

F_TEST is used to detect whether a lock by another process is present on


the specified region. lockf() returns zero if the region is accessible and -1 if it
is not; in which case errno is set to EACCES. F_LOCK and F_TLOCK both lock
a region of a file if the region is available. F_ULOCK removes locks from a
region of the file.

size is the number of contiguous bytes to be locked or unlocked. The


resource to be locked starts at the current offset in the file, and extends
forward for a positive size, and backward for a negative size. If size is zero,
the region from the current offset through the end of the largest possible file
is locked
4.2 BSD File locking

4.2 BSD provides the following locking function for file locking

#include <sys/file.h>
int flock(int fd, int operation);

On success, zero is returned. On error, -1 is returned, and errno is set


appropriately.

Apply or remove an advisory lock on the open file specified by fd. The
parameter operation is one of the following:
LOCK_SH Place a shared lock. More than one process may hold a
shared lock for a given file at a given time.
LOCK_EX Place an exclusive lock. Only one process may hold an
exclusive lock for a given file at a given time.
LOCK_UN Remove an existing lock held by this process.

A call to flock() may block if an incompatible lock is held by another


process. To make a non-blocking request, include LOCK_NB (by ORing)
with any of the above operations.

Following is the locking example program.


/*Write a program to implement lock operations*/

#include<sys/file.h> for(i=1; i<=20; i++)


#include<fcntl.h> {
#include<stdio.h> my_lock(fd);
#include<stdlib.h> lseek(fd,0L,0);
#include<unistd.h> n=read(fd,buff,MAXBUFF);
#include<string.h> buff[n]='\0';
#define SEQFILE "seqno.c" n=sscanf(buff,"%d",&seqno);
#define MAXBUFF 100 printf("pid= %d,seq# = %d\n",pid,seqno);
main() seqno++;
{ sprintf(buff,"%d",seqno);
void my_lock(int); n=strlen(buff);
void my_unlock(int); lseek(fd,0L,0);
int fd,i,n,pid,seqno=0; write(fd,buff,n);
char buff[MAXBUFF]; sleep(1);
pid=getpid(); my_unlock(fd);
fd=open(SEQFILE,O_CREAT|O_RDWR); }
}

void my_lock(int fd)


{
lseek(fd,0L,0);
lockf(fd,F_LOCK,0);
}

void my_unlock(int fd)


{
lseek(fd,0L,0);
lockf(fd,F_ULOCK,0);
}

Output pid= 3614,seq# = 9 pid= 3614,seq# = 25


[root@rgmcse ~]# pid= 3614,seq# = 10 pid= 3614,seq# = 26
gcc lock.c pid= 3614,seq# = 11 pid= 3614,seq# = 27
[root@rgmcse ~]# pid= 3614,seq# = 12 pid= 3615,seq# = 28
./a.out& ./a.out& pid= 3614,seq# = 13 pid= 3615,seq# = 29
[1] 3614 pid= 3614,seq# = 14 pid= 3615,seq# = 30
[2] 3615 pid= 3614,seq# = 15 pid= 3615,seq# = 31
pid= 3615,seq# = 0 pid= 3614,seq# = 16 pid= 3615,seq# = 32
pid= 3615,seq# = 1 pid= 3614,seq# = 17 pid= 3615,seq# = 33
pid= 3615,seq# = 2 pid= 3614,seq# = 18 pid= 3615,seq# = 34
pid= 3615,seq# = 3 pid= 3614,seq# = 19 pid= 3615,seq# = 35
pid= 3615,seq# = 4 pid= 3614,seq# = 20 pid= 3615,seq# = 36
pid= 3615,seq# = 5 pid= 3614,seq# = 21 pid= 3615,seq# = 37
pid= 3615,seq# = 6 pid= 3614,seq# = 22 pid= 3615,seq# = 38
pid= 3615,seq# = 7 pid= 3614,seq# = 23 pid= 3615,seq# = 39
pid= 3614,seq# = 8 pid= 3614,seq# = 24

Pipes: (also called unnamed pipe)-


Pipes are the oldest form of UNIX IPC and are provided by all Unix
systems. You know that a new process is created using the fork ( ) system
call wherein a process spawns a child process. UNIX has provided a means
for the parent process and its child to communicate through the use of
pipes. Pipes are the simplest form of IPC and exist on all UNIX platforms. An
Unnamed pipe is essentially a gateway between a parent process and its
child through which they can exchange data.

The concept of a pipe:-


A pipe is a means of transmitting the output of one program as input
to another program. For example, the output of the sort utility is , by default
, written onto the standard output. However, if you need to extract only a
few lines of this output, then you will need to send this output to the grep
utility, which would extract the required lines and display them. You would
do this by using a pipe.
A pipe is created by calling the pipe function.

#include <unistd.h>

int pipe(int filedes[2]);

Returns: 0 if OK, -1 on error

Two file descriptors are returned through the filedes argument: filedes[0] is
open for reading, and filedes[1] is open for writing. The output of filedes[1]
is the input for filedes[0].

Two ways to picture a half-duplex pipe are as follows


The left half of the figure shows the two ends of the pipe connected in a
single process. The right half of the figure emphasizes that the data in the
pipe flows through the kernel.

A pipe in a single process is useless. Normally, the process that calls pipe
then calls fork, creating an IPC channel from the parent to the child or vice
versa. Following shows this scenario.

Half duplex pipe after a fork

What happens after the fork depends on which direction of data flow we
want. For a pipe from the parent to the child, the parent closes the read end
of the pipe (fd[0]), and the child closes the write end (fd[1]). Following
figure shows the resulting arrangement of descriptors.
Pipe from parent to child

For a pipe from the child to the parent, the parent closes fd[1], and the
child closes fd[0].

When one end of a pipe is closed, the following two rules apply.

1.If we read from a pipe whose write end has been closed, read returns 0 to
indicate an end of file after all the data has been read.
2.If we write to a pipe whose read end has been closed, the signal SIGPIPE
is generated. If we either ignore the signal or catch it and return from the
signal handler, write returns 1 with errno set to EPIPE.

Following is the code to create a pipe between a parent and its child and to
send data down the pipe.

Example to show how to create and use a pipe:


#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
#include <stdlib.h>
int main(void)
{
int fd[2], nbytes;
pid_t childpid;
char string[] = "Hello, world!\n";
char readbuffer[80];
pipe(fd);
if((childpid = fork()) == -1)
{
perror("fork");
exit(1);
}
if(childpid == 0)
{
/* Child process closes up input side of pipe */
close(fd[0]);
/* Send "string" through the output side of pipe */
write(fd[1], string, (strlen(string)+1));
exit(0);
}
else
{
/* Parent process closes up output side of pipe */
close(fd[1]);
/* Read in a string from the pipe */
nbytes = read(fd[0], readbuffer, sizeof(readbuffer));
printf("Received string: %s", readbuffer);
}
return(0);

When a two way flow of data is desired, we must create two pipes and use
one for each direction. The actual steps are

Create pipe1,create pipe2, Fork


Parent closes read end of pipe1
Parent closes write end of pipe2
Child closes write end of pipe1
Child closes read end of pipe2.
This generates the following picture.

Two pipes to provide a bidirectional flow of data

Following is the code to implement two way communication between parent


and child using pipes
/* Implementing a client server program using pipes to convert lower case
into upper case text*/
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
#include <stdlib.h>
int main(void)
{
int p1fd[2],p2fd[2],nbytes;
int childpid,i;
char string[100];

char recvbuff[100],sendbuff[100];
pipe(p1fd);
pipe(p2fd);
if((childpid = fork()) == -1)
{
perror("fork");
exit(1);
}

if(childpid == 0) /*Server*/
{
close(p1fd[1]);
close(p2fd[0]);
server(p1fd[0],p2fd[1]);
}
else /*Client*/
{
close(p2fd[1]);
close(p1fd[0]);
client(p2fd[0],p1fd[1]);

return(0);
}

void server(int readfd, int writefd)


{
char sendbuff[MAXBUFF], recvbuff[MAXBUFF];

while(1)
{
nbytes=read(readfd, recvbuff, sizeof(recvbuff));

for(i=0;i<nbytes;i++)
sendbuff[i]=toupper(recvbuff[i]);

write(writefd, sendbuff, strlen(sendbuff));


}
}

void client(int readfd,int writefd)


{
char sendbuff[MAXBUFF], recvbuff[MAXBUFF];
int nbytes;
printf("Enter lines of text\n ");
while(fgets(string,100,stdin) !=NULL)
{
write(writefd, string, strlen(string)+1);

nbytes = read(readfd, recvbuff, sizeof(recvbuff));


recvbuff[nbytes]='\0';
printf("From child Server : Received string: %s\n", recvbuff);
}
}

Limitations of pipes:
While pipes are widely used in the UNIX environment, unnamed pipes are
the most commonly used form of IPC—they suffer from two major
drawbacks.
1. They are half-duplex in nature—data flows only in one direction at a time.
2. They can be used between directly related processes. i.e the processes
must have a parent-child relationship. A pipe is created by a process that
then calls fork( ), and The pipe is used between parent and child.
FIFOs (Named Pipes):

A FIFO ("First In, First Out") is sometimes known as a named pipe. That is,
it's like a pipe, except that it has a name! In this case, the name is that of a
file that multiple processes can open() and read and write to.

This latter aspect of FIFOs is designed to let them get around one of the
shortcomings of normal pipes: you can't grab one end of a normal pipe that
was created by an unrelated process. See, if I run two individual copies of a
program, they can both call pipe() all they want and still not be able to
speak to one another. (This is because you must pipe(), then fork() to get a
child process that can communicate to the parent via the pipe.) With FIFOs,
though, each unrelated process can simply open() the pipe and transfer data
through it.

A New FIFO is Born

Since the FIFO is actually a file on disk, you have to do some fancy-
schmancy stuff to create it. It's not that hard. You just have to call mknod()
with the proper arguments. Here is a mknod() call that creates a FIFO:

mknod("myfifo", S_IFIFO | 0644 , 0);

In the above example, the FIFO file will be called "myfifo". The second
argument is the creation mode, which is used to tell mknod() to make a
FIFO (the S_IFIFO part of the OR) and sets access permissions to that file
(octal 644, or rw-r--r--) which can also be set by ORing together macros
from sys/stat.h. This permission is just like the one you'd set using the
chmod command. Finally, a device number is passed. This is ignored when
creating a FIFO, so you can put anything you want in there.

(An aside: a FIFO can also be created from the command line using the Unix
mknod command.)

Producers and Consumers

Once the FIFO has been created, a process can start up and open it for
reading or writing using the standard open() system call.

The following are two programs which will send data through a FIFO. One is
speak.c which sends data through the FIFO, and the other is called tick.c, as
it sucks data out of the FIFO.
Here is speak.c:

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#define FIFO_NAME "american_maid"
int main(void)
{
char s[300];
int num, fd;

mknod(FIFO_NAME, S_IFIFO | 0666, 0);


printf("waiting for readers...\n");
fd = open(FIFO_NAME, O_WRONLY);
printf("got a reader--type some stuff\n");
while (gets(s), !feof(stdin)) {
if ((num = write(fd, s, strlen(s))) == -1)
perror("write");
else
printf("speak: wrote %d bytes\n", num);
}
return 0;
}

What speak does is create the FIFO, then try to open() it. Now, what will
happen is that the open() call will block until some other process opens the
other end of the pipe for reading. (There is a way around this—see
O_NDELAY, below.) That process is tick.c, shown here:

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#define FIFO_NAME "american_maid"
int main(void)
{
char s[300];
int num, fd;
mknod(FIFO_NAME, S_IFIFO | 0666, 0);
printf("waiting for writers...\n");
fd = open(FIFO_NAME, O_RDONLY);
printf("got a writer\n");
do {
if ((num = read(fd, s, 300)) == -1)
perror("read");
else {
s[num] = '\0';
printf("tick: read %d bytes: \"%s\"\n", num, s);
}
} while (num > 0);
return 0;
}
Like speak.c, tick will block on the open() if there is no one writing to the
FIFO. As soon as someone opens the FIFO for writing, tick will spring to life.
Two way communication using named pipes

Client server example using two FIFOs.


Following is another program to transfer file contents using two named
pipes.
/*Implement the file transfer using the named pipes(FIFOs).*/
/*Server Program*/ /*Client Program*/
#include<unistd.h> #include<unistd.h>
#include<stdio.h> #include<stdio.h>
#include<sys/stat.h> #include<stdlib.h>
#include<stdlib.h> #include<sys/stat.h>
#include<string.h> #include<string.h>
#define MAXBUFF 1024 #define MAXBUFF 1024
#define FIFO1 "/tmp/fifo.1" #define FIFO1 "/tmp/fifo.1"
#define FIFO2 "/tmp/fifo.2" #define FIFO2 "/tmp/fifo.2"
int main() int main()
{ {
void server(int,int); void client(int,int);
int readfd,writefd; int readfd,writefd;
mknod(FIFO1,S_IFIFO|0666,0); writefd=open(FIFO1,1);
mknod(FIFO2,S_IFIFO|0666,0); readfd=open(FIFO2,0);
readfd=open(FIFO1,0); client(readfd,writefd);
writefd=open(FIFO2,1); close(readfd);
server(readfd,writefd); close(writefd);
close(readfd); exit(0);
close(writefd); }
exit(0);
} void client(int readfd,int writefd)
void server(int readfd,int writefd) {
{ char buff[1024];
char buff[MAXBUFF]; int n;
int n,fd; printf("\nEnter file name:");
n=read(readfd,buff,MAXBUFF); fgets(buff,MAXBUFF,stdin);
buff[n]='\0'; n=strlen(buff);
fd=open(buff,0); n--;
while((n=read(fd,buff,MAXBUFF))>0) write(writefd,buff,n);
write(writefd,buff,n); while((n=read(readfd,buff,MAXBUFF))>0)
} write(1,buff,n);
}

O_NDELAY! I'm UNSTOPPABLE!

By Default the open() system call is blocking call if there is no


corresponding reader or writer. The way to do this is to call open() with the
O_NDELAY flag set in the mode argument:

fd = open(FIFO_NAME, O_RDONLY | O_NDELAY);


The effect of this flag is shown in the following figure.

Condition Normal O_NDELAY set


Open FIFO, read-only Wait until a process Return immediately, no
with no process having opens the FIFO for error
the FIFO open for writing
writing
Open FIFO, write-only Wait until a process Return an error
with no process having opens the FIFO for immediately, errno set
the FIFO open for reading to ENXIO
reading
Read pipe or FIFO, no Wait until there is data Return immediately,
data in the pipe or FIFO, or return value of zero
until no process have it
open for writing
Write, pipe or FIFO is Wait until space is Return immediately,
full available, then write return value of zero
data

A pipe or FIFO follows these rules for reading and writing:

1. A read requesting less than is in the pipe or FIFO returns only the
requested amount of data.
2. If a process asks to read more data than currently available in the pipe
or FIFO only the data available is returned.
3. If there is no data in the pipe or FIFO, and if no processes have it open
for writing, a read returns zero signifying the end of file.
4. If a process writes less than the capacity of a pipe of FIFO the write is
guaranteed to be atomic.
5. If the process writes to a pipe or FIFO, but there are no processes in
existence that have it open for reading, the SIGPIPE signal is
generated, and write returns zero with errno set to EPIPE.

Streams and Messages

The pipes and FIFOs, uses the stream I/O model, which is natural for
Unix. No record boundaries exist- reads and writes do not examine the data
at all. A process that reads 100 bytes from a pipe, for example, cannot tell
whether the process that wrote the data into the pipe did a single write of
100 bytes, five writes of 20 bytes, two writes of 50 bytes, or some other
combination of writes that totals 100 bytes. The data is a byte stream with
no interpretation done by the system. If any interpretation is desired, the
writing process and the reading process must agree to it a priori and do it
themselves.

Sometimes an application wants to impose some structure on the data


being transferred. This can happen when the data consists of variable-length
messages and the reader must know where the message boundaries are so
that it knows when a single message has been read. The following three
techniques are commonly used for this:
1. Special termination sequence in-band: many Unix applications use the
newline character to delineate each message. The writing process appends a
newline to each message, and the reading process reads one line at a time.
2. Explicit length: each record is preceded by its length. One advantage to
this technique is that escaping a delimiter that appears in the data is
unnecessary, because the receiver does not need to scan all the data,
looking for the end of each record.
3. One record per connection: the application closes the connection to its
peer (its TCP connection, in the case of a network application, or its IPC
connection) to indicate the end of a record.

More structured messages can also be built, and this capability is


provided by System V message queues. Each message has a length and a
type. The length and type are specified by the sender, and after the
message is read, both are returned to the reader. Each message is a record.
We can also add more structure to either a pipe or FIFO ourselves.

Name Spaces:

Pipes do not have names but FIFOs have a unix path name to identify
them. The set of possible names for a given type of IPC is called its name
space. The name space is important because for all forms of IPC other than
plain pipes, the name is how the client and server “connect to exchange
messages.
Following table summarizes the naming conventions used by different
forms of IPC.

IPC Type Name Space Identification


Pipe (noname) File descriptor
FIFO Pathname File descriptor
Message Queue key_t key Identifier
Share memory key_t key Identifier
Semaphore key_t key Identifier
Socket Unix Domain path name File descriptor
Socket Other domains (domain dependent) File descriptor
key_t Keys

The function ftok is provided by the System V standard C library to


convert a path name and project identifier into a system V IPC key. These
system V IPC keys are used to identify message queues, shared memory
and semaphores.

syntax of ftok function

#include <sys/types.h>
#include <sys/ipc.h>
key_k ftok(char *pathname, char proj);

An IPC application should have the both server and clients all agree on a
single path name that have some meaning to the application.
If the path name does not exists ftok returns -1
proj argument is an 8- bit character.

SYSTEM V IPC

The three types of IPC,


• message queues ,
• semaphores , and
• shared memory
are collectively referred to as "System V IPC". This term is commonly used
for these three IPC facilities, acknowledging their heritage from System V
Unix. They share many similarities in the functions that access them, and in
the information that the kernel maintains on them.

A summary of their functions is shown in foll.fig.


ipc_perm Structure:

The kernel maintains a structure of information for each IPC object, similar
to the information it maintains for files.

Message Queues

A message queue is a linked list of messages stored within the kernel.


System V message queues are identified by a message queue identifier. Any
process with adequate privileges can place a message onto a given queue,
and any process with adequate privileges can read a message from a given
queue. every message on a queue has the following attributes:
long integer type
length of the data portion of the message
data
For every message queue in the system, the kernel maintains the
following structure of information, defined by including <sys/msg. h>:

The ipc_perm structure contains the access permissions for this


particular message queue. the msg structures are the internal data
structures used by the kernel to maintain the linked list of messages on a
particular queue.
We can picture a particular message queue in the kernel as a linked
list of messages, as shown in following figure. Assume that three messages
are on a queue, with lengths of 1 byte, 2 bytes, and 3 bytes, and that the
messages were written in that order. Also assume that these three
messages were written with types of 100, 200, and 300, respectively.

msgget Function

A new message queue is created, or an existing message queue is


accessed with the msgget system call.

The return value is an integer identifier that is used in the other three
msg functions to refer to this queue, based on the specified key, which can
be a value returned by ftok or the constant IPC_PRIVATE. oflag is a
combination of the read-write permission values.

Once a message queue is opened by msgget, we put a message onto the


queue using msgsnd system call
msqid is an identifier returned by msgget. ptr is a pointer to a structure with
the following template, which is defined in <sys /msg.h>,

struct msgbuff
{
long mtype; /*message type , must be greater than zero*/
int len; /*actual length of data*/
char mtext[1]; /*Message data*/
}

The message type must be greater than 0, since nonpositive message types
are used as a special indicator to the msgrcv function.

The length argument to msgsnd specifies the length of the message in


bytes.
The flag argument can be specified as either IPC_NOWAIT or as zero. The
IPC_NOWAIT value allows the system call to return immediately if there is
no room on the specified queue for the new message. In this case msgsnd
returns -1. On success it will return 0.

msgrcv Function

A message is read from a message queue using the msgrcv function.

The ptr argument specifies where the received message is to be


stored. length specifies the size of the data portion of the buffer pointed to
by ptr. This is the maximum amount of data that is returned by the function.

type specifies which message on the queue is desired:


• If type == 0, the first message on the queue is returned. Since each
message queue is maintained as a FIFO list (first-in, first-out), a type of 0
specifies that the oldest message on the queue is to be returned.
• If type > 0, the first message whose type equals type is
returned.
• If type < 0, the first message with the lowest type that is less
than or equal to the absolute value of the type argument is returned.
The flag argument specifies what to do if a message of the requested
type is not on the queue. If the IPC_NOWAIT bit is set and no message is
available, the msqrcv function returns immediately with an error of
ENOMSG. otherwise, the caller is blocked until one of the following occurs:

1. a message of the requested type is available,


2. the message queue identified by msqid is removed from the system
3. the process receives a signal that is caught.

On successful return, msgrcv returns the number of bytes of data in


the received message. This does not include the bytes needed for the long
integer message type that is also returned through the ptr argument.

msgctl Function

The msgctl function provides a variety of control operations on a message


queue.

Three commands are provided:


IPC_RMID Remove the message queue specified by msqid from the sys
tem.

IPC_SET changes the properties of the queue such as queue size etc.

IPC_STAT gets the queue status information.

Multiplexing Messages

Two features are provided by the type field that is associated with each
message on a queue:
1.The type field can be used to identify the messages, allowing multiple
processes to multiplex messages onto a single queue. One value of the
type field is used for messages from the clients to the server, and a
different value that is unique for each client is used for messages from
the server to the clients. Naturally, the process ID of the client can be
used as the type field that is unique for each client.

2.The type field can be used as a priority field. This lets the receiver read
the messages in an order other than first-in, first-out (FIFO). With pipes
and FIFOs, the data must be read in the order in which it was written.
With System V message queues, we can read the messages in any order
that is consistent with the values we associate with the message types.

Write an application program to illustrate the message queues.

/* messageq sender program*/


#include<string.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
struct msgbuf{
long mtype;
char mtext[1];
};
int main(int argc, char *argv[])
{
int queue_id;

struct msgbuf *msg;


int i;
queue_id=msgget((key_t)999,IPC_CREAT|IPC_EXCL|0600);
printf("Message queue created, queue id '%d'.\n",queue_id);
msg=(struct msgbuf*)malloc(sizeof(struct msgbuf)+200);

for(i=1;i<=20;i++)
{
msg->mtype=(i%3)+1;
sprintf(msg->mtext,"hello world-%d",i);
msgsnd(queue_id,msg,strlen(msg->mtext)+1,0);
}
free(msg);
printf("generated 20 messages");
return 0;
}
/*messageq reader program */
#include<string.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
struct msgbuf{
long mtype;
char mtext[1];
};
int main(int argc, char *argv[])
{
int queue_id;
struct msgbuf *msg;
int msg_type;
if(argc !=2)
{
printf("Usage: %s <message type>\n",argv[0]);
exit(0);
}
msg_type=atoi(argv[1]);
if(msg_type <1 || msg_type >3)
{
printf("Message type must be between 1 and 3\n");
exit(0);
}
queue_id=msgget((key_t)999,0);
printf("Message queue opened, queue id '%d'.\n",queue_id);
msg=(struct msgbuf*)malloc(sizeof(struct msgbuf)+200);
while(1)
{
msgrcv(queue_id,msg,200,msg_type,0);
printf("Reader '%d' read message: '%s'\n",msg_type,msg->mtext);
sleep(1);
}
return 0;
}
Semaphores

A semaphore is not a form of IPC similar to the pipes, FIFOs, message


queues. A semaphore is a counter used to provide access to a shared data
object for multiple processes.

To obtain a shared resource, a process needs to do the following:

1. Test the semaphore that controls the resource.


2. If the value of the semaphore is positive, the process can use the
resource. In this case, the process decrements the semaphore value
by 1, indicating that it has used one unit of the resource.
3. Otherwise, if the value of the semaphore is 0, the process goes to
sleep until the semaphore value is greater than 0. When the process
wakes up, it returns to step 1.

When a process is done with a shared resource that is controlled by a


semaphore, the semaphore value is incremented by 1. If any other
processes are asleep, waiting for the semaphore, they are awakened.

To implement semaphores correctly, the test of a semaphore's value and


the decrementing of this value must be an atomic operation. For this reason,
semaphores are normally implemented inside the kernel.

A common form of semaphore is called a binary semaphore. It controls a


single resource, and its value is initialized to 1. In general, however, a
semaphore can be initialized to any positive value, with the value indicating
how many units of the shared resource are available for sharing.

For every set of semaphores in the system, the kernel maintains the
following structure of information, defined by including <sys/sem.h >:

The ipc_perm structure contains the access permissions for this


particular semaphore. The sem structure is the internal data structure used
by the kernel to maintain the set of values for a given semaphore. Every
member of a semaphore set is described by the following structure:

We can picture a particular semaphore in the kernel as being a


semid_ds structure that points to an array of sem structures . If the
semaphore has two members in its set, we would have the following picture.
In this figure, the variable sem_ nsems has a value of two, and we have
denoted each member of the set with the subscripts [0] and [1].

semget Function

The semget function creates a semaphore set or accesses an existing


semaphore set.

#include <sys/sem.h>

int semget(key_t key, int nsems, int flag);

Returns: semaphore ID if OK, -1 on error

The return value is an integer called the semaphore identifier that is used
with the semop and semctl functions.
The nsems argument specifies the number of semaphores in the set. If
we are not creating a new semaphore set but just accessing an existing set,
we can specify this argument as 0. We cannot change the number of
semaphores in a set once it is created.
The oflag value is a combination of the permissions. This can be bitwise-
ORed with either IPC_CREAT or IPC_EXCL.

semop Function

Once a semaphore set is opened with semget, operations are


performed on one or more of the semaphores in the set using the semop
function.
#include <sys/sem.h>
int semop(int semid, struct sembuf *opsptr, size_t nops);
Returns: 0 if OK, -1 on error

opsptr points to an array of the following structures:

The number of elements in the array of sembuf structures pointed to by


opsptr is specified by the nops argument. Each element in this array
specifies an operation for one particular semaphore value in the set. The
particular semaphore value is specified by the semnum value, which is 0 for
the first element, one for the second, and so on, up to nsems- 1 , where
nsems is the number of semaphore values in the set (the second argument
in the call to semget when the semaphore set was created ). The array of
operations passed to the semop function are guaranteed to be performed
atomically by the kernel. The kernel either does all the operations that are
specified, or it does none of them.
sem_op : specifies the operation on the semaphore and its meaning is
as follows.
sem_op What happens
Positive The value of the sem_op is added to the semaphore’s value.This
is how a program uses a semaphore to mark a resource as
allocated
Negative If the absolute value of sem_op is greater than the value of the
semaphore,the calling process will block until the value of the
semaphore reaches that of the absolute value of sem_op. Finally,
the absolute value of sem_op will be substracted from the
semaphore’s value. This is how a process releases a resource
guarded by the semaphore.
Zero This process will wait until the semaphore in question reaches 0.
semctl function
The semctl function is the catchall for various semaphore operations.

#include <sys/sem.h>

int semctl(int semid, int semnum, int cmd, union semun arg);

Returns: (see following)

The fourth argument is optional, depending on the command requested, and


if present, is of type semun, a union of various command-specific arguments:

union semun {
int val; /* for SETVAL */
struct semid_ds *buf; /* for IPC_STAT and IPC_SET */
unsigned short *array; /* for GETALL and SETALL */
};

The cmd argument specifies one of the following ten commands to be


performed on the set specified by semid. The five commands that refer to
one particular semaphore value use semnum to specify one member of the
set. The value of semnum is between 0 and nsems-1, inclusive.

IPC_STAT Fetch the semid_ds structure for this set, storing it in the
structure pointed to by arg.buf.
IPC_SET Set the sem_perm.uid, sem_perm.gid, and sem_perm.mode fields
from the structure pointed to by arg.buf in the semid_ds structure
associated with this set.
IPC_RMID Remove the semaphore set from the system. This removal is
immediate. Any other process still using the semaphore will get
an error of EIDRM on its next attempted operation on the
semaphore.
GETVAL Return the value of semval for the member semnum.
SETVAL Set the value of semval for the member semnum. The value is
specified by arg.val.
GETPID Return the value of sempid for the member semnum.
IPC_STAT Fetch the semid_ds structure for this set, storing it in the
structure pointed to by arg.buf.
GETNCNT Return the value of semncnt for the member semnum.
GETZCNT Return the value of semzcnt for the member semnum.
GETALL Fetch all the semaphore values in the set. These values are stored
in the array pointed to by arg.array.
SETALL Set all the semaphore values in the set to the values pointed to
by arg.array.

For all the GET commands other than GETALL, the function returns the
corresponding value. For the remaining commands, the return value is 0.

Write a program using semaphores to implement file locking

#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/sem.h>
#include<stdio.h>
#include<fcntl.h>
#include<unistd.h>
#include<string.h>
#define SEQFILE "seqno.c"
#define MAXBUFF 500
static struct sembuf op_lock[2]={{0,0,0},{0,1,0}};
static struct sembuf op_unlock[1]={0,-1,IPC_NOWAIT};
int semid=-1;
main()
{
void my_lock(int);
void my_unlock(int);
int fd,i,n,pid,seqno=0;
char buff[MAXBUFF];
pid=getpid();
fd=open(SEQFILE,O_CREAT|O_RDWR);
for(i=1; i<=20; i++)
{
my_lock(fd);
lseek(fd,0L,0);
n=read(fd,buff,MAXBUFF);
buff[n]='\0';
n=sscanf(buff,"%d",&seqno);
printf("pid= %d,seq# = %d\n",pid,seqno);
seqno++;
sprintf(buff,"%d",seqno);
n=strlen(buff);
lseek(fd,0L,0);
write(fd,buff,n);
my_unlock(fd);
}
}
void my_lock(int fd)
{
if(semid<0)
{
if((semid=semget((key_t)12345,1,IPC_CREAT|0666))<0)
perror("semget error");
}
if(semop(semid,&op_lock[0],2)<0)
perror("semop lock error");
}
void my_unlock(int fd)
{
if(semop(semid,&op_unlock[0],1)<0)
perror("semop unlock error");
}
[root@rgmcse ~]# gcc locksem.c
[root@rgmcse ~]# ./a.out& ./a.out&
[1] 3239
[2] 3240
pid= 3239,seq# = 14 pid= 3240,seq# = 29
pid= 3240,seq# = 0 pid= 3240,seq# = 15 pid= 3239,seq# = 30
pid= 3240,seq# = 1 pid= 3239,seq# = 16 pid= 3240,seq# = 31
pid= 3240,seq# = 2 pid= 3240,seq# = 17 pid= 3239,seq# = 32
pid= 3240,seq# = 3 pid= 3239,seq# = 18 pid= 3239,seq# = 33
pid= 3240,seq# = 4 pid= 3240,seq# = 19 pid= 3239,seq# = 34
pid= 3240,seq# = 5 pid= 3239,seq# = 20 pid= 3239,seq# = 35
pid= 3240,seq# = 6 pid= 3240,seq# = 21 pid= 3239,seq# = 36
pid=3240,seq# =7 pid= 3239,seq# = 22 pid= 3239,seq# = 37
pid= 3239,seq# = 8 pid= 3240,seq# = 23 pid= 3239,seq# = 38
pid= 3240,seq# = 9 pid= 3239,seq# = 24 pid= 3239,seq# = 39
pid= 3239,seq# = 10 pid= 3240,seq# = 25
pid= 3240,seq# = 11 pid= 3239,seq# = 26
pid= 3239,seq# = 12 pid= 3240,seq# = 27
pid= 3240,seq# = 13 pid= 3239,seq# = 28

Das könnte Ihnen auch gefallen