CSCB09: Software Tools and Systems Programming Nandita Vijaykumar [email protected] 2Processes can create other processes The fork system call creates a child process: #include pid_t fork(void); Fork creates a duplicate of the currently running program. Both processes run concurrently and independently. After fork() both execute the next instruction after fork. fork() Process A Process A Process A’ 3waitpid What if a process wants to wait for a particular child (rather than any child) What if a process does not want to block when no child has terminated pid_t waitpid(pid_t pid, int *status, int options); First parameter specifies PID of child to wait for If options is 0, waitpid blocks (just like wait) If options is WNOHANG, it immediately returns 0 instead of blocking when no terminated child 4How does a child become a zombie When a child terminates, but its parent process is not waiting for it The child (its exit code) is kept around as a zombie until parent collects its exit code through wait – or until parent terminates Shows up as Z in ps 5How does a child become an orphan If the parent process terminates before the child Who is now the new parent – Orphans get adopted by the init process – init is the first process started during booting – It’s the root of the process hierarchy – init has a PID of 1 – The PPID of orphans is 1 6Wait a second …. Fork creates a duplicate of the current process How do we actually create a new process that runs a different program 7exec() exec() is not one specific function, but a family of functions: execl(char *path, char *arg0, …, (char *)NULL); execv(char *path, char *argv[]); execlp(char *file, char *arg0, …,(char *)NULL); execvp(char *file, char *argv[]); First parameter: name of executable; then commandline parameters for executable; these are passed as argv[0], argv[1], …, to the main program of the executable. execl and execv differ from each other only in how the arguments for the new program are passed execlp and execvp differ from execl and execv only in that you don’t have to specify full path to new program 8Processes often need to communicate E.g. the different worker processes of a parallel program need to exchange data and/or synchronize Options: Exit code – limited solution… Cannot use variables, since after fork each process has separate copy of variables Use files — coordination is difficult 9Solution: Pipes parent child kernel pipe Writes to pipeReads from pipe Pipes are a one-way (half-duplex) communication channel Pipes are buffers managed by the OS Processes use low-level file descriptors for pipe operations Recall: I/O mechanisms in C So far we used: File pointers (regular files): – You use a pointer to a file structure (FILE *) as handle to a file. – The file struct contains a file descriptor and a buffer. – Use for regular files FILE *fp = fopen(“my_file.txt”, “w”); fprintf(fp, “Hello!n”); fclose(fp); Now we need: File descriptors (low-level): – Each open file is identified by a small integer – Operations: open, close, read, write File I/O with file descriptors int open(const char *pathname, int flags); – Returns a file descriptor – Flags: O_RDONLY, O_WRONLY, or O_RDWR to open the file read-only, write-only, or read/write (and a few more, see man pages) ssize_t read(int fd, void *buf, size_t count); – Returns #bytes read, 0 for EOF, -1 for error ssize_t write(int fd, const void *buf, size_t count); – R
eturns #bytes actually written, -1 for error int close(int fd); – Returns 0 on success, -1 on error Example (error checking omitted …): char buf[6]; int fd = open (“filename.txt”, O_RDONLY); read (fd, buf, 6); close(fd); Back to pipes How do you open/create a pipe int pipe(int pipefd[2]); You pass a pointer to two integers (i.e. an array or a malloc of two ints) and pipe fills it with two newly opened FDs. Returns 0 on success, -1 on error Example: int p[2]; if (pipe(p)) == -1 ) { perror (“pipe”); exit(1); } p[0] is now open for reading p[1] is now open for writing pipe kernel p[1]p[0] user process writeread Example: Process talking to itself #include #include #include #define MSG_SIZE 13 char *msg = “hello, worldn”; int main(void) { char buf[MSG_SIZE]; int p[2]; if(pipe(p) == -1) { perror(“pipe”); exit(1); } write(p[1], msg, MSG_SIZE); read(p[0], buf, MSG_SIZE); write (STDOUT_FILENO, buf, MSG_SIZE); return 0; } Now lets first write msg to the pipe and then read from the pipe ssize_t read(int fd, void *buf, size_t count); ssize_t write(int fd, const void *buf, size_t count); 14 pipe kernel p[1]p[0] user process writeread A process talking to itself is not very useful How do we get two processes talking to each other using pipes 15 The OS manages file descriptors Code Static data Heap Stack Process control block (PCB) pc (program counter) sp (stack pointer) fd table Global file table fd 0 fd 1 fd 2 fd 3 … file status flags inode pointer current file offset Per-process fd table has all open files of a process Global file table has all files open system-wide On fork, a child gets a copy of parent’s fd table, so it will have same files open The fd table is preserved on exec 16 On fork, child gets copy of parent’s file descriptors, including pipes kernel fd[1]fd[0]fd[1]fd[0] pipe writeread writeread parent child How can we make the child send a message to the parent Example 1: Child talking to parent Pipes and EOF What if the parent does not know beforehand when and how much data a child will write If no writing end is open, read detects EOF and returns 0. read blocks until data is available in the pipe. All open pipes (and other FDs) are closed when a process exits. Example 2 : Child talking to parent Parent hangs, even after child exits. Why is EOF not detected Example 3 : Child talking to parent Parent needs to close its writing end. 21 Closing ends of pipes In general, each process will read or write a pipe (not both) Close the end you are not using – Before reading: close p[1] – Before writing: close p[0] 22 Direction of data flow parent child kernel fd[1]fd[0]fd[1]fd[0] pipechild to parent close fd[1] in parent and fd[0] in child) read write 23 Direction of data flow parent child kernel fd[1]fd[0]fd[1]fd[0] pipe parent to child (close fd[0] in parent and fd[1] in child) write read 24 Summary: Pipes and File Descriptors A forked child inherits file descriptors from its parent pipe() creates an OS internal system buffer and two file descriptors, one for reading and one for writing. After the pipe call, the parent and child should close the file descriptors for the opposite direction. Now off to the work sheet … ssize_t read(int fd, void *buf, size_t count); Returns #bytes read, 0 for EOF, -1 for error ssize_t write(int fd, const void *buf, size_t count); Returns #bytes actually written, -1 for error int close(int fd); Returns 0 on success, -1 on error int pipe(int pipefd[2]); Returns 0 on success, -1 on error