Lab1 Pipes

Download as odt, pdf, or txt
Download as odt, pdf, or txt
You are on page 1of 9

Pipes

Theory:
We can think of pipe as piece of plumbing that allows data to flow from one process to another.

• A pipe is byte stream. No boundaries maintained between two writes of sender process.

• Pipe are unidirectional.

o Data can travel in only one direction.

o One end is used for reading and the other for writing.

• Pipe is simply a buffer maintained in kernel memory.

o Its size PIPE_BUF is normally 4096 bytes.

The pipe() system call creates a new pipe.

o Successful call return two file descriptors.

 Filedes[0] for read end and filedes[1] for write end.

• Normally pipe is used for communication between two processes. So fork() follows pipe() system
call.
If there is need for both parent and child to read and write data, then

o Using single pipe leads to race conditions. Can be avoided using some synchronizations
mechanism.

o Simpler is to use to two pipes, one in each direction.

 This may lead to deadlock situation. Both parent and child blocked in reading but there is no data
in the pipes.

• Not only parent and child but any two processes having a common ancestor can use pipe provided
that common ancestor has created the pipe.

Process reading from pipe closes write end of the pipe. Why?

o While reading from pipe an EOF is encountered only if there are no more write ends open.

o If not closed, the read may block indefinitely waiting.

• Process writing to pipe closes read end of the pipe. Why?

o If a process tries to write to a pipe for which there is no read end open, then kernel generates
SIGPIPE signal. This signal has default action, terminate process.

 Although write() also returns an error EPIPE, but the generation of signal is meant for killing the
sender process because the processes using pipe do not know that they are using pipe.

o If the process doesn’t close read end, process will still be able to write to the pipe, once full it will
indefinitely blocked waiting for someone to read the pipe.

 No other process has read end open.


ls | wc

Create a pipe in the parent

Fork a child

Duplicate the standard output descriptor to write end of pipe

Exec ‘ls’ program

In the parent wait for the child.

Duplicate the standard input descriptor to read end of pipe

Exec ‘wc’ program


1. Pipes : Practical
//pipe.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>

#define MSGSIZE 16
main ()
{
int i;
char *msg = "How are you?";
char inbuff[MSGSIZE];
int p[2];
pid_t ret;
pipe (p);
ret = fork ();
if (ret > 0)
{
i = 0;
while (i < 10)
{
write (p[1], msg, MSGSIZE);
sleep (2);
read (p[0], inbuff, MSGSIZE);
printf ("Parent: %s\n", inbuff);
i++;
}
exit(1);
}
else
{
i = 0;
while (i < 10)
{
sleep (1);
read (p[0], inbuff, MSGSIZE);
printf ("Child: %s\n", inbuff);
write (p[1], "i am fine", strlen ("i am fine"));
i++;
}
}
exit (0);
}

Q?
a. Check the output of the above program. Observe that using one pipe we
can communicate both ways but in only one direction at a time.
b. Remove one of the sleep statements and see the output.
c. Remove both the sleep statements and see the output.
d. Try to make the above program synchronized i.e. only when the child
completes its writing, parent writes data; child doesn’t write until parent
completes writing.

2. Application of Pipe
The program needs to read only the upper case letters even though the user
may enter the lowercase letters. For that the following design is considered.
The filter program reads the characters from terminal and converts all of them
into upper case and writes to output. Filter program is invoked by the program
using popen().

/*First compile filter program gcc filter.c –o filter*/

/*filter.c*/
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
void
err_sys (char *str)
{
perror (str);
exit (-1);
}

int
main (void)
{
int c;

while ((c = getchar ()) != EOF)


{
if (islower (c))
c = toupper (c);
if (putchar (c) == EOF)
err_sys ("output error");
if (c == '\n')
fflush (stdout);
}
exit (0);
}

/*parent.c*/
#include <sys/wait.h>
#include <stdlib.h>
#include <stdio.h>
void err_sys(char* str)
{
perror(str);
exit(-1);
}

#define MAXLINE 80
int
main (void)
{
char line[MAXLINE];
FILE *fpin;

if ((fpin = popen ("./filter", "r")) == NULL)


err_sys ("popen error");
for (;;)
{
fputs ("prompt> ", stdout);
fflush (stdout);
if (fgets (line, MAXLINE, fpin) == NULL) /* read from pipe */
break;
if (fputs (line, stdout) == EOF)
err_sys ("fputs error to pipe");
}
if (pclose (fpin) == -1)
err_sys ("pclose error");
putchar ('\n');
exit (0);
}

Q?
1. Observe the usage of popen. Using pipes we can filter the data that is
coming from terminal into the program.
2. modify the program so that parent can accept only numbers. User may
enter anything. But the program should read only the numbers.
3. Pipelines
Consider the following program for executing ls –l|wc –l.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>

main ()
{
int i;
int p[2];
pid_t ret;
pipe (p);
ret = fork ();

if (ret == 0)
{
close (1);
dup (p[1]);
close (p[0]);
execlp ("ls", "ls", "-l", (char *) 0);
}

if (ret > 0)
{
close (0);
dup (p[0]);
close (p[1]);
wait (NULL);
execlp ("wc", "wc", "-l", (char *) 0);
}
}

Q?
a. Is wait required here? Why or why not?

b. Is it required to close unused ends of pipe? Suppose if you don’t close p[1] in
parent will it have any effect? Find out.

c. dup2() system call can be used instead of dup(). Find out more on dup2() using
man dup2 command. Dup2 is considered safer version of dup(). Can you see
why is it so?

d. Modify the above program to execute ls –l| grep ^d |wc –l. The output of
this should be the number of directories in the current directory.

4. Coprocesses:

The diagram shows the use of a child process loading an executable. Here the
noticeable point is parent is supplying the input and the same parent is reading
the output from the child. This requires two pipes to make the duplex
communication possible. In such cases the child process is called as coprocess.
The co-process can do variety of tasks like spell checking, validation, sorting,
etc …
In the following example, we use a coprocess to validate emails. The emailValidate.c
program is follows.
#include <stdio.h>
#include <string.h>
#define MAXSIZE 100

main()
{
char buf[MAXSIZE];int n;
while((n=read(0, buf, MAXSIZE))>0)
{
buf[n]='\0';
if(strstr(buf,"@")>0)
if(strstr(buf,".")>0)
write(1,"1\n",2);
else write(1,"-2\n",3);
else write(1,"-3\n",3);
}
}

It takes the string from stdin and writes the result to stdout. Now consider the
following parent process.
main ()
{
int p1[2], p2[2];
int ret;
FILE *fpi, *fpo;
char line[MAXSIZE], result[MAXSIZE];
pipe (p1);
pipe (p2);
ret = fork ();
if (ret == 0)
{
close (p1[1]);
close (p2[0]);
dup2 (p1[0], 0);
dup2 (p2[1], 1);
execl ("./validateEmail", "validateEmail", (char *) 0);
}
else
{
close (p1[0]);
close (p2[1]);
fpi = fopen ("emails.txt", "r");
fpo = fopen ("emails_validation.txt", "w");
while (fgets (line, MAXSIZE, fpi) != NULL)
{
write (p1[1], line, strlen (line));
read (p2[0], result, MAXSIZE);
fprintf (fpo, "%s,%d\n", line, atoi (result));
}}}

The parent process takes help from the co-process validateEmail to check the
emails.

Q?
1. Execute the above program. First compile validateEmailCoprocess.c to
validateEmail executable. gcc validateEmailCoprocess.c –o
validateEmail Then compile coprocess_parent.c and run it . The
emails.txt sample input is available here. Double click it to open.
. Now parent is running, if we kill validateEmail child process by
Emails.txt

sending SIGKILL signal, find out what would happen? Modify the parent to
recognize such unexpected situations and respond positively.

2. Suppose the input is in order of millions. We want to concurrently process


the validation. Modify the above program so that the there can be
several coprocesses concurrently processing.

3. Consider the following alteration in the validateEmail.c file. Compile it to


validateEmail executable and run the parent.
main ()
{
char buf[MAXSIZE];
while (fgets (buf, MAXSIZE, stdin) != NULL)
{
if (strstr (buf, "@") > 0)
if (strstr (buf, ".") > 0)
printf ("1\n");
else
printf ("-2\n");
else
printf ("-3\n");
}
}

What did you notice? Why the program doesn’t work? Find out. [hint: Effect
of using low-level system calls like read(), write() with standard i/o functions
like printf(), scanf() etc]

You might also like