Using pipes-assignment 4

In this exercise, you will use anonymous and named pipes to transmit information across address spaces. One process, the source, uses a host file interface to read information from a file and to write the information to an anonymous pipe. A second process, the filter process, reads infomration from the source (passed by the pipe), performs a simple filtering step (such as converting all upper-case letters to lower-case letters), and writes the resulting data to a named pipe. The named pipe is used for IPC between the filter and the sink process. The sink process reads the named pipe from the filter and writes the information to a second file. Remeber not to allow the filter to block on empty input if it has something to transmit.

Anonymouus pipes in UNIX

Pipes are the main IPC mechanism for UNIX in a single processor. They are implemented by sockets in multiprocessor BSD UNIX. A pipe employs an asynchronous send() and a blocking receive() operation. The blocking receive() operation can be changed to a non-blocking receive(). Pipes are FIFO buffers designed with an API that is similar (or tries to be) to the file I/O interface. A pipe can contain a (system defined) maximum number of bytes (normally 4KB) at a given time. A process sends information by writing it into one end of a pipe. Another process receives information by reading the other end of the pipe. The buffer is a kernel buffer.

A pipe is represented in the kernel by a file descriptor. When a process wants to to create a pipe, it makes a system call of the form:
int pipeID[2];
...
pipe(pipeID);

The kernel creates the pipe as a kernel FIFO data structure with two file ID's. In this example code, pipeID[0] is a file pointer to the read end of the pipe and pipeID[1] is a file pointer to the write end of the pipe. The file pointer is an index into the process's open file table.

In order for two or more processes to use anonymous pipes for IPC, a common ancestor of the processes must create the pipe prior to creating the processes. Because the UNIX fork() command creates a child that has a copy of the parent's open file table, each child ingerits pipes that the parent created. To use a pipe, it need only read and write the proper file descriptors.

If a parent creates a pipe, it can create a child and communicate with it as follows:
...
pipe(pipeID);
if(fork()==0 {/*The child process*/
...
read(pipeID[0],childBuf,len);
/*process the message in childBuf*/
...
} else {/* The parent process*/
...
/*Send a message to the child*/ write [pipeID[1],msgToChild,len);
...

Consider a pair of processes A and B, which share the variables x and y. Process A writes x and reads y while process B writes y and reads x. B must not read x until A has written to it and A must not read y until B has written to it. Here is a way to arrange this by means of pipes.
int A_to_B[2], B_toA[2];
main(){
pipe(A_to_B);
pipe(B_to_A);
if (fork()==0) {/*This is the first child process*/
execve("prog_A.out",...);
exit(1); /*Error; terminate the child*/
}
/*This is the parent process code*/
wait(...);
wait(...);
}
proc_A(){
while (TRUE) {
;
read)B_to_A[0],y,sizeof(int));
/*Use this pipe to get info*/
}
}
proc_B() {
while (TRUE) {
read(A_to_B[0], x, sizeof(int)):
/*Use this pipe to get info*/ ;
write(B_to_A[1],y,sizeof(int));
/*Use this pipe to send info*/
; }
}

Nonblocking read operations in Unix

As with any file, the read end of a pipe, a file descriptor, or a socket can be configured to use nonblocking semantics with an ioctl() call in Unix. After the call has been issued on the descriptor, a read() on the stream returns immediately with the error code set to EAGAIN in Posix (or EWOULDBLOCK in 4.3 BSD). Also, the read() will return a value of 0, thereby indicating it did not read any information into the buffer. Alternatively, the program can check the length value to see if it is non-zero and if the read succeeded. The following code fragment illustrates the use of the ioctl() to switch the read end of a pipe from its default blocking behavior to non-blocking behavior:
#include
int errno; /*For non-blocking read flag
...
main() {
int pipeID[2];
...
pipe(pipeID); /*Switch the read end of the pipe to the non-blocking mode*/
ioctl(pipeID[0],FIONBIO,&on);
...
while(...) {
/*Poll the read end of the pipe*/
read(pipeID[0],buffer,BUFLEN0;
if (errno != EWOULDBLOCK){
/*Incoming info available from the pipe; process it*/
...
}... {
/*Check the pipe for input again later-do other things*/
...
}
}
...
}

Attacking the Problem

It is necessary to check the asynchronous behavior of the solution. In order to do this your processes have to be able to sleep or do a busy loop for random amounts of time, thereby allowing information to build up in the pipes. Make use of the rand() function to introduce the random delay in your program.