Shell (Part 2) Example What if we want to support something like this: ps –le | sort One process should execute ps –le and another should execute sort By default a command like ps requires that its output goes to standard output i.e., the terminal (stdout) The sort command requires that a file be provided as a command line argument from standard input (stdin) First Attempt pid = fork(); if (pid<0) { perror("Problem forking"); exit(1); } else if (pid>0) { /* parent process */ execlp("ps","ps","-le", NULL); perror("exec problem"); exit(1); } else { /* child process */ } execlp("sort","sort",NULL); perror("exec problem"); exit(1); } } return(0); Example Why doesn’t this work? The output of the ps -le goes to the terminal We want it to be the input to the sort The diagram on the next page shows the status of the file descriptor tables after the fork Fork and Files 0 1 2 3 4 stdin stdout stderr Parent File Descriptor table 0 1 2 3 4 stdin stdout stderr Child File Descriptor table Terminal info Terminal info Terminal info System file table What is Needed? Assume process P1 is to execute ps –le and that P2 is to execute sort There is a need for shared memory that allows P1 to write the results of ps –le to the shared memory P2 should be able to read the results from the shared memory and provide the results to the sort command Pipes The pipe function can be used to provide the shared memory We will first provide a general discussion of the pipe function which is to be followed by a discussion of how it applies to executing ps –le | sort Pipes Pipes can be used between processes that have a common ancestor Typical use: Pipe created by a process Process calls fork() Pipe used between parent and child Allows for communication between processes Creating a Pipe #include <unistd.h> int pipe(int filedes[2]); Returns 0 if ok, -1 on error Returns two file descriptors filedes[0] is open for reading filedes[1] is open for writing Example #include <unistd.h> #include <stdio.h> int main(void){ int n; int fd[2]; pid_t pid; char line[80]; // track of num bytes read // hold fds of both ends of pipe // pid of child process // hold text read/written Continued … if (pipe(fd) < 0) perror("pipe error"); // create the pipe if ((pid = fork()) < 0) { // fork off a child perror("fork error"); } else if (pid > 0) { // parent process close(fd[0]); // close read end write(fd[1], "hello world\n", 12); // write to it wait(NULL); }… Continued… else { close(fd[1]); n = read(fd[0], line, 80); write(1, line, n); } exit(0); } // child process // close write end // read from pipe // echo to screen Fork and Pipes A fork copies the file descriptor table to the child The parent should close one of the file descriptors while the child should close the other Example code on the two previous slides: Parent closes fd[0] since it does not read from it Child closes fd[1] since it does not write Fork and Pipes 0 1 2 3 4 stdin stdout stderr fd[0] fd[1] Parent File Descriptor table 0 1 2 3 4 stdin stdout stderr fd[0] pipe info e.g., read offset pipe info e.g., write offset System file table fd[1] Child File Descriptor table After Fork Fork and Pipes 0 1 2 3 4 stdin stdout stderr fd[0] = NULL fd[1] Parent File Descriptor table 0 1 2 3 4 stdin stdout stderr fd[0] fd[1] = NULL Child File Descriptor table pipe info e.g., read offset pipe info e.g., write offset System file table After Closing Ends Pipes By default, if a writing process attempts to write to a full pipe, the system will automatically block the process until the pipe is able to receive the data Likewise, if a read is attempted on an empty pipe, the process will block until data is available In addition, the process will block if a specified pipe has been opened for reading, but another process has not opened the pipe for writing Pipe Capacity The OS has a limit on the buffer space used by the pipe If you hit the limit, write will block Example We will now show how pipes can be used for supporting the execution of ps –le | sort Example First let us Create shared memory that is to be used by the parent and child processes This is done using the pipe function The pipe function is executed before the fork function The results of ps –le should be put into the shared memory to be used by the child process for sort See next slide for code The slide after code slide depicts the file descriptor table and System File table Example int main(int argc, char **argv) { int fds[2]; pid_t pid; /* attempt to create a pipe */ if (pipe(fds)<0) { perror("Fatal Error"); exit(1); } Example Terminal info Parent File Desc. table 0 1 2 3 4 stdin stdout stderr fds[0] fds[1] Terminal info Terminal info Shared mem. info: read Shared mem. Info: write System file table Terminal info Example Terminal info Terminal info Shared mem. Info: read Shared mem. Info: write System file table Shared Memory Example Each entry in the system file table has information about the “file” which could be the terminal, disk file or pipe (shared memory) For shared memory created by the pipe function: The read descriptor includes information about the last location read from The write descriptor includes information about the last location written to. Example Let us now add the code for the fork See next slide for the code Example /* create another process */ pid = fork(); if (pid<0) { perror("Problem forking"); exit(1); } …………….. What is the status of the file descriptor table Example Parent File Desc. table Child File Desc. table 0 1 2 3 4 0 1 2 3 4 stdin stdout stderr fds[0] fds[1] stdin stdout stderr fds[0] fds[1] Terminal info Terminal info Terminal info Shared mem. Info: read Shared mem. Info: write System file table Fork and Pipes A fork copies the file descriptor table to the child The parent should close one of the file descriptors while the child should close the other Fork and Pipes 0 1 2 3 4 stdin stdout stderr fd[0] fd[1] Parent File Descriptor table 0 1 2 3 4 stdin stdout stderr fd[0] pipe info e.g., read offset pipe info e.g., write offset System file table fd[1] Child File Descriptor table After Fork Fork and Pipes 0 1 2 3 4 stdin stdout stderr fd[0] = NULL fd[1] Parent File Descriptor table 0 1 2 3 4 stdin stdout stderr fd[0] fd[1] = NULL Child File Descriptor table pipe info e.g., read offset pipe info e.g., write offset System file table After Closing Ends Example We want the output of the ps –le command to be put into the shared memory The sort command should read from the shared memory Two issues: The sort command assumes that it receives its input from stdin The ps command assumes that it outputs to stdout We need to “reroute” This can be done using the dup() function dup() and dup2 #include <unistd.h> int dup(int filedes1); int dup2(int filedes1, int filedes2); Both will duplicate an existing file descriptor dup() returns lowest available file descriptor, now referring to whatever filedes1 refers to dup2() - filedes2 (if open) will be closed and then set to refer to whatever filedes1 refers to Example Now we want what would normally go to the standard output to go to the shared memory This is done with the following code: if ( dup2(fds[1],STDOUT_FILENO)<0) { perror("can't dup"); exit(1); } The new parent file descriptor table is on the next page Example Terminal info Parent File Desc. table Child File Desc. table stdin 0 stdout 1 stderr 2 3 fds[0]=NULL fds[1] 4 stdin stdout stderr fds[0] 0 1 2 3 4 fds[1]=NULL Terminal info Terminal info Shared mem. info: read Shared mem. info: write System file table Example Now want to set it up so that the child reads from the shared memory This is done with the following code: if ( dup2(fds[0],STDIN_FILENO)<0) { perror("can't dup"); exit(1); } The new child file descriptor is on the next page Example Terminal info Parent File Desc. table Child File Desc. table stdin 0 stdout 1 stderr 2 3 fds[0]=NULL fds[1] 4 stdin stdout stderr fds[0] 0 1 2 3 4 fds[1]=NULL Terminal info Terminal info Shared mem. info: read Shared mem. info: write System file table Example Let us now put it together Example /* create another process */ pid = fork(); if (pid<0) { perror("Problem forking"); exit(1); } else if (pid>0) { /* parent process */ close(fds[0]); /* close stdout, reconnect to the writing end of the pipe */ if ( dup2(fds[1],STDOUT_FILENO)<0) { perror("can't dup"); exit(1); } execlp("ps","ps","-le", NULL); perror("exec problem"); exit(1); Example } else { /* child process */ } } close(fds[1]); if (dup2(fds[0],STDIN_FILENO) < 0) { perror("can't dup"); exit(1); } execlp("sort","sort",NULL); perror("exec problem"); exit(1); return(0);