0% found this document useful (0 votes)
22 views54 pages

IPC: Inter-Process Communication Within OS

network programming lecture at bits

Uploaded by

jarison_mander
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPTX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
22 views54 pages

IPC: Inter-Process Communication Within OS

network programming lecture at bits

Uploaded by

jarison_mander
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPTX, PDF, TXT or read online on Scribd
You are on page 1/ 54

IPC : Inter-process Communication

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

/* Other steps here, e.g., fork() */

close(STDOUT_FILENO); /* Free file descriptor 1 */

dup(pfd[1]); /* Duplication uses lowest free file


descriptor, i.e., fd 1 */

• The end result of the above steps is that the process’s


standard output is bound to the write end of the pipe.
• Steps to bind a process’s standard input to the read
end of the pipe.
int pfd[2];
pipe(pfd); /* Allocates (say) file descriptors 3 and
4 for pipe */

/* Other steps here, e.g., fork() */

close(STDIN_FILENO); /* Free file descriptor 0 */

dup(pfd[0]); /* Duplication uses lowest free file


descriptor, i.e., fd 1 */
• Above steps depend on the assumption that file
descriptors 0, 1, and 2 for a process are already open. (The
shell normally ensures this for each program it executes.)
• If file descriptor 0 was already closed prior to the above
steps, then we would erroneously bind the process’s
standard input to the write end of the pipe.
• To avoid this possibility, we can replace the calls to close()
and dup() with the following dup2() call, which allows us to
explicitly specify the descriptor to be bound to the pipe
end:

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:

dup2(1, 1); /* Does nothing */


close(1); /* Closes sole descriptor for write end
of pipe */

• Therefore, it is good defensive programming practice to


bracket these calls with an if statement of the following form:

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)

Int main(int argc, char *argv[])


{
int pfd[2]; /* Pipe file descriptors */
if (pipe(pfd) == -1) /* Create pipe */
errExit("pipe");
switch (fork())
{
case -1: /* fork error */
errExit("fork");
case 0: /* First child: exec 'ls' to write to pipe */
if (close(pfd[0]) == -1) /* Read end is unused */
errExit("close 1");
/* Duplicate stdout on write end of pipe; close duplicated
descriptor */

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.

• Historically, they have been half duplex (i.e., data


flows in only one direction).

• Pipes can be used only between processes that


have a common ancestor. Normally, a pipe is
created by a process, that process calls fork, and
the pipe is used between the parent and the child.
Pipe () system call
• 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].


Pictorial view of Half Duplex Pipe
• Two ways to view a half-duplex pipe
• 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 Figure shows this
scenario.
• What happens after the fork depends on 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.

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

• 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.
Program to demonstrate that, the file
descriptors inherited by the child have the
same value. (ipc2.c)
#include<stdio.h>
#include<unistd.h>
Int main()
{
Int pfd[2], pid;
Pipe(pfd);
If((pid=fork())==0)
Printf(“In child pfd[0] is %d and pfd[1] is %d\n”,pfd[0],pfd[1]);
Else
Printf(In parent child pfd[0] is %d and pfd[1] is %d\n”,pfd[0],pfd[1]);
Return 0;
}
Output:
In child pfd[0] is 3 and pfd[1] is 4
In parent pfd[0] is 3 and pfd[1] is 4
One way communication using single pipe
(ipc3.c, ipc4.c and ipc5.c)
#include<stdio.h>
#include<unistd.h>
#define DATASIZE 20
Int main()
{ Output:
Char *msg=“Hello World\n”; I am in parent
Char buff[DATASIZE];
Int pfd[2],pid; I am in child
Pipe(pfd); Hello World
Pid=fork();
If(pid>0)
{
Printf(“I am in Parent\n”);
Close(pfd[0]);
Write(pfd[1],msg,DATASIZE);
}
If(pid==0)
{
Printf(“I am in Child\n”);
Close(pfd[1]);
read(pfd[0],buff,DATASIZE);
Printf(“Data received form parent=%s”,buff);
}
Return 0;
}
Two way Communication
• Pipes are always unidirectional. Hence, using single pipe we cannot
have a two way communication between two related processes.
• To have two way communication between the related processes
we have to create two pipes, one for each direction.
• The following procedure will help us have the two way
communication.
 Create pipe-1. It gives pfd1[0] and pfd1[1]. A reading and
writing end.
 Create pipe-2. It gives pfd2[0] and pfd2[1]. A reading and
writing end.
 Parent closes the read end of pipe 1 (I,e close(pfd1[0])
 Parent closes the write end of pipe 2 (I,e close(pfd2[1])
 Child closes write end of pipe 1 (I,e closes pfd1[1])
 Child closes read end of pipe 2 (I,e closes pfd2[0])
Pictorial Representation of 2-way
communication
Example program on two way
communication (ipc6.c)
#include<stdio.h>
#include<unistd.h>
Int main()
{
Int pid;
Char buffer1[20], buffer2[20];
Int ps[2],pc[2];
Pipe(ps);
Pipe(pc);
If((pid=fork())>0)
{
Close(ps[0]);
Close(pc[1]);
Write(ps[1], “Had your coffee?\n”, 20);
Read(pc[0], buffer1, 20);
Printf(“%s\n”,buffer1);
}
If(pid==0)
{
Close(ps[1]);
Close(pc[0]);
Read(ps[0], buffer2, 20);
Printf(“ %s\n”,buffer2);
Write(pc[1], “Yes, I had\n”, 20);
}
Return 0;
}
Output:
Had your Coffee?
Yes, I had
Using a pipe to communicate between a parent and child process
––––––––––––––––––––––––––––––––––––––––pipes/simple_pipe.c (ipc7.c)

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

case 0: /* Child - reads from pipe */


if (close(pfd[1]) == -1) /* Write end is unused */
errExit("close - child");

for (;;) /* Read data from pipe, echo on stdout */


{
numRead = read(pfd[0], buf, BUF_SIZE);
if (numRead == -1)
errExit("read");

if (numRead == 0)
break; /* End-of-file */

if (write(STDOUT_FILENO, buf, numRead) != numRead)


fatal("child - partial/failed write");
}
write(STDOUT_FILENO, "\n", 1);
if (close(pfd[0]) == -1)
errExit("close");
_exit(EXIT_SUCCESS);

default: /* Parent - writes to pipe */


if (close(pfd[0]) == -1) /* Read end is unused */
errExit("close - parent");

if (write(pfd[1], argv[1], strlen(argv[1])) != strlen(argv[1]))


fatal("parent - partial/failed write");

if (close(pfd[1]) == -1) /* Child will see EOF */


errExit("close");
wait(NULL); /* Wait for child to finish */
exit(EXIT_SUCCESS);
}
}
Using a pipe to synchronize multiple processes
–––––––––––––––––––––––––––––––pipes/pipe_sync.c (ipc8.c)
Int main(int argc, char *argv[])
{
int pfd[2]; /* Process synchronization pipe */
int j, dummy;

if (argc < 2 || strcmp(argv[1], "--help") == 0)


usageErr("%s sleep-time...\n", argv[0]);

setbuf(stdout, NULL); /* Make stdout unbuffered, since we


terminate child with _exit() */
time_t curtime1;

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>

#define POPEN_FMT "/bin/ls -d %s 2> /dev/null"


#define PAT_SIZE 50
#define PCMD_BUF_SIZE (sizeof(POPEN_FMT) + PAT_SIZE)
#define PATH_MAX 10
int main(int argc, char *argv[])
{
char pat[PAT_SIZE]; /* Pattern for globbing */
char popenCmd[PCMD_BUF_SIZE];
FILE *fp; /* File stream returned by popen() */
int badPattern; /* Invalid characters in 'pat'? */
int len, status, fileCnt, j;
char pathname[PATH_MAX];
for (;;) { /* Read pattern, display results of globbing */
printf("pattern: ");
fflush(stdout);
if (fgets(pat, PAT_SIZE, stdin) == NULL)
break; /* EOF */
len = strlen(pat);
if (len <= 1) /* Empty line */
continue;
if (pat[len - 1] == '\n') /* Strip trailing newline */
pat[len - 1] = '\0';
/* Ensure that the pattern contains only valid characters,
i.e., letters, digits, underscore, dot, and the shell
globbing characters. (Our definition of valid is more
restrictive than the shell, which permits other characters
to be included in a filename if they are quoted.) */
for (j = 0, badPattern = 0; j < len && !badPattern;
j++)
if (!isalnum((unsigned char) pat[j]) &&
strchr("_*?[^-].", pat[j]) == NULL)
badPattern = 1;
if (badPattern) {
printf("Bad pattern character: %c\n", pat[j - 1]);
continue;
}
/* Build and execute command to glob 'pat' */
snprintf(popenCmd, PCMD_BUF_SIZE, POPEN_FMT, pat);
/*Print Formatted Data to Buffer*/
popenCmd[PCMD_BUF_SIZE - 1] = '\0'; /* Ensure string is
null-terminated */

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.

• The mode argument specifies the permissions for the


new FIFO. These permissions are specified by ORing
the desired combination of constants with S_IFIFO.
• Once a FIFO has been created, any process can open
it, subject to the usual file permission checks
• By default, opening a FIFO for reading (the open() O_RDONLY
flag) blocks until another process opens the FIFO for writing
(the open() O_WRONLY flag).
• Conversely, opening the FIFO for writing blocks until another
process opens the FIFO for reading.
• Under most UNIX implementations (including Linux), it is
possible to avoid the blocking behavior when opening FIFOs
by specifying the O_RDWR flag when opening a FIFO.
• In this case, open() returns immediately with a file descriptor
that can be used for reading and writing on the FIFO.
• Few UNIX prevents opening a FIFO with the O_RDWR flag;
therefore, for portability reasons, this technique should be
avoided.
• In circumstances where we need to prevent blocking when
opening a FIFO, the open() O_NONBLOCK flag provides a
standardized method for doing so.
Using FIFOs and tee to create a dual
pipeline
• shell pipelines are linear; each process in the pipeline
reads data produced by its predecessor and sends data
to its successor.
• Using FIFOs, it is possible to create a fork in a pipeline,
so that a duplicate copy of the output of a process is
sent to another process in addition to its successor in
the pipeline.
• In order to do this, we need to use the tee command,
which writes two copies of what it reads from its
standard input: one to standard output and the other to
the file named in its command-line argument.
• Tee is demonstrated in the following shell session,
which creates a FIFO named myfifo, starts a
background wc command that opens the FIFO for
reading (this will block until the FIFO is opened for
writing).
• Executes a pipeline that sends the output of ls to tee,
which both passes the output further down the
pipeline to sort and sends it to the myfifo FIFO.
$ mkfifo myfifo
$ wc -l < myfifo &
$ ls -l | tee myfifo | sort -k5n
• The –k5n option to sort causes the output of ls to be
sorted in increasing numerical order on the fifth
space-delimited field.)
• Diagrammatically, the above commands create
the situation shown in the following figure.
Ipc12.c
#include<stdio.h>
#include<unistd.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string.h>
#include<stdlib.h>

int main(int argc,char * argv[])


{
char buff[256];
int fd,fd1;
if(argc!=2 && argc!=3)
{
printf("Usage:Reader 2 arguments and writer 3
arguments\n");
exit(0);
}
(void)mkfifo(argv[1],S_IFIFO|S_IRWXU|S_IRGRP|S_IROTH);
if(argc==2)
{
printf("Reader process\n");
fd=open(argv[1],O_RDONLY);
if(fd==-1)
{
perror("Open error");
exit(0);
}
int r=read(fd,&buff,sizeof(buff));
buff[r]='\0';
printf("Data read=%s\n",buff);
}
else
{
printf("Writer process\n");
fd1=open(argv[1],O_WRONLY);
if(fd1==-1)
{
perror("Open error");
exit(0);
}
write(fd1,argv[2],strlen(argv[2]));
printf("%d bytes of data written\n",strlen(argv[2]));
}
close(fd);
close(fd1);
return 0;
}

You might also like