Join
Thread Class Additions
List Class Additions
rcsmerge
Join Details
Exec Changes
Exit Changes
Papers
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.
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.
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 ...
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. };
Here are the implementations of the Wait and Signal member functions added to Thread:
void Thread::Wait() { joinSem->P(); } void Thread::Signal() { joinSem->V(); }
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.
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 };
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
};
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();
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,
The Exec system call will need to be slightly modified to add something like this:
t = new Thread(exeFileName); currentThread->addChild(t);
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();