SpaceId Exec(char *executable)
SpaceId Exec(char *executable, int argc, char** argv, int willJoin);

// Create a user process by creating a new address space, initializing
// it from the executable program file, and starting a new thread (via
// Thread::Fork ) to run within it. To start execution of the child
// process, the kernel sets up the CPU state for the new process and
// then calls Machine::Run to start the machine simulator executing the
// specified program's instructions in the context of the newly created
// address space. Note that Nachos Exec combines the Unix fork and exec
// system calls: Exec both creates a new process (like Unix fork ) and
// executes a specified program within its context (like Unix exec ).

// Exec returns a unique SpaceId identifying the child user process. The
// SpaceId can be passed as an argument to other system calls (e.g.,
// Join ) to identify the process, thus it must be unique among all
// currently existing processes. However, your kernel should be able to
// recycle SpaceId values so that it does not run out of them. By
// convention, the SpaceId 0 will be used to indicate an error.

// "argc" is the number of arguments, "argv" is the array of argument
// strings, and "willJoin" determines whether this process will be joined
// by another. The semantics of argc and argv are exactly like standard
// C. The semantics of the "willJoin" argument are the same as for Nachos
// threads -- non-zero means that a process will eventually Join on the one
// being Exec'ed. If you do not implement the Join system call, you should
// pass in "0" for this argument.

// willJoin parameter (pipectrl)

// The Exec system call includes a pipectrl argument as defined in Process
// Management. This argument is used to direct the optional binding of
// OpenFileIds 0 (stdin) and 1 (stdout) to pipes rather than the
// console. This allows a process to create strings of child processes
// joined by a pipeline. A pipeline is a sequence of pipes, each with one
// reader and one writer. The first process in the pipeline has stdin bound
// to the console and stdout bound to the pipeline input. Processes in the
// middle of the pipeline have both stdin and stdout bound to pipes. The
// process at the end of the pipe writes its stdout to the console.

// The Nachos interface for creating pipes is much simpler and less
// flexible than Unix. A parent process can use nonzero values of the
// pipectrl argument to direct that its children are to be strung out in a
// pipeline in the order in which they are created. A pipectrl value of 1
// indicates that the process is the first process in the pipeline. A
// pipectrl value of 2 indicates a process in the middle of the pipeline;
// stdin is bound to the output of the preceding child, and stdout is bound
// to the input of the next child process to be created. A pipectrl value
// of 3 indicates that the process is at the end of the pipeline.

// To handle these pipectrl values, the kernel must keep a list of all
// children of each process in the order that they are created.

// Pipes are implemented as producer/consumer bounded buffers with a
// maximum buffer size of N bytes. If a process writes to a pipe that is
// full, the Write call blocks until the pipe has drained sufficiently to
// allow the write to continue. If a process reads from a pipe that is
// empty, the Read call blocks until the sending process exits or writes
// data into the pipe. If a process at either end of a pipe exits, the pipe
// is said to be broken : reads from a broken pipe drain the pipe and then
// stop returning data, and writes to a broken pipe silently discard the
// data written.

// Exec interprets willJoin to indicate joining and pipe control as follows:

//    willJoin & 0x1: child will be Joined 
//    willJoin & 0x2: bind stdout to a pipe 
//    willJoin & 0x4: bind stdin to a pipe 
// In other words, for a child that will not be Joined willJoin=0x2
// corresponds to pipectrl=1, willJoin=0x6 corresponds to pipectrl=2, and
// willJoin=0x4 corresponds to pipectrl=3.

void Exit(int status)

// A user process calls Exit to indicate that it is finished and ready
// to terminate. The user program may call Exit explicitly, or it may
// simply return from main , since the common runtime library routine
// (in start.s ) that calls main to start the program also calls Exit
// when main returns. The kernel handles an Exit system call by
// destroying the process data structures and thread(s), reclaiming any
// memory assigned to the process, and arranging to return the exit
// status value as the result of the Join on this process, if any. Note
// that other processes are not affected: do not confuse Exit with Halt
// .

// Note : if you are implementing threads (Threads), then Exit destroys
// the calling thread rather than the entire process/address space. The
// process and its address space are destroyed only when the last thread
// calls Exit , or if one of its threads generates a fatal exception.

// Warning : the Exit system call should never return; it should always
// destroy the calling thread. Returning from Exit may cause your Nachos
// system to mysteriously shut down. To see why, look at the startup
// instruction sequence in test/start.s .

int Join(SpaceId joineeId)

// This is called by a process (the joiner ) to wait for the termination
// of the process (the joinee ) whose SpaceId is given by the joineeId
// argument. If the joinee is still active, then Join blocks until the
// joinee exits. When the joinee has exited, Join returns the joinee's
// exit status to the joiner. To simplify the implementation, impose the
// following limitations on Join : the joiner must be the parent of the
// joinee, and each joinee may be joined on at most once. Nachos Join is
// basically equivalent to the Unix wait system call.

void Write(char *buffer, int size, OpenFileId id);

// Write "size" bytes from "buffer" to the open file.

int Read(char *buffer, int size, OpenFileId id);

// Read "size" bytes from the open file into "buffer".  
// Return the number of bytes actually read -- if the open file isn't
// long enough, or if it is an I/O device, and there aren't enough 
// characters to read, return whatever is available (for I/O devices, 
// you should always wait until you can return at least one character)