IPC: Inter-Process Communication Within OS
IPC: Inter-Process Communication Within OS
within OS
• IPC within OS can be achieved through pipes
and fifos.
• Pipes are the oldest method of IPC within OS in
UNIX systems.
• Pipes are used for making the related process
to communicate.
• FIFOs are the variation of the Pipes and they
are used for making any unrelated processes to
communicate.
Pipes Overview
• User of the shell is familiar with the use of pipes
in commands such as the following.
$ ls | wc –l
• To execute the above command, the shell
creates two processes.
• One process for executing ls and another for
executing wc.
• This is done by shell using vfork() and exec()
system calls.
• Two processes are connected to the pipe so that the
writing process (ls) has its standard output (file
descriptor 1) joined to the write end of the pipe,
while the reading process (wc) has its standard input
(file descriptor 0) joined to the read end of the pipe.
• These two processes are unaware of the existence of
the pipe; they just read from and write to the
standard file descriptors.
• The shell must do some work in order to set
things up in this way.
• When a pipe is created, the file descriptors used
for the two ends of the pipe are the next lowest-
numbered descriptors available.
• In normal circumstances, descriptors 0, 1, and 2
are already in use for a process, some higher-
numbered descriptors will be allocated for the
pipe.
• So how do we bring about the situation shown in
Figure 44-1
• In Figure 44-1, standard output of one
Program is directed into the pipe and the
standard input of the other is taken from the
pipe.
• How can we do this without modifying the
code of the programs.
• The answer is to use the techniques of
duplicating file descriptors.
• Traditionally, the following series of calls was
used to accomplish the desired result.
int pfd[2];
pipe(pfd); /* Allocates (say) file descriptors 3 and
4 for pipe */
dup2(pfd[1], STDOUT_FILENO);
/* Close descriptor 1, and reopen bound
to write end of pipe */
• After duplicating pfd[1], we now have two file
descriptors referring to the write end of the pipe:
descriptor 1 and pfd[1]. Since unused pipe file
descriptors should be closed, after the dup2() call, we
close the pfd[0] descriptor:
close(pfd[1]);
• The code we have shown so far relies on standard
output having been previously open.
• Suppose that, prior to the pipe() call, standard input
and standard output had both been closed.
• In this case, pipe() would have allocated these two
descriptors to the pipe, perhaps with pfd[0] having the
value 0 and pfd[1] having the value 1.
• Consequently, the preceding dup2() and close() calls would be
equivalent to the following:
if (pfd[1] != STDOUT_FILENO)
{
dup2(pfd[1], STDOUT_FILENO);
close(pfd[1]);
}
Example program
The program in Listing 44-4 uses the techniques
described in this section to bring about the
setup shown in Figure 44-1. After building a
pipe, this program creates two child processes.
The first child binds its standard output to the
write end of the pipe and then execs ls. The
second child binds its standard input to the read
end of the pipe and then execs wc.
Listing 44-4: Using a pipe to connect ls and wc
–––––––––––––––––––––––––––––pipes/pipe_ls_wc.c (ipc1.c)
if (pfd[1] != STDOUT_FILENO)
{ /* Defensive check */
if (dup2(pfd[1], STDOUT_FILENO) == -1)
errExit("dup2 1");
if (close(pfd[1]) == -1)
errExit("close 2");
}
execlp("ls", "ls", (char *) NULL); /* Writes to pipe */
errExit("execlp ls");
default: /* Parent falls through to create next child */
break;
}
switch (fork())
{
case -1: /*fork error */
errExit("fork");
case 0: /* Second child: exec 'wc' to read from
pipe */
if (close(pfd[1]) == -1) /* Write end is unused */
errExit("close 3");
/* Duplicate stdin on read end of pipe; close duplicated
descriptor */
if (pfd[0] != STDIN_FILENO) /* Defensive check */
{
if (dup2(pfd[0], STDIN_FILENO) == -1)
errExit("dup2 2");
if (close(pfd[0]) == -1)
errExit("close 4");
}
execlp("wc", "wc", "-l", (char *) NULL); /* Reads from pipe */
errExit("execlp wc");
default: /* Parent falls through */
break;
}
/* Parent closes unused file descriptors for pipe, and waits for children
*/
if (close(pfd[0]) == -1)
errExit("close 5");
if (close(pfd[1]) == -1)
errExit("close 6");
if (wait(NULL) == -1)
errExit("wait 1");
if (wait(NULL) == -1)
errExit("wait 2");
exit(EXIT_SUCCESS);
}
When we run the program in Listing 44-4, we see the following:
$ ./pipe_ls_wc
24
$ ls | wc -l Verify the results using shell commands
24
Pipes Limitations
Pipes have two limitations.
• 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.
#define BUF_SIZE 10
Int main(int argc, char *argv[])
{
int pfd[2]; /* Pipe file descriptors */
char buf[BUF_SIZE];
ssize_t numRead;
if (argc != 2 || strcmp(argv[1], "--help") == 0)
usageErr("%s string\n", argv[0]);
if (pipe(pfd) == -1) /* Create the pipe */
errExit("pipe");
switch (fork())
{
case -1:
errExit("fork");
if (numRead == 0)
break; /* End-of-file */
time(&curtime1);
printf("%s Parent started\n", ctime(&curtime1));
if (pipe(pfd) == -1)
errExit("pipe");
for (j = 1; j < argc; j++)
{
switch (fork())
{
case -1:
errExit("fork %d", j);
case 0: /* Child */
if (close(pfd[0]) == -1) /* Read end is unused */
errExit("close");
/* Child does some work, and lets parent know it's done */
sleep(atoi(argv[j]));
/* Simulate processing */
time_t curtime2;
time(&curtime2);
printf("%s Child %d (PID=%ld) closing pipe\n", ctime(&curtime2), j, (long)
getpid());
if (close(pfd[1]) == -1)
errExit("close");
/* Child now carries on to do other things... */
_exit(EXIT_SUCCESS);
default: /* Parent loops to create next child */
break;
}
}
/* Parent comes here; close write end of pipe so we can
see EOF */
if (close(pfd[1]) == -1) /* Write end is unused */
errExit("close");
/* Parent may do other work, then synchronizes with
children */
if (read(pfd[0], &dummy, 1) != 0)
fatal("parent didn't get EOF");
time_t curtime3;
time(&curtime3);
printf("%s Parent ready to go\n", ctime(&curtime3));
/* Parent can now carry on to do other things... */
exit(EXIT_SUCCESS);
}
popen AND pclose FUNCTIONS
• Talking to a Shell Command via a Pipe: popen()
• A common use for pipes is to execute a shell command
and either read its output or send it some input.
• The popen() and pclose() functions are provided to
simplify this task.
#include <stdio.h>
FILE *popen(const char *command, const char *mode);
Returns: file stream, or NULL on error
int pclose(FILE *stream);
Returns: termination status of child process, or –1 on error
• The popen() function creates a pipe, and then forks a child process that execs
a shell, which in turn creates a child process to execute the string given in
command.
• The mode argument is a string that determines whether the calling process
will read from the pipe (mode is r) or write to it (mode is w).
• Since pipes are unidirectional, two-way communication with the executed
command is not possible.
• The value of mode determines whether the standard output of the executed
command is connected to the write end of the pipe or its standard input is
connected to the read end of the pipe as shown below.
Example-1 on popen and pclose (ipc9.c)
#include <stdio.h>
int main(void)
{
FILE *in;
char buff[512];
if(!(in = popen("ls -l", "r")))
{
exit(1);
}
while(fgets(buff, sizeof(buff), in)!=NULL){
printf("%s", buff);
}
pclose(in);
}
Example-2 on popen and pclose (ipc10.c)
#include<stdio.h>
#include<unistd.h>
fp = popen(popenCmd, "r");
if (fp == NULL) {
printf("popen() failed\n");
continue;
}
/* Read resulting list of pathnames until EOF */
fileCnt = 0;
while (fgets(pathname, PATH_MAX, fp) != NULL) {
printf("%s", pathname);
fileCnt++;
}
/* Close pipe, fetch and display termination status */
status = pclose(fp);
printf(" %d matching file%s\n", fileCnt, (fileCnt != 1) ? "s" : "");
printf(" pclose() status == %#x\n", (unsigned int) status);
if (status != -1)
printf("%d\t", status);
}
exit(0);
}
Example-3 on popen and pclose (ipc11.c)
#include<stdio.h>
#include<string.h>
#include<sys/types.h>
int main()
{
size_t n;
char buffer[50], command[50];
FILE *fp;
printf("Enter the File Name:");
fgets(buffer,50,stdin);
snprintf(command, sizeof(command),"cat %s",buffer);
fp=popen(command,"r");
while(fgets(buffer,50,fp)!=NULL)
fputs(buffer,stdout);
pclose(fp);
return 0;
}
FIFOs
• FIFOs are sometimes called named pipes. Pipes can be used
only between related processes when a common ancestor
has created the pipe.
• The principal difference is that a FIFO has a name within the
file system and is opened in the same way as a regular file.
This allows a FIFO to be used for communication between
unrelated processes (e.g., a client and server).
• Once a FIFO has been opened, we use the same I/O system
calls as are used with pipes and other files (i.e., read(),
write(), and close()). Just as with pipes, a FIFO has a write
end and a read end, and data is read from the pipe in the
same order as it is written.
• This fact gives FIFOs their name: first in, first out. FIFOs are
also sometimes known as named pipes.
• We can create a FIFO from the shell using the
mkfifo command:
$ mkfifo [ -m mode ] pathname
The pathname is the name of the FIFO to be
created.
–m option is used to specify a permission mode
in the same way as for the chmod command.
• When applied to a FIFO (or pipe), fstat() and
stat() return a file type of S_IFIFO in the st_mode
field of the stat structure
• When listed with ls –l, a FIFO is shown with the
type p in the first column, and ls –F appends an
the pipe symbol (|) to the FIFO pathname.
The mkfifo() function creates a new FIFO with the given
pathname.