CSC 325 Operating Systems with an Emphasis on UNIX
Assignment 9

This assignment will introduce students to C++ programming, compiling, linking, and running in the Linux environment. It will look more deeply at processes and multi-processing.

  1. Use ssh to logon to your Linux account on cs.berea.edu.

  2. Do you remember what the first program that we wrote in CSC 306 was? Right, you guessed it, it was "Hello World". We are going to get back to basics and create "Hello World" again. Put the following into text file named hello.cpp. (One way to easily do this is to use vi hello.cpp. Then just copy and paste the code into vi.)
    
    //hello.cpp
    #include <iostream>
    using namespace std;
    
    int main()
    {
         cout << "hello world" << endl;
    
         return(0);
    }
    
    To compile and link this code, type g++ hello.cpp -o hello. Then to run this program type ./hello. Exciting huh?

  3. Okay, now that you know how to compile, link and run C++ in Linux, let's get back to looking at operating systems. Linux provides several facilities for implementing a program allows concurrent (parallel) processing of data. The simplest facility is the fork() system call. Read about this call in man fork.

    The fork() system call is defined in the header files <sys/types.h> and <unistd.h>. It creates an additional process. The created process is referred to as the child, and the process that called fork() is referred to as the parent. These two processes are identical copies of one another, except for their process ID (PID). We have seen this using the top command. Following the execution of the fork() call, both processes resume execution and the next instruction.

    The child has copies of ALL variables that were declared in the parent. The values of these variables are the same as their values in the parent at the time of the fork(). The processes also share the same file handles. While the file handles are copies of the originals, the resource that they point to remains the same. This introduces the problem of process synchronization. The results of two or more processes accessing the same resource simultaneously is unpredictable. Part of process synchronization deals with ensuring that this does not occur. This will be discussed in detail in our text in Chapter 7.

    A process is also allowed to load program into its memory space, overwriting the original program it was executing. This is typically used in conjuction with fork() to spawn a process to execute an external program whose return value and/or output the parent is interested in. This functionality is achieved by using of the exec() calls. The most common example of this is your terminal shell. When you execute a program, your shell forks a new copy of itself and passes the command line you entered to the child. The child then locates the program you specified and issues an exec call to load it into its memory, and passes the remainder of the command line you entered as arguements to the new program.

    Use ssh to logon to your Linux account on cs.berea.edu in a second session. Place the two session windows on your screen in such a way that you can see both sessions. Run top in one of your shell sessions. Then in the top session, u username so you can see only own processes.

  4. Next, save the following code as fork1.cpp.

    
    //fork1.cpp
    #include <iostream>
    #include <sys/types.h>
    #include <sys/wait.h>
    #include <stdlib.h>
    using namespace std;
    
    int main()
    {
    const int BASE_PROCESSES = 3;
    
      int i, j;
    
      for(i = 0; i < BASE_PROCESSES; i++)
        {
          j = fork();
          if(j == 0)
            {
              cout << "My ppid is " << getppid();
              cout << " and my pid is " << getpid() << "." << endl;
              sleep(2);
            }
          else
            wait(&j);
        }
    
      return (0);
    }
    
    Compile this code with fork1.cpp -o fork1. Then run it with ./fork1 while watching what is happening in the top session window.

  5. Read the above code and try to understand what is happening on each line. You may want to reread man fork and you may want to visit info wait.

  6. The exec family of functions replaces the current process image with a new process image. The functions described in this manual page are front-ends for the function execve(2). See the manual page for execve and execlp for detailed information about the replacement of the current process.

    Our final example is based on the code from fig 4.8 of "Operating System Concepts"

            //fork2.cpp
    	#include <iostream>
    	
    	using namespace std;
    	
    	int main()
    	{
    		int pid;
    	
    		/* fork another process */
    		pid = fork();
    	
    		if (pid < 0)
    		{ 
    			/* error occurred */
    			cout << "Fork Failed\n";
    			exit(-1);
    		}
    		else if (pid == 0)
    		{ 
    			/* child process */
    			execlp("/bin/ls","ls",NULL);
    		}
    		else
    		{ 
    			/* parent process */
    			/* parent will wait for the child to complete */
    			wait();
    			
    			cout << "Child Complete\n";
    			exit(0);
    		}
    		return 0;
    	}
    	
    Save this code as fork2.cpp. Then compile and run it. Again, try to understand each line of this code.


Problem:

Your task is to create a multi-processing "Hello World" program. Your program should: