CSC546 - Operating Systems
(Lecture 9)


Topics

Join

Thread Class Additions

List Class Additions

rcsmerge

Join Details

Exec Changes

Exit Changes

Papers


Nachos Join System Call

Here is the declaration of the Join system call:

/* Only return once the the user program "id" has finished.
 * Return the exit status.
 */
int Join(SpaceId id);

a. Which user program is meant by id is not exactly clear from this description. We will assume that the user program should be a child of the nachos calling process.

b. Join should return -1 if the id parameter is not the id of one of currentThread's children.

c. Otherwise, Join will need to get a pointer to the Thread object represented by id.

d. Join must then check if this child thread is a zombie.

e. The parent must wait if the child is not a zombie. This will cause a context switch. The parent will be awaken if one of its children subsequently finishes.

f. But is the child that finished the one specified by the id passed to Join? Perhaps not.

g. So the parent must check again when it is awaken if this child thread is now a zombie.

h. Once the child designated by id has finished, Join should get the child's exitStatus value. This is the value that Join returns to the parent.

Is it possible that the child is the threadToBeDestroyed? It may depend on how Exit is implemented.

i. This child should be removed from the parent's list of children and the Thread object deleted.


Additions to Thread Class

A modified version of the Thread class (which I will provide) follows. The purpose of these modifications is to facilitate Unix style implementation of Nachos Exit and Join.

Added include Files

The Join system call will need to block the calling Nachos process until notification that one of its children has terminated. Nachos semaphores will be used to block the process.

When a Nachos process exits, all its terminated children can be completely removed. So a list of all children is needed. This is why the list.h include file is added.

Here are the additional include files needed in thread.h:

// Additional include files
#include "list.h"
#include "synch.h" // 
class Semaphore;   // Forward declaration; needed for synch.cc
                   // since synch.cc incs synch.h incs thread.h
...

Modified Thread Class Definition

class Thread {
  private: 
    ...

  public:
    ...

    // Additions
    ThreadStatus getStatus() { return status; }
    void setParent(Thread *t) { parent = t; }
    Thread * getParent() { return parent; }
    void addChild(Thread *t) { children.Append(t);  t->setParent(this);}
    void releaseChildren();
    void releaseChild(Thread *t);
    Thread * getChild(int id);

    bool isZombie() const { return _zombie; }
    void setZombie(bool b) { _zombie = b; }
    int getExitStatus() const { return exitStatus; }
    void setExitStatus(int val) { exitStatus = val;}
    void Wait();
    void Signal();
    // End of Additions

  private:
    // Additions
    List  children;
    Thread * parent;
    Semaphore *joinSem;
    int exitStatus;
    bool _zombie;

    // End additions.
};

Additions: Thread::Wait and Thread::Signal

Here are the implementations of the Wait and Signal member functions added to Thread:

void Thread::Wait()
{
   joinSem->P();
}

void Thread::Signal()
{
  joinSem->V();
}

Nachos List Class or STL list Class?

The nachos code comes with a List class in the threads directory.

I would have liked to use this class without modification to represent the list of children for each Thread object. However, the interface doesn't support easy removal from the middle of the list.

The next idea was to use the standard template library list class since it provides all the operations one would need.

However, this conflicts with nachos existing classes and use of standard c++ classes for i/o, strings, etc.

Conclusion: Modify the nachos List class slightly.


Original Nachos List Class

class List {
  public:
    List();                     // initialize the list
    ~List();                    // de-allocate the list

    void Prepend(void *item);   // Put item at the beginning of the list
    void Append(void *item);    // Put item at the end of the list
    void *Remove();             // Take item off the front of the list

    void Mapcar(VoidFunctionPtr func);  // Apply "func" to every element
                                        // on the list
    bool IsEmpty();             // is the list empty?


    // Routines to put/get items on/off list in order (sorted by key)
    void SortedInsert(void *item, int sortKey); // Put item into list
    void *SortedRemove(int *keyPtr);            // Remove first item from list

  private:
    ListElement *first;         // Head of the list, NULL if list is empty
    ListElement *last;          // Last element of list
};

List Class Additions

class List {
  public:
    List();                     // initialize the list
    ~List();                    // de-allocate the list

    void Prepend(void *item);   // Put item at the beginning of the list
    void Append(void *item);    // Put item at the end of the list
    void *Remove();             // Take item off the front of the list


    void Mapcar(VoidFunctionPtr func);  // Apply "func" to every element
                                        // on the list
    bool IsEmpty();             // is the list empty?


    // Routines to put/get items on/off list in order (sorted by key)
    void SortedInsert(void *item, int sortKey); // Put item into list
    void *SortedRemove(int *keyPtr);            // Remove first item from list
  class Iterator {
    friend class List;
    ListElement *ptr;
  public:
    Iterator() { ptr = 0; }
    Iterator(ListElement *p);
    void * operator*() {
      ASSERT(ptr != NULL);
      return ptr->item;
    }
    Iterator& operator++();   // prefix operator
    Iterator operator++(int); // postfix operator
    bool operator!=(const Iterator& it) const;
    bool operator==(const Iterator& it) const;
  };

  Iterator Erase(Iterator it); // Unlink and deallocate item
                                // pointed to by it, return iterator
                                // positioned at next item.
  Iterator begin();
  Iterator end();

  private:
    ListElement *first;         // Head of the list, NULL if list is empty
    ListElement *last;          // Last element of list
};

Join Details

The nachos code comes with a List class (in the threads directory) which is used to represent the list of children of a nachos process in the Thread class:

List children;

In the implementation of Join, the following code could use the additions to Thread to check if currentThread is the parent of the child with id passed to Join.

Thread *t;
...
t = currentThread->getChild(spaceId);
if ( t == NULL ) // No such child

What should this code do if no such child exists?

To check if this child is a zombie

t->isZombie()

returns true if this child is a zombie.

If t is a child, but not a zombie, the currentThread must wait for t to finish. It can do so by:

t->Wait();

Join Details (cont.)

When the waited for child has terminated, the parent should remove the child from its list and delete it by:

currentThread->releaseChild( t );

The addition, Thread::releaseChild,


Exec Changes

The Exec system call will need to be slightly modified to add something like this:

  t = new Thread(exeFileName);
  currentThread->addChild(t);

Exit Changes

Exit will need to be modified also.

  currentThread->setExitStatus(status); // Why?
  ...
  releaseChildren();

// Remember: space is only defined when USER_PROGRAM is defined
  if (space != NULL)
    {
      delete space;
      space = NULL;
    }
  if (getParent() != NULL)
    {
      setZombie(true);
      Signal();
    }
  else
    {
      threadToBeDestroyed = currentThread;
    }
  Sleep();     

Papers