![]() |
Faculty of Engineering and Information Technology (FEIT)
Department of Computer Science
|
Laboratory 7Fighting Zombies with Forks and PipesYou are expected to use the man pages! Remember that man pages with the same title may appear in multiple sections. If you want to search all sections use man -a. Process IdentifiersEvery Unix process is associated with a unique non-negative integer corresponding to its process ID. There are some special processes, such as process ID 0 that is usually associated with the scheduler, while process ID 1 (remember this number) is usually the init process that is invoked by the kernel at the end of the bootstrap procedure. Two useful functions that we will use in this lab are: pid_t getpid(void); pid_t getppid(void);ACTIONS
The fork() FunctionThe only way a new process is created by the Unix kernel is when an existing process calls the fork() function. This function is called once but returns twice as two processes - the parent and the child process. The only difference between the two processes is the value of the return code, in the parent the return code gives the PID of the child, while in the return value of the child is 0. A small example code is given below:
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
int main(void) {
pid_t fork_pid;
printf("Hello from main\n");
fork_pid = fork();
printf("After fork, fork_pid=%d\n", fork_pid);
return 0;
}
ACTIONS
The wait() and waitpid() FunctionsWhen a process terminates, either normally or abnormally, the parent is notified by the kernel sending the parent the SIGCHLD signal. (What's a signal you ask - do man 7 signal, and/or ask your tutor). The parent can choose to ignore this signal, or it can provide a function that is called when the signal occurs (an `signal handler' function). The default action is for this signal to be ignored. The functions wait() and waitpid() permit the parent to check on the termination status of its children. These functions do one of the following:
In the following code, we create two processes. The child process is forced to sleep for some period (5 seconds - see man sleep). To verify that the process is sleeping we have inserted a number of timestamps.
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/time.h>
#include <sys/wait.h>
#define SLEEP_TIME 5
double get_elapsed_time();
int main(void) {
pid_t fork_pid;
double elp_time;
elp_time = get_elapsed_time();
printf("Hello from main %14.6lf\n",elp_time);
fork_pid = fork();
if (fork_pid == 0) {
elp_time = get_elapsed_time();
printf("1st Hello from Child %14.6lf\n", elp_time);
sleep(SLEEP_TIME);
elp_time = get_elapsed_time();
printf("2nd Hello from Child %14.6lf\n", elp_time);
} else {
elp_time = get_elapsed_time();
printf("1st Hello from Parent %14.6lf\n", elp_time);
elp_time = get_elapsed_time();
printf("2nd Hello from Parent %14.6lf\n", elp_time);
}
elp_time = get_elapsed_time();
printf("Good bye from %s %14.6lf\n", fork_pid==0? "Child": "Parent",
elp_time);
return 0;
}
double get_elapsed_time(void) {
/* Routine to get wall clock time since first call */
struct timeval mytime;
static double start_time=0.0e0;
double new_time;
/* this function gets seconds and microseconds since the epoch */
gettimeofday(&mytime, NULL);
new_time = (double)mytime.tv_sec + mytime.tv_usec*1.0e-06;
if (start_time <= 0.0e0) start_time = new_time;
return new_time-start_time;
}
ZombiesIn Unix terminology, a process that has terminated, but whose parent has not yet waited for it, is called a zombie, i.e. it is a process that is not quite dead - it can no longer execute instructions, but information concerning this process is still taking up space in the process tables. The following code creates a zombie child.
ACTIONS
#include <unistd.h>
#include <stdio.h>
int main(void) {
printf("Hello from main \n");
if (fork() != 0) {
/* THIS IS THE PARENT */
printf("Hello from Parent\n");
sleep(4);
} else {
printf("Parent PID of Child before sleep %d\n", getppid());
sleep(2);
printf("Parent PID of Child after sleep %d\n", getppid());
}
return 0;
}
The exec FunctionsAs demonstrated above, fork() enables the user to create new processes. These processes are, however, a clone of the calling process. If we want to create a new process running a different executable we have to follow the initial fork() command with a call to one of the exec family of functions (essentially members of this family differ in how they locate the executable, the treatment of command line arguments, and their treatment of current execution environment. Do man exec to view the details). When a process calls exec(), that process is completely replaced by the new program, and the new program starts executing at its main() function. The process ID does not change, but the current process (its text, data, heap and stack segments) are replaced with a brand new program from disk. The following gives a short example of the use of exec().
#include <unistd.h>
#include <stdio.h>
int main(void) {
int status;
if (fork()) {
// Parent
} else {
// Child
printf(" HELLO from Child \n");
status = execl("/bin/ls", "ls", "-l", "-t", (char *) NULL);
if (status != 0)
perror("execl error:");
}
return 0;
}
ACTIONS
The pipe() FunctionUnix IPC (Interprocess Communication) has been, and continues to be, a hodgepodge of different approaches, few of which are portable across all Unix implementations. .... About the only form of IPC that we can count on, regardless of the Unix implementation, is half duplex pipes (Stevens, Advanced Programming in the UNIX Environment). Although common to all flavors of Unix, pipes have two limitations:
A pipe is like an I/O buffer administered by the kernel through which data can flow. We create a pipe by a call to the pipe() function. Two file descriptors are returned, one that is used for reading and one that is used for writing to the pipe. The following illustrates a pipe in a single process.
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
int main(void) {
int status, nbytes;
char string1[] = "Hello Mate";
char string2[] = "Get Lost Mate";
int my_pipe[2];
int len1, len2;
status = pipe(my_pipe);
len1 = strlen(string1);
len2 = strlen(string2);
printf("initial string1: \"%s\", length %d\n", string1, len1);
printf("initial string2: \"%s\", length %d\n", string2, len2);
nbytes = write(my_pipe[1], string1, len1);
printf("write(): %d bytes\n", nbytes);
nbytes = read(my_pipe[0], string2, len2);
printf("read(): %d bytes\n", nbytes);
printf("read(): dest. string: \"%s\"\n", string2);
return 0;
}
The program declares two character strings that are initialized to some
random strings. The program writes character string string1 to
the pipe and then reads from the pipe into character string
string2.
ACTIONS
In a single process a pipe is next to useless. Normally they are used together with fork to provide an IPC channel between a parent and child process. To do this two pipes (A and B) are defined before the process issues a fork() call - this gives rise to 4 file descriptors; fd_A_read, fd_A_write, fd_B_read and fd_B_write. After forking, both the parent and child has copies of all these file descriptors. To turn pipe A into a channel that provides communication from:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
int main(void) {
char string1[] = "Hello Mate";
char string2[ ]= "XXXXXXXXXX";
char string3[] = "*Get Lost*";
int fd_pc[2], fd_cp[2];
int status;
pipe(fd_pc);
pipe(fd_cp);
if (fork()) {
/* PARENT */
close(fd_pc[0]); /* close read pc */
close(fd_cp[1]); /* close write cp */
write(fd_pc[1], string1, strlen(string1));
printf("PARENT - string2 BEFORE pipe: %s\n", string2);
read(fd_cp[0], string2, strlen(string1));
printf("PARENT - string2 AFTER pipe: %s\n", string2);
wait(&status);
} else {
/* CHILD */
close(fd_pc[1]); /* close write pc*/
close(fd_cp[0]); /* close read cp*/
printf("CHILD - string2 BEFORE pipe: %s\n", string2);
read(fd_pc[0], string2, strlen(string1));
printf("CHILD - string2 AFTER pipe: %s\n", string2);
write(fd_cp[1], string3, strlen(string3));
}
return -1;
}
ACTIONS
Next LabSelect and Sockets in Unix
|
|
Please direct all enquiries to: Alistair.Rendell@anu.edu.au Page authorised by: Head of Department, DCS |
| The Australian National University — CRICOS Provider Number 00120C |