Unit_IV_LP
Unit_IV_LP
Introduction to IPC
Interprocess communication (IPC) includes thread synchorization and data exchange between
threads beyond the process boundaries. If threads belong to the same process, they execute in the
same address space, i.e. they can access global (static) data or heap directly, without the help of
the operating system. However, if threads belong to different processes, they cannot access each
others address spaces without the help of the operating system.
The first case is easier to implement because processes can share memory either in the user space
or in the system space. This is equally true for uniprocessors and multiprocessors.
In the second case the computers do not share physical memory, they are connected via I/O
device(for example serial communication or Ethernet). Therefore the processes residing in
different computers can not use memory as a means for communication.
Most of this chapter is focused on IPC on a single computer system, including four general
approaches:
Shared memory
Messages
Pipes
Sockets
The synchronization objects considered in the previous chapter normally work across the process
boundaries (on a single computer system). There is one addition necessary however: the
synchronization objects must be named. The handles are generally private to the process, while
the object names, like file names, are global and known to all processes.
h = init_CS("xxx");
h = init_semaphore(20,"xxx");
h = init_event("xxx");
h = init_condition("xxx");
h = init_message_buffer(100,"xxx");
IPC between processes on different systems
IPC is Inter Process Communication, more of a technique to share data across different processes
within one machine, in such a way that data passing binds the coupling of different processes.
The first, is using memory mapping techniques, where a memory map is created, and others
162
open the memory map for reading/writing...
The second is, using sockets, to communicate with one another...this has a high overhead, as
each process would have to open up the socket, communicate across... although effective
The third, is to use a pipe or a named pipe, a very good example
PIPES:
A pipe is a serial communication device (i.e., the data is read in the order in which it was
written),which allows a unidirectional communication. The data written to end
isreadbackfromtheotherend.
The pipe is mainly used to communicate between two threads in a single process or between
parent and child process. Pipes can only connect the related process. In shell,
thesymbolcanbeusedtocreateapipe.
In pipes the capacity of data is limited. (i.e.) If the writing process is faster than the reading
process which consumes the data, the pipe cannot store the data. In this situation the writer
process will block until more capacity becomes available. Also if the reading process tries to
read data when there is no data to read, it will be blocked until the data becomes available. By
this, pipes automatically synchronize the two process.
Creatingpipes:
The pipe() function provides a means of passing data between two programs and also allows to
read and write the data.
#include<unistd.h>
int pipe(int file_descriptor[2]);
pipe()function is passed with an array of file descriptors. It will fill the array with new file
descriptors and returns zero. On error, returns -1 and sets the errno to indicate the reason of
failure.
The file descriptors are connected in a way that is data written to file_ descriptor[1] can be read
back from the file_descriptor[0].
(Note: As this uses file descriptors and not the file streams, we must use read and write system
calls to access the data.)
Pipes are originally used in UNIX and are made even more powerful in Windows 95/NT/2000.
Pipes are implemented in file system. Pipes are basically files with only two file offsets: one for
reading another for writing. Writing to a pipe and reading from a pipe is strictly in FIFO manner.
(Therefore pipes are also called FIFOs).
For efficiency, pipes are in-core files, i.e. they reside in memory instead on disk, as any other
global data structure. Therefore pipes must be restricted in size, i.e. number of pipe blocks must
be limited. (In UNIX the limitation is that pipes use only direct blocks.)Since the pipes have a
limited size and the FIFO access discipline, the reading and writing processes are synchronized
in a similar manner as in case of message buffers. The access functions for pipes are the same as
for files: WriteFile() and ReadFile().
163
Pipes used as standard input and output:
We can invoke the standard programs, ones that don‘t expect a file descriptor as a parameter.
#include<unistd.h>
int dup(int file_descriptor);
int dup2(int file_descriptor_1,
int file_descriptor_2);
The purpose of dup call is to open a new file descriptor, which will refer to the same file as an
existing file descriptor. In case of dup, the value of the new file descriptor is the lowest number
available. In dup2 it is same as, or the first available descriptor greater than the parameter
file_descriptor_2.
We can pass data between process by first closing the file descriptor 0 and call is made to dup.
By this the new file descriptor will have the number 0.As the new descriptor is the duplicate of
an existing one, standard input is changed to have the access. So we have created two file
descriptors for same file or pipe, one of them will be the standard input.
(Note: The same operation can be performed by using the fcntl() function. But compared to this
dup and dup2 are more efficient)
//pipes.c
#include<unistd.h>
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
int main()
{
int data_processed;
int file_pipes[2];
const char some_data[]= "123";
pid_t fork_result;
if(pipe(file_pipes)==0)
{
fork_result=fork();
if(fork_result==(pid_t)-1)
{
fprintf(stderr,"fork failure");
exit(EXIT_FAILURE);
}
if(fork_result==(pid_t)0)
{
close(0);
dup(file_pipes[0]);
close(file_pipes[0]);
164
close(file_pipes[1]);
execlp("od","od","-c",(char *)0);
exit(EXIT_FAILURE);
}
else
{
close(file_pipes[0]);
data_processed=write(file_pipes[1],
some_data,strlen(some_data)); close(file_pipes[1]);
printf("%d -wrote %d bytes\n",(int)getpid(),data_processed);
}
} exit(EXIT_SUCCESS);
}
The program creates a pipe and then forks. Now both parent and child process will have its own
file descriptors for reading and writing. Therefore totally there are four file descriptors.
The child process will close its standard input with close(0) and calls duo(file_pipes[0]). This
will duplicate the file descriptor associated with the read end. Then child closes its original file
descriptor. As child will never write, it also closes the write file descriptor,
file_pipes[1]. Now there is only one file descriptor 0 associated with the pipe that is standard
input. Next, child uses the exec to invoke any program that reads standard input.
The od command will wait for the data to be available from the user terminal.
Since the parent never read the pipe, it starts by closing the read end that is file_pipe[0]. When
writing process of data has been finished, the write end of the parent is closed and exited. As there
are no file descriptor open to write to pipe, the od command will be able to read the three bytes
written to pipe, meanwhile the reading process will return 0 bytes indicating the end of the file.
Namedpipes.
Unnamed pipes (Anonymous pipes)
FIFO creation:
int mkfifo ( const char *pathname, mode_t mode );
- makes a FIFO special file with name pathname.
(mode specifies the FIFO's permissions, as common in UNIX-like file systems).
- A FIFO special file is similar to a pipe, except that it is created in a different way. Instead of being an
anonymous communications channel, a FIFO special file is entered into the file system by calling mkfifo()
Once a FIFO special file has been created, any process can open it for reading or writing, in the
same way as an ordinary file.
165
A First-in, first-out(FIFO) file is a pipe that has a name in the filesystem. It is also called as
med pipes.
Creation of FIFO:
We can create a FIFO from the command line and within a program.
To create from command line we can use either mknod or mkfifo commands.
$ mknod filename p
$ mkfifo filename
(Note: The mknod command is available only n older versions, you can make use of mkfifo in
new versions.)
To create FIFO within the program we can use two system calls. They are,
#include<sys/types.h>
#include<sys/stat.h>
If we want to use the mknod function we have to use ORing process of fileaccess mode with
S_IFIFO and the dev_t value of 0.Instead of using this we can use the simple mkfifo function.
Accessing FIFO:
Let us first discuss how to access FIFO in command line using file commmands. The useful
feature of named pipes is, as they appear in the file system, we can use them in commands.
FIFO can also be accessed as like a file in the program using low-level I/O functions or C library
I/O functions.
The only difference between opening a regular file and FIFO is the use of open_flag with the
optionO_NONBLOCK. The only restriction is that we can‘t open FIFO for reading and writing
with O_RDWR mode.
//fifo1.c
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <limits.h>
166
#include <sys/types.h>
#include <sys/stat.h>
int main()
{
int pipe_fd;
int res;
int open_mode = O_WRONLY;
int bytes_sent = 0;
char buffer[BUFFER_SIZE + 1];
if (access(FIFO_NAME, F_OK) == -1) {
res = mkfifo(FIFO_NAME, 0777);
if (res != 0) {
fprintf(stderr, "Could not create fifo %s\n", FIFO_NAME);
exit(EXIT_FAILURE);
}
}
printf("Process %d opening FIFO O_WRONLY\n", getpid());
pipe_fd = open(FIFO_NAME, open_mode);
printf("Process %d result %d\n", getpid(), pipe_fd); if
(pipe_fd != -1) {
while(bytes_sent < TEN_MEG) {
res = write(pipe_fd, buffer, BUFFER_SIZE); if
(res == -1) {
fprintf(stderr, "Write error on pipe\n");
exit(EXIT_FAILURE);
}
}
(void)close(pipe_fd);
}
else { exit(EXIT_FAILURE);
}
printf("Process %d finished\n", getpid());
exit(EXIT_SUCCESS);
}
//fifo2.c
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
#define FIFO_NAME "/tmp/my_fifo"
#define BUFFER_SIZE PIPE_BUF int
167
main()
{
int pipe_fd; int
res;
int open_mode = O_RDONLY; char
buffer[BUFFER_SIZE + 1]; int
bytes_read = 0;
memset(buffer, '\0', sizeof(buffer));
printf("Process %d opening FIFO O_RDONLY\n", getpid());
pipe_fd = open(FIFO_NAME, open_mode);
printf("Process %d result %d\n", getpid(), pipe_fd);
if (pipe_fd != -1) {
do {
res = read(pipe_fd, buffer, BUFFER_SIZE);
bytes_read += res;
} while (res > 0);
(void)close(pipe_fd);
}
else {
exit(EXIT_FAILURE);
}
printf("Process %d finished, %d bytes read\n", getpid(), bytes_read);
exit(EXIT_SUCCESS);
}
Both fifo1.c and fifo2.c programs use the FIFO in blocking mode.
First fifo1.c is executed .It blocks and waits for reader to open the named pipe. Now writer
unblocks and starts writing data to pipe. At the same time, the reader starts reading data from the
pipe.
Anonymous pipes don't have names, therefore they can be used only between related processes
which can inherit the file handles (file descriptors).
Anonymous pipes are typically used to "pipe" two programs: standard output from one program
is redirected to the pipe input (write handle), while the standard input of the second program is
redirected to from the pipe output (read handle). The pipe is created by the parent (usually the
login shell), and the pipe handles are passed to children through the inheritance mechanism.
Anonymous pipes cannot be used across a network. Also, anonymous pipes are unidirectional- in
order to communicate two related processes in both directions, two anonymous pipes must be
created.
//*******************************************************************
// This program implements piping of programs p1.exe and p2.exe
// through an anonymous pipe. The program creates two child processes
// (which execute images p1.exe and p2.exe) and a pipe, then passes
// the pipe handles to the children.
168
//
// The program is invoked as: pipe p1 p2 (no command line arguments)
//*******************************************************************
#include <windows.h>
#include <iostream.h>
int main(int argc, char *argv[])
{
// Create anonymous (unnamed) pipe
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.lpSecurityDescriptor = 0;
sa.bInheritHandle = TRUE; // Handles are inheritable (default is FALSE)
HANDLE rh,wh; // Read and write handles of the pipe
if(!CreatePipe(&rh,&wh,&sa,0))
{
cout << "Couldn't create pipe " << GetLastError()<< endl;
return (1);
}
// Create the first child process p1
PROCESS_INFORMATION pi1;
STARTUPINFO si1;
GetStartupInfo(&si1); // Get default startup structure
si1.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
si1.hStdOutput = wh; // Std output of p1 is input to the pipe
si1.dwFlags = STARTF_USESTDHANDLES;
CreateProcess( argv[1], // Name of the p1's image (without ".exe."
0,0,0,
TRUE, // Each open inheritable handle of the
// parent will be inherited by the child
0,0,0,
&si1,&pi1);
CloseHandle(wh); // Pipe handle no longer needed
// Create the second child process p2
PROCESS_INFORMATION pi2;
STARTUPINFO si2;
GetStartupInfo(&si2); // Get default startup structure
si2.hStdInput = rh; // Std input of p2 is otput from the pipe
si2.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
si2.dwFlags = STARTF_USESTDHANDLES;
CreateProcess( 0,argv[2], // Name of the p1's image (without ".exe."
0,0,
TRUE, // Each open inheritable handle of the
// parent will be inherited by the child
0,0,0,
&si2,&pi2);
WaitForSingleObject(pi1.hProcess,INFINITE);
CloseHandle(pi1.hProcess);
WaitForSingleObject(pi2.hProcess,INFINITE);
CloseHandle(pi2.hProcess);
CloseHandle(rh);
169
return(0);
}
Comment:
In order to communicate two processes (P1 and P2) through anonymous pipes by redirecting the
standard I/O, the processes don't have to be aware of the existence of pipes, i.e. their sources and
images don't have to be modified.
The process of passing data between two programs can be done with the help of popen() and
pclose() functions.
#include<stdio.h>
FILE *popen(const char *command ,
const char *open-mode);
int pclose(FILE *stream_to_close);
popen():
The popen function allows a program to invoke another program as a new process and either
write the data to it or to read from it. The parameter command is the name of the program to run.
The open_mode parameter specifies in which mode it is to be invoked, it can be only either "r" or
"w". On failure popen() returns a NULL pointer. If you want to perform bi-directional
communication you have to use two pipes.
pclose():
By using pclose(), we can close the filestream associated with popen() after the process started
by it has been finished. The pclose() will return the exit code of the process, which is to be
closed. If the process was already executed a wait statement before calling pclose, the exit status
will be lost because the process has been finished. After closing the filestream, pclose() will wait
for the child process to terminate.
Messagequeue:
This is an easy way of passing message between two process. It provides a way of
sending a block of data from one process to another. The main advantage of using this is, each
block of data is considered to have a type, and a receiving process receives the blocks of data
having different type values independently.
The first parameter is the key value, which specifies the particular message queue. The special
constant IPC_PRIVATE will create a private queue. But on some Linux systems the message
queue may not actually be private.
The second parameter is the flag value, which takes nine permission flags.
170
Adding a message:
The msgsnd() function allows to add a message to a message queue.
#include<sys/msg.h>
int msgsnd(int msqid,const void *msg_ptr ,size_t msg_sz,int msgflg);
The first parameter is the message queue identifier returned from an msgget function.
The second parameter is the pointer to the message to be sent. The third parameter is the size of
the message pointed to by msg_ptr. The fourth parameter, is the flag value controls what happens
if either the current message queue is full or within the limit. On success, the function returns 0
and a copy of the message data has been taken and placed on the message queue, on failure -1 is
returned.
Retrieving a message:
The smirch() function retrieves message from the message queue.
#include<sys/msg.h>
int msgsnd(int msqid,const void *msg_ptr
,size_t msg_sz,long int msgtype ,int msgflg);
The fourth parameter allows a simple form of reception priority. If its value is 0,the first
available message in the queue is retreived. If it is greater than 0,the first message type is
retrived. If it is less than 0,the first message that has a type the same a or less than the absolute
value of msgtype is retrieved.
On success, msgrcv returns the number on bytes placed in the receive buffer, the message is
copied into the user-allocated buffer and the data is deleted from the message queue. It returns -1
on error.
#include<sys/msg.h>
int msgctl(int msgid,int command,
struct msqid_ds *buf);
1.) IPC_STAT - Sets the data in the msqid_ds to reflect the values associated with the message
queue.
2.) IPC_SET - If the process has the permission to do so, this sets the values associated with the
message queue to those provided in the msgid_ds data structure.
(Note: If the message queue is deleted while the process is writing in a msgsnd or msgrcv
function, the send or receive function will fail.
171
Client /server Example:
//msgq1.c
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
#include<errno.h>
#include<unistd.h>
#include<sys/msg.h>
struct my_msg_st
{
long int my_msg_type;
char some_text[BUFSIZ];
};
int main()
{
int running = 1;
int msgid;
struct my_msg_st some_data;
long int msg_to_receive = 0;
{
fprintf(stderr, "failedto receive: \n");
exit(EXIT_FAILURE);
}
}
}
if (msgctl(msgid, IPC_RMID, 0) == -1)
{
fprintf(stderr, "failed to delete\n");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}
//msgq2.c
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/msg.h>
#define MAX_TEXT 512
struct my_msg_st
{
long int my_msg_type;
char some_text[MAX_TEXT];
};
int main()
{
int running = 1;
struct my_msg_st some_data;
int msgid;
char buffer[BUFSIZ];
The msgq1.c program will create the message queue using msgget() function.
The msgid identifier is returned by the msgget().The message are received from the queue
using msgrcv() function until the string "end" is encountered. Then the queue is
deletedusing msgctl() function.
The msgq2.c program uses the msgsnd() function to send the entered text to the queue.
Semaphore:
While we are using threads in our programs in multi-user systems, multiprocessing system, or a
combination of two, we may often discover critical sections in the code. This is the section where
we have to ensure that a single process has exclusive access totheresource.
For this purpose the semaphore is used. It allows in managing the access to resource.
To prevent the problem of one program accessing the shared resource simultaneously, we are in
Need to generate and use a token which guarantees the access to only one
threadofexecutioninthecriticalsectionatatime.
It is counter variable, which takes only the positive numbers and upon which programs can only
act atomically. The positive number is the value indicating the number of units of the shared
resources are available for sharing.
The common form of semaphore is the binary semaphore, which will control a single resource,
and its value is initialized to 0.
Creation of semaphore:
The shmget() function creates a new semaphore or obtains the semaphore key of an existing
semaphore.
#include<sys/sem.h>
intshmget(key_tkey,intnum_sems,
intsem_flags);
The first parameter, key, is an integral value used to allow unrelated process to access the same
semaphore. The semaphore key is used only by semget. All others use the identifier return by the
semget(). There is a special key value IPC_PRIVATE whichallows to create the semaphore and
to be accessed only by the creating process.
174
The second parameter is the number of semaphores required, it is almost always 1. The
third parameter is the set of flags. The nine bits are the permissions for the semaphore.
On success it will return a positive value which is the identifier used by the other semaphore
functions. On error, it returns -1.
#include<sys/sem.h>
int semop(int sem_id,struct sembuf
*sem_ops,size_t num-_sem_ops);
The first parameter is the shmid is the identifier returned by the semget().
The second parameter is the pointer to an array of structure. The structure may contain at least
the following members:
struct sembuf{
short sem_num;
short sem_op;
short sem_flg;
}
The first member is the semaphore number, usually 0 unless it is an array of semaphore. The
sem_op is the value by which the semaphore should be changed. Generally it takes -1,which is
operation to wait for a semaphore and +1, which is the operation to signal the availability of
semaphore.
The third parameter, is the flag which is usually set to SET_UNDO. If the process terminates
without releasing the semaphore, this allows to release it automatically.
#include<sys/sem.h>
int semctl(int sem_id,int sem_num,
int command,.../*union semun arg */);
The third parameter is the command, which defines the action to be taken. There are two
common values:
//sem.c
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/sem.h>
175
#include<sys/ipc.h>
#include<sys/types.h>
union semun
{
int val;
struct semid_ds *buf;
unsigned short *array;
};
if (!semaphore_v()) exit(EXIT_FAILURE);
pause_time = rand() % 2;
sleep(pause_time);
}
printf("\n%d - finished\n", getpid());
if (argc > 1)
{
sleep(10);
del_semvalue();
}
exit(EXIT_SUCCESS);
}
The function set_semvalue() initializes the semaphore using the SETVAL command
in semctl()function. But this is to be done before the usage of semaphore.
In the function semaphore_v(),the semop member of the structure sembuf is set to 1.By this the
semphore becomes available for the other processes because it is released.
178