Assignment: Implementing Join


Overview

I am providing you with new versions of thread.h and thread.cc. These new versions add data members and function members to the Thread class which facilitate managing the parent and child nachos process (if any) of a Thread object.

Here is an outline of items you will need to add/modify/implement.


Merge the new versions of thread.h and thread.cc

  1. Copy the zip file, newthread.zip from my directory to your code/threads directory.

    This compressed file contains two files, thread.h and thread.cc.

  2. Important! Make sure you make backup copies of your current thread.h and thread.cc, e.g. copy these files to new files thread_old.h and thread_old.cc before doing the next step.

  3. (Did you make back up copies as in the previous step?) Now unzip the newthread.zip file:
    hawk% unzip newthread
    

    This will extract the new thread.h and thread.cc, overwriting any existing files of the same name. (Did you make back up copies?)

  4. Using the back up copies of thread.h and thread.cc, modify the new copies of thread.h and thread.cc to include any changes you have made to the original nachos versions.

Copy new versions for the List class

The new thread class uses a modified version of the List class provided with nachos. You will need to copy the new list class as well.

The new list class files, list.h and list.cc, are in the zip file, newlist.zip. Both newlist.zip and newthread.zip are in the directory:

~glancast/546class/docs

and can be copied to your code/threads directory and unzipped there.

The make utility uses file timestamps and can get confused if you simply replace list.h and list.cc but do not delete list.o in the threads directory. Since make only looks at file time stamps, it will think the old list.o is 'up to date'. To avoid this problem remove the list.o file after unzipping the new list.h and list.cc and then recompile.


Modifications to your Exec implementation

The only modification to Exec that you should have to make is to add the newly created Thread as a child of currentThread:

 Thread *t;

 ...
 currentThread->addChild(t);


Modifications to your Exit implementation

Your current implementation of Exit probably calls Thread::Finish. You will need to make some modifications to that code. The following remarks describe the changes.


Exit (a): exitStatus

One of the data member additions to the Thread class is the integer, exitStatus.

The Exit implementation should set this value with the parameter, status, passed from the user program. This can be done by using the new function member Thread::setExitStatus.

  currentThread->setExitStatus(status);

Exit (b) and (c): Process Zombie/Non-zombie Children

When a nachos user process calls Exit, all its children must be processed. Zombie children can be deleted immediately. Non-zombie children should have their parent pointer set to NULL. The new Thread::releaseChildren() member function processes all the children.

If the child is a Zombie, releaseChildren deletes the child Thread object immediately.

If the child is not a zombie, Thread::releaseChildren simply sets the child's parent pointer to NULL.


Exit (d): Delete currentThread's address space

For a user program calling Exit, the AddrSpace object pointed to by the Thread space member should be deleted.

Note that the destructor, ~AddrSpace(), must be modified to free all physical page frames allocated to this user program and then the desctructor can safely delete the pageTable and the shadowPageTable.


Exit (e): CurrentThread's parent still exists

Finaly, Exit must check whether the currentThread still has a parent or not. This can be done using the new Thread::getParent() member function which returns the value of the data member, parent. This should be NULL if the current thread's parent has already terminated. Otherwise, the parent still exists.

If the parent still exists, then the current thread, must not be completely destroyed yet.

Instead it must mark itself as a Zombie using the new Thread::setZombie(true) member function.

The parent may have already tried to Join on this thread. If so it is blocked. Or the parent may try to Join on this thread later.

In either case, the current thread that is exiting should invoke the Thread::Signal() function.

The purpose is to unblock the parent now or to prevent the parent from being blocked later.


Exit (f): CurrentThread's parent has terminated

On the other hand, if the parent of the current thread no longer exists, then the original code in Thread::Finish is sufficient to set this thread up to be completely destroyed. That code does this:

 threadToBeDestroyed = currentThread;

In either case, (e) parent still exists or (f) parent has terminated, the last statement in Thread::Finish still needs to be executed - Sleep() - as it causes the required context switch since the current thread is exiting.


Implement Join

The Join call is passed the address of the Thread object (cast as an integer) for the child on which to wait. Join should check that this parameter is indeed a child of the current thread:

Thread *t;

t = currentThread->getChild(spaceId);
  if (t == NULL)
    {
	// If NULL then spaceId was not found
        // among currentThread's children.
       ...
    }

If the parameter was not one of the current thread's children, Join should return -1 to the user program (in register 2).

Otherwise, the current thread must block itself while the the child thread is not a zombie. The member function new Thread::isZombie() can be used to do this check and the current thread can block itself by calling the new Thread::Wait() member function. (The Thread::Wait() member function does a P() operation on the Thread's semaphore which is initialized to value 0.)

When this thread is finaly unblocked (by the child when it finally exits), this thread will continue in the Join code. It should then extract the exitStatus value from the child using the new Thread::getExitStatus() member function. This is the value that Join will return to the user program (in register 2).

Finally, the current thread needs to remove this child from its list of children. It can do this by calling the new Thread::releasechild(t) member function. That function will take care of completely deleting the child thread, t, as well as removing it from the list of currentThread's children.


Write a "shell user program

You should now be able to write a useful shell user program using the system calls you have implemented: Write, Red, Exec, Join, and Exit.

One problem will be that the Mips memory (only 32 physical pages) is rather small and will restrict the degree of multiprogramming. To allow running larger user programs or more simultaneous user programs simultaneously, change the NumPhysPages definition in machine/machine.h to allow more pages. E.g., change

#define NumPhysPages    32
to
// #define NumPhysPages    32
#define NumPhysPages    64

and then recompile nachos before continuing to test your shell and other user programs.


Note on using the -rs nachos option

Also, I have suggested using the -rs option to nachos to ensure more random scheduling of user processes. However, if you want to also activate DEBUG statements, the -d option should come before the -rs option:

hawk% ../userprog/nachos -d p -rs -x shell