CSC209H Lecture 6 Dan Zingaro February 11, 2015
Zombie Children (Kerrisk 26.2) As with every other process, a child process terminates with an exit status This exit status is often of interest to the parent who created the child But what happens if the child process terminates before the parent can grab its exit status? The child turns into a zombie process Minimal information about the process is retained When the parent obtains the exit status, the zombie will get removed How does the parent accept the exit status?...
wait (Kerrisk 26.1.1) pid_t wait(int *status); If a child process has terminated, store the exit status in *status and continue Otherwise, block until any child terminates, and then store its status If status is NULL, don t store exit status wait returns the PID of the child process that has terminated, or -1 on error A child can terminate normally or be killed by a signal WIFEXITED tells you if the child terminated normally; use WEXITSTATUS to get the exit status
wait: example (wait.c) #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <sys/wait.h> int main(void) { pid_t child; int status, exit_status; if ((child = fork()) == 0) { sleep(5); exit(8); wait(&status); if (WIFEXITED(status)) { exit_status = WEXITSTATUS(status); printf("child %d done: %d\n", child, exit_status); return 0;
waitpid (Kerrisk 26.1.2) pid_t waitpid(pid_t pid, int *status, int options); The first parameter allows you to indicate the PID of the child for whom you want to wait To wait for any child, which is what wait does, use -1 If options is 0, waitpid blocks when there is no new child info (like wait) If options is WNOHANG, immediately return 0 if there is no status to obtain (instead of blocking like wait)
waitpid: example (waitpid.c) #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <sys/wait.h> int main(void) { pid_t child; int status, exit_status; if ((child = fork()) == 0) { sleep(5); exit(8); while (waitpid(child, &status, WNOHANG) == 0) { printf("waiting...\n"); sleep(1); if (WIFEXITED(status)) { exit_status = WEXITSTATUS(status); printf("child %d done: %d\n", child, exit_status); return 0;
Orphan Processes What if a parent dies without waiting for all of its children? These unwaited-for children are called orphan processes Any orphan processes are adopted by a process called init (PID 1) init calls wait on them so that they can terminate and be cleaned up If a long-living parent keeps creating processes but doesn t wait on them, the process table will eventually get full
The exec Family (Kerrisk 27.1-27.2) There are six functions whose names start with exec that are used to overwrite the current process image with a new program They differ only in how they are called, not what they do. Here s one... int execl(const char *path, const char *arg,..., (char*)null); The first parameter, path, is the executable file to run Then come the commandline parameters to pass to path When path runs, those parameters are in argv[0], argv[1],...
Example: execl (execl.c) If successful, the exec functions do not return (they can t the original program is gone!). #include <unistd.h> #include <stdio.h> #include <stdlib.h> int main(void) { printf("before exec\n"); execl("/bin/ls", "ls", "-l", (char *)NULL); perror("execl"); exit(1);
Example: execv (execv.c) execv takes an array of strings instead of multiple arguments (more convenient for building commands at runtime) execlp, execvp: search path (i.e. do not have to provide full path to executable) execle, execve: accept environment variables #include <unistd.h> #include <stdio.h> #include <stdlib.h> int main(void) { char *args[] = {"ls", "-l", NULL; printf("before exec\n"); execv("/bin/ls", args); perror("execv"); exit(1);
Why Exec? Shells can use fork and exec to start new processes Process p waits for keyboard input. You type ls p forks a new process c c uses exec to run ls p waits for c to terminate, then prints a new prompt How do you think the shell can start a process in the background?
Shell Skeleton while (1) // Infinite print_prompt(); read_command(command, parameters); if (fork()) { //Parent wait(&status); else { execve(command, parameters, NULL);
File Descriptors (Kerrisk 4.1) If we open something (e.g. a file) in a process, and then fork, it is also open in the child Open files also stay open across exec... but we have to use file descriptors and low-level file I/O, not C stdio I/O To get a file descriptor (FD), use open...
open and close (Kerrisk 4.3, 4.6) int open(const char *fname, int flags, [mode_t mode]); int close(int fd); Some flags: O_RDONLY, O_WRONLY, O_RDWR (like "rb", "wb" (but without the truncation), and "r+b" to fopen) flags can also include O_CREAT which creates a file if it doesn t exist If O_CREAT is included, mode must be supplied and be set to the permissions used when the file gets created open returns an FD (a positive integer that serves a similar purpose as the FILE * returned by fopen)
read and write (Kerrisk 4.4-4.5) //Returns #bytes actually read, 0 for EOF, -1 for error ssize_t read(int fd, void *buffer, size_t maxbytes); // e.g. char buf[1024]; int num = read(stdin_fileno, buf, 1024); //Returns #bytes actually written, -1 for error ssize_t write(int fd, void *buffer, size_t maxbytes);
File Descriptors and fork (forkfd.c) FD s coming from the same open call share a common file position. int fd; char buf[6]; if ((fd = open("blah", O_RDONLY)) == -1) { perror("open"); exit(1); if (fork() == 0) { read(fd, buf, 6); write(stdout_fileno, buf, 6); else { wait(null); read(fd, buf, 6); write(stdout_fileno, buf, 6); read(fd, buf, 6); write(stdout_fileno, buf, 6); parent child filename "blah" pos 6
File Descriptors and fork... Assume the child closes an FD that it inherits from fork, and then opens the file itself: parent child filename pos filename pos "blah" 6 "blah" 6 Now if the child reads six more bytes: parent child filename pos filename pos "blah" 6 "blah" 12
dup and dup2 (Kerrisk 5.5) int dup(int oldfd); int dup2(int oldfd, int newfd); dup returns a new FD that refers to the same file as oldfd (As with fork) Both FD s share a common file position You have to close both FD s to really close the file Whereas dup chooses the new FD to use, dup2 lets you specify which new FD to use dup2 first closes newfd if it is open
Example: Output Redirection (execl2.c) #include <unistd.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> int main(void) { int fd = open("lsout", O_WRONLY O_CREAT, 0600); if (fd == -1) { perror("open"); exit(1); dup2(fd, STDOUT_FILENO); execl("/bin/ls", "ls", "-l", (char *)NULL); perror("execl"); return 1;
Inter-Process Communication After a fork, we have two independent processes They have separate address spaces (most importantly, separate copies of variables) So, the processes can t use variables to communicate They could communicate using files, but coordination is difficult We ll discuss ways for processes to communicate after reading week!