Forking PDF
Document Details
Uploaded by CalmSerpentine8789
The University of Texas at Dallas
Tags
Summary
This presentation describes the fork, pipe, and dup system calls and concepts in Unix. It covers the basic concepts and demonstrates through example implementations. The document is a lecture slide set on the topic from The University of Texas at Dallas.
Full Transcript
Fork and pipes Simple C Program #include #include int main(void) { printf("This is statement 1. \n"); printf("This is statement 2. \n"); return(0); } This is statement 1 This is statement 2. fork() #include #include int main(void) {...
Fork and pipes Simple C Program #include #include int main(void) { printf("This is statement 1. \n"); printf("This is statement 2. \n"); return(0); } This is statement 1 This is statement 2. fork() #include #include int main(void) { printf("This is statement 1. \n"); fork(); printf("This is statement 2. \n"); return(0); } This is statement 1 This is statement 2. This is statement 2. fork() Program normally within a single process When fork is reached: – A new process is created: child Execute rest of program – Old process stays alive: Parent Execute rest of program (as well as first part too) Parent/child get their own copy of variables - Not knowing who is executing what is not a good idea Parent and Child // Includes are removed, for sake of space int main(void){ pid_t childpid; if((childpid = fork()) == -1){ perror("fork"); exit(1); } if(childpid == 0){ printf("%d: This is the child process. \n", childpid); exit(0); } else { printf("%d: This is the parent process. \n", childpid); exit(0); } return(0); } Pipes Process sometime need to communicate with each other: Inter-process Communication Pipes: – Low level mechanism – pipe () call: Create a pipe – Returns two descriptors first for reading second for writing Pipes A simple, synchronized way of passing information between processes A special file/buffer that stores a limited amount of data in a FIFO manner Pipes are commonly used from within shells to connect the stdout of one utility to the stdin of another ps -ef | grep “3376” Pipe synchronization: – If a process tries to write to a full pipe, it is blocked until the reader process consumes some data – If a process tries to read from an empty pipe, it is blocked until the writer produces some data Data is written to and read from the pipe using the unbuffered system calls write and read Type of pipes There are two types of pipes Unnamed pipes – Used only with related processes – Parent/child – Child/child – The pipe exists only as long as the processes using it are alive Named pipes – Actually exist as directory entries – Have file access permissions – Can be used by unrelated processes Writing to a pipe write(int fd, const void *buf, size_t count) writes up to count bytes to the fd from the buffer starting at buf The number of bytes actually written is returned In the case of pipes – fd refers to a pipe and not a regular file – Each write request is always appended to the end of the pipe – write requests of size PIPE_BUF or less are guaranteed to not be interleaved with other write requests to the same pipe The writer process will complete the write system call without being preempted by another process Look at /usr/include/limits.h to see the block size for an atomic write to a pipe #define PIPE_BUF 5120 If a process tries to write more bytes to a pipe than PIPE_BUF, no guarantees of atomicity apply – If a write is made to a pipe that is not open for reading by any process: A SIGPIPE signal is generated SIGPIPE's default action is to terminate the writer process errno is set to EPIPE (broken pipe) Reading from a pipe read(int fd, void *buf, size_t count) Attempts to read up to count bytes from file descriptor fd into the buffer starting at buf The number of bytes actually read is returned – If EOF is encountered, the number of bytes read is 0 In the case of pipes – fd refers to a pipe and not a regular file – All reads are started from the current position I.e., you can't manipulate the internal file pointer – If the pipe is not opened for writing by another process, read returns 0 – If a process reads from an empty pipe whose write end is still open, it sleeps until some input becomes available pipe( ) system call int pipe(int fildes) If successful, it will return TWO integer file descriptors in fd and fd – fd must be an int array of size 2; The file descriptor in fd is associated with the read end of the pipe and fd is associated with the write end of the pipe Pipes and File Descriptor Table Unnamed Pipes Only the process that created the unnamed pipes and its descendents may use the pipe The typical sequence of opening unnamed pipes is as follows: – The parent process creates an unnamed pipe It is crucial that this be done before forking – The parent process forks – The writer process closes the read end of the pipe – The reader process closes the write end of the pipe – The processes communicate by using write( ) and read( ) – Each process closes its active pipe-end when finished Pipe: one process #include #include int main(void) { char string[] = "Hello!\n"; char readbuffer; int fd; pipe(fd); write(fd, string, (strlen(string)+1)); nbytes = read(fd, readbuffer, sizeof(readbuffer)); printf("Received string: %s", readbuffer); pdes pdes } hello! Pipe: two processes // Includes removed for slide clarity int main(void){ int fd, nbytes; pid_t childpid; char string[] = "Hello, world!\n"; char readbuffer; pipe(fd); if((childpid = fork()) == -1) { perror("fork"); exit(1); } //… continue in next slide Pipe: two processes //.. Continuation from previous slide if(childpid == 0){ close(fd); write(fd, string, (strlen(string)+1)); exit(0); } else{ close(fd); nbytes = read(fd, readbuffer, sizeof(readbuffer)); printf("Received string: %s", readbuffer); } return(0); } Message Flow fd fd fd fd Child Parent Quiz // Includes are removed, for sake of space int main(void){ int i= 0; pid_t childpid; if((childpid = fork()) == -1){ perror("fork"); exit(1); } i= 5; if(childpid == 0){ i++; printf(”process one: %d, value of i: %d \n", childpid, i); i=10; exit(0); } else { i--; printf(”process two: %d, value of i: %d \n", childpid, i); exit(0); } return(0); } dup( ) system call int dup(int oldfd) dup creates a copy of the file descriptor oldfd – The two file descriptors may be used interchangeably It returns a new file descriptor having the following in common with the original file descriptor oldfd – Same open file (or pipe) – Same file pointer – Same access mode (read, write, or read/write) – The new file descriptor is set to remain open across exec functions ***The file descriptor returned is the lowest numbered unused descriptor*** Example int fd; pipe( fd ); close( fileno( stdout ) ); dup( fd ); – fileno( ) library function int fileno(FILE *stream); fileno returns the file descriptor of stream – Since 1 is the lowest fd available, the write end of the pipe is duplicated at fd 1 (stdout) Now any data written to stdout will be written to the pipe – But you are taking a chance that the file descriptor that will be returned by dup is what you want The process may be interrupted between the close( ) and the dup( ) dup2( ) system call int dup2(int oldfd, int newfd) dup2( ) makes newfd be the copy of oldfd, closing newfd first if necessary There is no time lapse between closing newfd and duplicating oldfd into its spot Example #include #include #include #include main() { int fd1, fd2, fd3; fd1 = open("test.txt", O_RDWR | O_TRUNC); printf("fd1 = %i\n", fd1); write(fd1, "what's", 6); fd2 = dup(fd1); printf("fd2 = %i\n", fd2); write(fd2, " up", 3); close(0); fd3 = dup(fd1); printf("fd3 = %i\n", fd3); write(0, " doc", 4); dup2(3,2); write(2, "?\n", 2); } Example Execution Example #include #include #include #include main() { int fd1, fd2, fd3; fd1 = open("test.txt", O_RDWR | O_TRUNC); printf("fd1 = %i\n", fd1); write(fd1, "what's", 6); fd2 = dup(fd1); printf("fd2 = %i\n", fd2); write(fd2, " up", 3); close(0); fd3 = dup(fd1); printf("fd3 = %i\n", fd3); write(0, " doc", 4); dup2(3,2); write(2, "?\n", 2); } Example #include #include #include #include main() { int fd1, fd2, fd3; fd1 = open("test.txt", O_RDWR | O_TRUNC); printf("fd1 = %i\n", fd1); write(fd1, "what's", 6); fd2 = dup(fd1); printf("fd2 = %i\n", fd2); write(fd2, " up", 3); close(0); fd3 = dup(fd1); printf("fd3 = %i\n", fd3); write(0, " doc", 4); dup2(3,2); write(2, "?\n", 2); } Example #include #include #include #include main() { int fd1, fd2, fd3; fd1 = open("test.txt", O_RDWR | O_TRUNC); printf("fd1 = %i\n", fd1); write(fd1, "what's", 6); fd2 = dup(fd1); printf("fd2 = %i\n", fd2); write(fd2, " up", 3); close(0); fd3 = dup(fd1); printf("fd3 = %i\n", fd3); write(0, " doc", 4); dup2(3,2); write(2, "?\n", 2); } Example #include #include #include #include main() { int fd1, fd2, fd3; fd1 = open("test.txt", O_RDWR | O_TRUNC); printf("fd1 = %i\n", fd1); write(fd1, "what's", 6); fd2 = dup(fd1); printf("fd2 = %i\n", fd2); write(fd2, " up", 3); close(0); fd3 = dup(fd1); printf("fd3 = %i\n", fd3); write(0, " doc", 4); dup2(3,2); write(2, "?\n", 2); } Example #include #include #include #include main() { int fd1, fd2, fd3; fd1 = open("test.txt", O_RDWR | O_TRUNC); printf("fd1 = %i\n", fd1); write(fd1, "what's", 6); fd2 = dup(fd1); printf("fd2 = %i\n", fd2); write(fd2, " up", 3); close(0); fd3 = dup(fd1); printf("fd3 = %i\n", fd3); write(0, " doc", 4); dup2(3,2); write(2, "?\n", 2); } execvp( ) system call int execvp(const char* file, char * const argv[]); (See APUE, Figure 8.14 for other variants) executes a file/command replaces the current process image with a new process image specified by executing file – file: construct a pathname to executable file. – argv: An array of character pointers to NULL-terminated strings. constitute the argument list available to the new process image. The value in argv must point to a filename that's associated with the process being started. Pipe: two processes // Includes removed for slide clarity int main(int argc, char **argv){ int status; int childpid; char *cat_args[] = {"ls", "-ltr", NULL}; char *grep_args[] = {"grep", "3376", NULL}; // create 1 pipe to send the output of "ls" process to "grep" process int pipes; pipe(pipes); // fork the first child (to execute cat) if((childpid = fork()) == -1){ perror("Error creating a child process"); exit(1); } Pipe: two processes if (childpid == 0) { // replace cat's stdout with write part of 1st pipe dup2(pipes, 1); close(pipes); close(pipes); execvp(*cat_args, cat_args); exit(0); } else { // replace grep's stdin with read end of 1st pipe dup2(pipes, 0); close(pipes); close(pipes); execvp(*grep_args, grep_args); } return(0); }