************************************************************************** Nachos code for Com Sci 230/330 final exam, winter 1995 ************************************************************************** CONTENTS: Thread Dispatching thread.h 2 - 4 thread.cc 5 - 10 scheduler.h 11 scheduler.cc 11 - 13 switch.h 14 - 16 switch.s 16 - 21 timer.h 22 timer.cc 23 - 24 Bootstrapping the System main.cc 25 - 26 system.h 27 system.cc 28 - 30 Synchronization synch.h 30 - 33 synch.cc 33 - 34 User Address Space addrspace.h 35 addrspace.cc 36 - 39 syscall.h 39 - 40 exception.cc 41 filesys.h 42 openfile.h 43 openfile.cc 44 - 47 sysdep.h 48 sysdep.cc 49 - 55 translate.h 56 translate.cc 57 - 61 Machine Interface machine.h 62 - 65 Utilities utility.h 66 list.h 67 bitmap.h 68 Copyright (c) 1992-1993 The Regents of the University of California. All rights reserved. See copyright.h for copyright notice and limitation of liability and disclaimer of warranty provisions. ********************** ****** THREAD.H ****** ********************** // thread.h // Data structures for managing threads. A thread represents // sequential execution of code within a program. // So the state of a thread includes the program counter, // the processor registers, and the execution stack. // // Note that because we allocate a fixed size stack for each // thread, it is possible to overflow the stack -- for instance, // by recursing to too deep a level. The most common reason // for this occuring is allocating large data structures // on the stack. For instance, this will cause problems: // // void foo() { int buf[1000]; ...} // // Instead, you should allocate all data structures dynamically: // // void foo() { int *buf = new int[1000]; ...} // // // Bad things happen if you overflow the stack, and in the worst // case, the problem may not be caught explicitly. Instead, // the only symptom may be bizarre segmentation faults. (Of course, // other problems can cause seg faults, so that isn't a sure sign // that your thread stacks are too small.) // // One thing to try if you find yourself with seg faults is to // increase the size of thread stack -- ThreadStackSize. // // In this interface, forking a thread takes two steps. // We must first allocate a data structure for it: "t = new Thread". // Only then can we do the fork: "t->fork(f, arg)". #ifndef THREAD_H #define THREAD_H #include "utility.h" #ifdef USER_PROGRAM #include "machine.h" #include "addrspace.h" #endif ****** THREAD.H ****** // CPU register state to be saved on context switch. // The SPARC and MIPS only need 10 registers, but the Snake needs 18. // For simplicity, this is just the max over all architectures. #define MachineStateSize 18 // Size of the thread's private execution stack. // WATCH OUT IF THIS ISN'T BIG ENOUGH!!!!! #define StackSize (4 * 1024) // in words // Thread state enum ThreadStatus { JUST_CREATED, RUNNING, READY, BLOCKED }; // external function, dummy routine whose sole job is to call Thread::Print extern void ThreadPrint(int arg); // The following class defines a "thread control block" -- which // represents a single thread of execution. // // Every thread has: // an execution stack for activation records ("stackTop" and "stack") // space to save CPU registers while not running ("machineState") // a "status" (running/ready/blocked) // // Some threads also belong to a user address space; threads // that only run in the kernel have a NULL address space. class Thread { private: // NOTE: DO NOT CHANGE the order of these first two members. // THEY MUST be in this position for SWITCH to work. int* stackTop; // the current stack pointer int machineState[MachineStateSize]; // all registers except for stackTop public: Thread(char* debugName); // initialize a Thread ~Thread(); // deallocate a Thread // NOTE -- thread being deleted // must not be running when delete // is called // basic thread operations void Fork(VoidFunctionPtr func, int arg); // Make thread run (*func)(arg) void Yield(); // Relinquish the CPU if any // other thread is runnable void Sleep(); // Put the thread to sleep and // relinquish the processor void Finish(); // The thread is done executing void CheckOverflow(); // Check if thread has // overflowed its stack void setStatus(ThreadStatus st) { status = st; } char* getName() { return (name); } void Print() { printf("%s, ", name); } ****** THREAD.H ****** private: // some of the private data for this class is listed above int* stack; // Bottom of the stack // NULL if this is the main thread // (If NULL, don't deallocate stack) ThreadStatus status; // ready, running or blocked char* name; void StackAllocate(VoidFunctionPtr func, int arg); // Allocate a stack for thread. // Used internally by Fork() #ifdef USER_PROGRAM // A thread running a user program actually has *two* sets of CPU registers -- // one for its state while executing user code, one for its state // while executing kernel code. int userRegisters[NumTotalRegs]; // user-level CPU register state public: void SaveUserState(); // save user-level register state void RestoreUserState(); // restore user-level register state AddrSpace *space; // User code this thread is running. #endif }; // Magical machine-dependent routines, defined in switch.s extern "C" { // First frame on thread execution stack; // enable interrupts // call "func" // (when func returns, if ever) call ThreadFinish() void ThreadRoot(); // Stop running oldThread and start running newThread void SWITCH(Thread *oldThread, Thread *newThread); } #endif // THREAD_H *********************** ****** THREAD.CC ****** *********************** // thread.cc // Routines to manage threads. There are four main operations: // // Fork -- create a thread to run a procedure concurrently // with the caller (this is done in two steps -- first // allocate the Thread object, then call Fork on it) // Finish -- called when the forked procedure finishes, to clean up // Yield -- relinquish control over the CPU to another ready thread // Sleep -- relinquish control over the CPU, but thread is now blocked. // In other words, it will not run again, until explicitly // put back on the ready queue. #include "thread.h" #include "switch.h" #include "synch.h" #include "system.h" #define STACK_FENCEPOST 0xdeadbeef // this is put at the top of the // execution stack, for detecting // stack overflows //---------------------------------------------------------------------- // Thread::Thread // Initialize a thread control block, so that we can then call // Thread::Fork. // // "threadName" is an arbitrary string, useful for debugging. //---------------------------------------------------------------------- Thread::Thread(char* threadName) { name = threadName; stackTop = NULL; stack = NULL; status = JUST_CREATED; #ifdef USER_PROGRAM space = NULL; #endif } //---------------------------------------------------------------------- // Thread::~Thread // De-allocate a thread. // // NOTE: the current thread *cannot* delete itself directly, // since it is still running on the stack that we need to delete. // // NOTE: if this is the main thread, we can't delete the stack // because we didn't allocate it -- we got it automatically // as part of starting up Nachos. //---------------------------------------------------------------------- Thread::~Thread() { DEBUG('t', "Deleting thread \"%s\"\n", name); ASSERT(this != currentThread); if (stack != NULL) DeallocBoundedArray((char *) stack, StackSize * sizeof(int)); } ****** THREAD.CC ****** //---------------------------------------------------------------------- // Thread::Fork // Invoke (*func)(arg), allowing caller and callee to execute // concurrently. // // NOTE: although our definition allows only a single integer argument // to be passed to the procedure, it is possible to pass multiple // arguments by making them fields of a structure, and passing a pointer // to the structure as "arg". // // Implemented as the following steps: // 1. Allocate a stack // 2. Initialize the stack so that a call to SWITCH will // cause it to run the procedure // 3. Put the thread on the ready queue // // "func" is the procedure to run concurrently. // "arg" is a single argument to be passed to the procedure. //---------------------------------------------------------------------- void Thread::Fork(VoidFunctionPtr func, int arg) { DEBUG('t', "Forking thread \"%s\" with func = 0x%x, arg = %d\n", name, (int) func, arg); StackAllocate(func, arg); IntStatus oldLevel = interrupt->SetLevel(IntOff); scheduler->ReadyToRun(this); // ReadyToRun assumes that interrupts // are disabled! (void) interrupt->SetLevel(oldLevel); } //---------------------------------------------------------------------- // Thread::CheckOverflow // Check a thread's stack to see if it has overrun the space // that has been allocated for it. If we had a smarter compiler, // we wouldn't need to worry about this, but we don't. // // NOTE: Nachos will not catch all stack overflow conditions. // In other words, your program may still crash because of an overflow. // // If you get bizarre results (such as seg faults where there is no code) // then you *may* need to increase the stack size. You can avoid stack // overflows by not putting large data structures on the stack. // Don't do this: void foo() { int bigArray[10000]; ... } //---------------------------------------------------------------------- void Thread::CheckOverflow() { if (stack != NULL) #ifdef HOST_SNAKE // Stacks grow upward on the Snakes ASSERT(stack[StackSize - 1] == STACK_FENCEPOST); #else ASSERT(*stack == STACK_FENCEPOST); #endif } ****** THREAD.CC ****** //---------------------------------------------------------------------- // Thread::Finish // Called by ThreadRoot when a thread is done executing the // forked procedure. // // NOTE: we don't immediately de-allocate the thread data structure // or the execution stack, because we're still running in the thread // and we're still on the stack! Instead, we set "threadToBeDestroyed", // so that Scheduler::Run() will call the destructor, once we're // running in the context of a different thread. // // NOTE: we disable interrupts, so that we don't get a time slice // between setting threadToBeDestroyed, and going to sleep. //---------------------------------------------------------------------- void Thread::Finish () { (void) interrupt->SetLevel(IntOff); ASSERT(this == currentThread); DEBUG('t', "Finishing thread \"%s\"\n", getName()); threadToBeDestroyed = currentThread; Sleep(); // invokes SWITCH // not reached } //---------------------------------------------------------------------- // Thread::Yield // Relinquish the CPU if any other thread is ready to run. // If so, put the thread on the end of the ready list, so that // it will eventually be re-scheduled. // // NOTE: returns immediately if no other thread on the ready queue. // Otherwise returns when the thread eventually works its way // to the front of the ready list and gets re-scheduled. // // NOTE: we disable interrupts, so that looking at the thread // on the front of the ready list, and switching to it, can be done // atomically. On return, we re-set the interrupt level to its // original state, in case we are called with interrupts disabled. // // Similar to Thread::Sleep(), but a little different. //---------------------------------------------------------------------- void Thread::Yield () { Thread *nextThread; IntStatus oldLevel = interrupt->SetLevel(IntOff); ASSERT(this == currentThread); DEBUG('t', "Yielding thread \"%s\"\n", getName()); nextThread = scheduler->FindNextToRun(); if (nextThread != NULL) { scheduler->ReadyToRun(this); scheduler->Run(nextThread); } (void) interrupt->SetLevel(oldLevel); } ****** THREAD.CC ****** //---------------------------------------------------------------------- // Thread::Sleep // Relinquish the CPU, because the current thread is blocked // waiting on a synchronization variable (Semaphore, Lock, or Condition). // Eventually, some thread will wake this thread up, and put it // back on the ready queue, so that it can be re-scheduled. // // NOTE: if there are no threads on the ready queue, that means // we have no thread to run. "Interrupt::Idle" is called // to signify that we should idle the CPU until the next I/O interrupt // occurs (the only thing that could cause a thread to become // ready to run). // // NOTE: we assume interrupts are already disabled, because it // is called from the synchronization routines which must // disable interrupts for atomicity. We need interrupts off // so that there can't be a time slice between pulling the first thread // off the ready list, and switching to it. //---------------------------------------------------------------------- void Thread::Sleep () { Thread *nextThread; ASSERT(this == currentThread); ASSERT(interrupt->getLevel() == IntOff); DEBUG('t', "Sleeping thread \"%s\"\n", getName()); status = BLOCKED; while ((nextThread = scheduler->FindNextToRun()) == NULL) interrupt->Idle(); // no one to run, wait for an interrupt scheduler->Run(nextThread); // returns when we've been signalled } //---------------------------------------------------------------------- // ThreadFinish, InterruptEnable, ThreadPrint // Dummy functions because C++ does not allow a pointer to a member // function. So in order to do this, we create a dummy C function // (which we can pass a pointer to), that then simply calls the // member function. //---------------------------------------------------------------------- static void ThreadFinish() { currentThread->Finish(); } static void InterruptEnable() { interrupt->Enable(); } void ThreadPrint(int arg){ Thread *t = (Thread *)arg; t->Print(); } ****** THREAD.CC ****** //---------------------------------------------------------------------- // Thread::StackAllocate // Allocate and initialize an execution stack. The stack is // initialized with an initial stack frame for ThreadRoot, which: // enables interrupts // calls (*func)(arg) // calls Thread::Finish // // "func" is the procedure to be forked // "arg" is the parameter to be passed to the procedure //---------------------------------------------------------------------- void Thread::StackAllocate (VoidFunctionPtr func, int arg) { stack = (int *) AllocBoundedArray(StackSize * sizeof(int)); #ifdef HOST_SNAKE // HP stack works from low addresses to high addresses stackTop = stack + 16; // HP requires 64-byte frame marker stack[StackSize - 1] = STACK_FENCEPOST; #else // i386 & MIPS & SPARC stack works from high addresses to low addresses #ifdef HOST_SPARC // SPARC stack must contains at least 1 activation record to start with. stackTop = stack + StackSize - 96; #else // HOST_MIPS || HOST_i386 stackTop = stack + StackSize - 4; // -4 to be on the safe side! #ifdef HOST_i386 // the 80386 passes the return address on the stack. In order for // SWITCH() to go to ThreadRoot when we switch to this thread, the // return addres used in SWITCH() must be the starting address of // ThreadRoot. *(--stackTop) = (int)ThreadRoot; #endif #endif // HOST_SPARC *stack = STACK_FENCEPOST; #endif // HOST_SNAKE machineState[PCState] = (int) ThreadRoot; machineState[StartupPCState] = (int) InterruptEnable; machineState[InitialPCState] = (int) func; machineState[InitialArgState] = arg; machineState[WhenDonePCState] = (int) ThreadFinish; } #ifdef USER_PROGRAM #include "machine.h" //---------------------------------------------------------------------- // Thread::SaveUserState // Save the CPU state of a user program on a context switch. // // Note that a user program thread has *two* sets of CPU registers -- // one for its state while executing user code, one for its state // while executing kernel code. This routine saves the former. //---------------------------------------------------------------------- void Thread::SaveUserState() { for (int i = 0; i < NumTotalRegs; i++) userRegisters[i] = machine->ReadRegister(i); } ****** THREAD.CC ****** //---------------------------------------------------------------------- // Thread::RestoreUserState // Restore the CPU state of a user program on a context switch. // // Note that a user program thread has *two* sets of CPU registers -- // one for its state while executing user code, one for its state // while executing kernel code. This routine restores the former. //---------------------------------------------------------------------- void Thread::RestoreUserState() { for (int i = 0; i < NumTotalRegs; i++) machine->WriteRegister(i, userRegisters[i]); } #endif ************************* ****** SCHEDULER.H ****** ************************* // scheduler.h // Data structures for the thread dispatcher and scheduler. // Primarily, the list of threads that are ready to run. #ifndef SCHEDULER_H #define SCHEDULER_H #include "list.h" #include "thread.h" // The following class defines the scheduler/dispatcher abstraction -- // the data structures and operations needed to keep track of which // thread is running, and which threads are ready but not running. class Scheduler { public: Scheduler(); // Initialize list of ready threads ~Scheduler(); // De-allocate ready list void ReadyToRun(Thread* thread); // Thread can be dispatched. Thread* FindNextToRun(); // Dequeue first thread on the ready // list, if any, and return thread. void Run(Thread* nextThread); // Cause nextThread to start running void Print(); // Print contents of ready list private: List *readyList; // queue of threads that are ready to run, // but not running }; #endif // SCHEDULER_H ************************** ****** SCHEDULER.CC ****** ************************** // scheduler.cc // Routines to choose the next thread to run, and to dispatch to // that thread. // // These routines assume that interrupts are already disabled. // If interrupts are disabled, we can assume mutual exclusion // (since we are on a uniprocessor). // // NOTE: We can't use Locks to provide mutual exclusion here, since // if we needed to wait for a lock, and the lock was busy, we would // end up calling FindNextToRun(), and that would put us in an // infinite loop. // // Very simple implementation -- no priorities, straight FIFO. // Might need to be improved in later assignments. #include "scheduler.h" #include "system.h" ****** SCHEDULER.CC ****** //---------------------------------------------------------------------- // Scheduler::Scheduler // Initialize the list of ready but not running threads to empty. //---------------------------------------------------------------------- Scheduler::Scheduler() { readyList = new List; } //---------------------------------------------------------------------- // Scheduler::~Scheduler // De-allocate the list of ready threads. //---------------------------------------------------------------------- Scheduler::~Scheduler() { delete readyList; } //---------------------------------------------------------------------- // Scheduler::ReadyToRun // Mark a thread as ready, but not running. // Put it on the ready list, for later scheduling onto the CPU. // // "thread" is the thread to be put on the ready list. //---------------------------------------------------------------------- void Scheduler::ReadyToRun (Thread *thread) { DEBUG('t', "Putting thread %s on ready list.\n", thread->getName()); thread->setStatus(READY); readyList->Append((void *)thread); } //---------------------------------------------------------------------- // Scheduler::FindNextToRun // Return the next thread to be scheduled onto the CPU. // If there are no ready threads, return NULL. // Side effect: // Thread is removed from the ready list. //---------------------------------------------------------------------- Thread * Scheduler::FindNextToRun () { return (Thread *)readyList->Remove(); } ****** SCHEDULER.CC ****** //---------------------------------------------------------------------- // Scheduler::Run // Dispatch the CPU to nextThread. Save the state of the old thread, // and load the state of the new thread, by calling the machine // dependent context switch routine, SWITCH. // // Note: we assume the state of the previously running thread has // already been changed from running to blocked or ready (depending). // Side effect: // The global variable currentThread becomes nextThread. // // "nextThread" is the thread to be put into the CPU. //---------------------------------------------------------------------- void Scheduler::Run (Thread *nextThread) { Thread *oldThread = currentThread; #ifdef USER_PROGRAM // ignore until running user programs if (currentThread->space != NULL) { // if this thread is a user program, currentThread->SaveUserState(); // save the user's CPU registers currentThread->space->SaveState(); } #endif oldThread->CheckOverflow(); // check if the old thread // had an undetected stack overflow currentThread = nextThread; // switch to the next thread currentThread->setStatus(RUNNING); // nextThread is now running DEBUG('t', "Switching from thread \"%s\" to thread \"%s\"\n", oldThread->getName(), nextThread->getName()); // This is a machine-dependent assembly language routine defined // in switch.s. You may have to think // a bit to figure out what happens after this, both from the point // of view of the thread and from the perspective of the "outside world". SWITCH(oldThread, nextThread); DEBUG('t', "Now in thread \"%s\"\n", currentThread->getName()); // If the old thread gave up the processor because it was finishing, // we need to delete its carcass. Note we cannot delete the thread // before now (for example, in Thread::Finish()), because up to this // point, we were still running on the old thread's stack! if (threadToBeDestroyed != NULL) { delete threadToBeDestroyed; threadToBeDestroyed = NULL; } #ifdef USER_PROGRAM if (currentThread->space != NULL) { // if there is an address space currentThread->RestoreUserState(); // to restore, do it. currentThread->space->RestoreState(); } #endif } ********************** ****** SWITCH.H ****** ********************** /* switch.h * Definitions needed for implementing context switching. * * Context switching is inherently machine dependent, since * the registers to be saved, how to set up an initial * call frame, etc, are all specific to a processor architecture. * * This file currently supports the DEC MIPS and SUN SPARC architectures. */ #ifndef SWITCH_H #define SWITCH_H #ifdef HOST_MIPS /* Registers that must be saved during a context switch. * These are the offsets from the beginning of the Thread object, * in bytes, used in switch.s */ #define SP 0 #define S0 4 #define S1 8 #define S2 12 #define S3 16 #define S4 20 #define S5 24 #define S6 28 #define S7 32 #define FP 36 #define PC 40 /* To fork a thread, we set up its saved register state, so that * when we switch to the thread, it will start running in ThreadRoot. * * The following are the initial registers we need to set up to * pass values into ThreadRoot (for instance, containing the procedure * for the thread to run). The first set is the registers as used * by ThreadRoot; the second set is the locations for these initial * values in the Thread object -- used in Thread::AllocateStack(). */ #define InitialPC s0 #define InitialArg s1 #define WhenDonePC s2 #define StartupPC s3 #define PCState (PC/4-1) #define FPState (FP/4-1) #define InitialPCState (S0/4-1) #define InitialArgState (S1/4-1) #define WhenDonePCState (S2/4-1) #define StartupPCState (S3/4-1) #endif // HOST_MIPS ****** SWITCH.H ****** #ifdef HOST_SPARC /* Registers that must be saved during a context switch. See comment above. */ #define I0 4 #define I1 8 #define I2 12 #define I3 16 #define I4 20 #define I5 24 #define I6 28 #define I7 32 /* Aliases used for clearing code. */ #define FP I6 #define PC I7 /* Registers for ThreadRoot. See comment above. */ #define InitialPC %o0 #define InitialArg %o1 #define WhenDonePC %o2 #define StartupPC %o3 #define PCState (PC/4-1) #define InitialPCState (I0/4-1) #define InitialArgState (I1/4-1) #define WhenDonePCState (I2/4-1) #define StartupPCState (I3/4-1) #endif // HOST_SPARC #ifdef HOST_SNAKE /* Registers that must be saved during a context switch. See comment above. */#define SP 0 #define S0 4 #define S1 8 #define S2 12 #define S3 16 #define S4 20 #define S5 24 #define S6 28 #define S7 32 #define S8 36 #define S9 40 #define S10 44 #define S11 48 #define S12 52 #define S13 56 #define S14 60 #define S15 64 #define PC 68 /* Registers for ThreadRoot. See comment above. */ #define InitialPC %r3 /* S0 */ #define InitialArg %r4 #define WhenDonePC %r5 #define StartupPC %r6 #define PCState (PC/4-1) #define InitialPCState (S0/4-1) #define InitialArgState (S1/4-1) #define WhenDonePCState (S2/4-1) #define StartupPCState (S3/4-1) #endif // HOST_SNAKE ****** SWITCH.H ****** #ifdef HOST_i386 /* the offsets of the registers from the beginning of the thread object */ #define _ESP 0 #define _EAX 4 #define _EBX 8 #define _ECX 12 #define _EDX 16 #define _EBP 20 #define _ESI 24 #define _EDI 28 #define _PC 32 /* These definitions are used in Thread::AllocateStack(). */ #define PCState (_PC/4-1) #define FPState (_EBP/4-1) #define InitialPCState (_ESI/4-1) #define InitialArgState (_EDX/4-1) #define WhenDonePCState (_EDI/4-1) #define StartupPCState (_ECX/4-1) #define InitialPC %esi #define InitialArg %edx #define WhenDonePC %edi #define StartupPC %ecx #endif // HOST_i386 #endif // SWITCH_H ********************** ****** SWITCH.S ****** ********************** /* switch.s * Machine dependent context switch routines. DO NOT MODIFY THESE! * * Context switching is inherently machine dependent, since * the registers to be saved, how to set up an initial * call frame, etc, are all specific to a processor architecture. * * This file currently supports the following architectures: * DEC MIPS * SUN SPARC * HP PA-RISC * Intel 386 * * We define two routines for each architecture: * * ThreadRoot(InitialPC, InitialArg, WhenDonePC, StartupPC) * InitialPC - The program counter of the procedure to run * in this thread. * InitialArg - The single argument to the thread. * WhenDonePC - The routine to call when the thread returns. * StartupPC - Routine to call when the thread is started. * * ThreadRoot is called from the SWITCH() routine to start * a thread for the first time. * * SWITCH(oldThread, newThread) * oldThread - The current thread that was running, where the * CPU register state is to be saved. * newThread - The new thread to be run, where the CPU register * state is to be loaded from. */ #include "switch.h" ****** SWITCH.S ****** #ifdef HOST_MIPS /* Symbolic register names */ #define z $0 /* zero register */ #define a0 $4 /* argument registers */ #define a1 $5 #define s0 $16 /* callee saved */ #define s1 $17 #define s2 $18 #define s3 $19 #define s4 $20 #define s5 $21 #define s6 $22 #define s7 $23 #define sp $29 /* stack pointer */ #define fp $30 /* frame pointer */ #define ra $31 /* return address */ .text .align 2 .globl ThreadRoot .ent ThreadRoot,0 ThreadRoot: or fp,z,z # Clearing the frame pointer here makes gdb # backtraces of thread stacks end here (I hope!) jal StartupPC # call startup procedure move a0, InitialArg jal InitialPC # call main procedure jal WhenDonePC # when we're done, call clean up procedure # NEVER REACHED .end ThreadRoot # a0 -- pointer to old Thread # a1 -- pointer to new Thread .globl SWITCH .ent SWITCH,0 SWITCH: sw sp, SP(a0) # save new stack pointer sw s0, S0(a0) # save all the callee-save registers sw s1, S1(a0) sw s2, S2(a0) sw s3, S3(a0) sw s4, S4(a0) sw s5, S5(a0) sw s6, S6(a0) sw s7, S7(a0) sw fp, FP(a0) # save frame pointer sw ra, PC(a0) # save return address lw sp, SP(a1) # load the new stack pointer lw s0, S0(a1) # load the callee-save registers lw s1, S1(a1) lw s2, S2(a1) lw s3, S3(a1) lw s4, S4(a1) lw s5, S5(a1) lw s6, S6(a1) lw s7, S7(a1) lw fp, FP(a1) lw ra, PC(a1) # load the return address j ra .end SWITCH #endif HOST_MIPS ****** SWITCH.S ****** #ifdef HOST_SPARC /* NOTE! These files appear not to exist on Solaris -- * you need to find where (the SPARC-specific) MINFRAME, ST_FLUSH_WINDOWS, ... * are defined. (I don't have a Solaris machine, so I have no way to tell.) */ #include #include .seg "text" /* SPECIAL to the SPARC: * The first two instruction of ThreadRoot are skipped because * the address of ThreadRoot is made the return address of SWITCH() * by the routine Thread::StackAllocate. SWITCH() jumps here on the * "ret" instruction which is really at "jmp %o7+8". The 8 skips the * two nops at the beginning of the routine. */ .globl _ThreadRoot _ThreadRoot: nop ; nop /* These 2 nops are skipped because we are called * with a jmp+8 instruction. */ clr %fp /* Clearing the frame pointer makes gdb backtraces * of thread stacks end here. */ /* Currently the arguments are in out registers we * save them into local registers so they won't be * trashed during the calls we make. */ mov InitialPC, %l0 mov InitialArg, %l1 mov WhenDonePC, %l2 /* Execute the code: * call StartupPC(); * call InitialPC(InitialArg); * call WhenDonePC(); */ call StartupPC,0 nop call %l0, 1 mov %l1, %o0 /* Using delay slot to setup argument to InitialPC */ call %l2, 0 nop ta ST_BREAKPOINT /* WhenDonePC call should never return. If it does * we execute a trap into the debugger. */ .globl _SWITCH _SWITCH: save %sp, -SA(MINFRAME), %sp st %fp, [%i0] st %i0, [%i0+I0] st %i1, [%i0+I1] st %i2, [%i0+I2] st %i3, [%i0+I3] st %i4, [%i0+I4] st %i5, [%i0+I5] st %i7, [%i0+I7] ta ST_FLUSH_WINDOWS nop mov %i1, %l0 ld [%l0+I0], %i0 ld [%l0+I1], %i1 ld [%l0+I2], %i2 ld [%l0+I3], %i3 ld [%l0+I4], %i4 ld [%l0+I5], %i5 ld [%l0+I7], %i7 ld [%l0], %i6 ret restore #endif HOST_SPARC ****** SWITCH.S ****** #ifdef HOST_SNAKE ;rp = r2, sp = r30 ;arg0 = r26, arg1 = r25, arg2 = r24, arg3 = r23 .SPACE $TEXT$ .SUBSPA $CODE$ ThreadRoot .PROC .CALLINFO CALLER,FRAME=0 .ENTRY .CALL ble 0(%r6) ;call StartupPC or %r31, 0, %rp ;put return address in proper register or %r4, 0, %arg0 ;load InitialArg .CALL ;in=26 ble 0(%r3) ;call InitialPC or %r31, 0, %rp ;put return address in proper register .CALL ble 0(%r5) ;call WhenDonePC .EXIT or %r31, 0, %rp ;shouldn't really matter - doesn't return .PROCEND SWITCH .PROC .CALLINFO CALLER,FRAME=0 .ENTRY ; save process state of oldThread stw %sp, SP(%arg0) ;save stack pointer stw %r3, S0(%arg0) ;save callee-save registers stw %r4, S1(%arg0) stw %r5, S2(%arg0) stw %r6, S3(%arg0) stw %r7, S4(%arg0) stw %r8, S5(%arg0) stw %r9, S6(%arg0) stw %r10, S7(%arg0) stw %r11, S8(%arg0) stw %r12, S9(%arg0) stw %r13, S10(%arg0) stw %r14, S11(%arg0) stw %r15, S12(%arg0) stw %r16, S13(%arg0) stw %r17, S14(%arg0) stw %r18, S15(%arg0) stw %rp, PC(%arg0) ;save program counter ****** SWITCH.S ****** ; restore process state of nextThread ldw SP(%arg1), %sp ;restore stack pointer ldw S0(%arg1), %r3 ;restore callee-save registers ldw S1(%arg1), %r4 ldw S2(%arg1), %r5 ldw S3(%arg1), %r6 ldw S4(%arg1), %r7 ldw S5(%arg1), %r8 ldw S6(%arg1), %r9 ldw S7(%arg1), %r10 ldw S8(%arg1), %r11 ldw S9(%arg1), %r12 ldw S10(%arg1), %r13 ldw S11(%arg1), %r14 ldw S12(%arg1), %r15 ldw S13(%arg1), %r16 ldw S14(%arg1), %r17 ldw PC(%arg1), %rp ;save program counter bv 0(%rp) .EXIT ldw S15(%arg1), %r18 .PROCEND .EXPORT SWITCH,ENTRY,PRIV_LEV=3,RTNVAL=GR .EXPORT ThreadRoot,ENTRY,PRIV_LEV=3,RTNVAL=GR #endif #ifdef HOST_i386 .text .align 2 .globl _ThreadRoot /* void ThreadRoot( void ) ** ** expects the following registers to be initialized: ** eax points to startup function (interrupt enable) ** edx contains inital argument to thread function ** esi points to thread function ** edi point to Thread::Finish() */ _ThreadRoot: pushl %ebp movl %esp,%ebp pushl InitialArg call StartupPC call InitialPC call WhenDonePC # NOT REACHED movl %ebp,%esp popl %ebp ret ****** SWITCH.S ****** /* void SWITCH( thread *t1, thread *t2 ) ** ** on entry, stack looks like this: ** 8(esp) -> thread *t2 ** 4(esp) -> thread *t1 ** (esp) -> return address ** ** we push the current eax on the stack so that we can use it as ** a pointer to t1, this decrements esp by 4, so when we use it ** to reference stuff on the stack, we add 4 to the offset. */ .comm _eax_save,4 .globl _SWITCH _SWITCH: movl %eax,_eax_save # save the value of eax movl 4(%esp),%eax # move pointer to t1 into eax movl %ebx,_EBX(%eax) # save registers movl %ecx,_ECX(%eax) movl %edx,_EDX(%eax) movl %esi,_ESI(%eax) movl %edi,_EDI(%eax) movl %ebp,_EBP(%eax) movl %esp,_ESP(%eax) # save stack pointer movl _eax_save,%ebx # get the saved value of eax movl %ebx,_EAX(%eax) # store it movl 0(%esp),%ebx # get return address from stack into ebx movl %ebx,_PC(%eax) # save it into the pc storage movl 8(%esp),%eax # move pointer to t2 into eax movl _EAX(%eax),%ebx # get new value for eax into ebx movl %ebx,_eax_save # save it movl _EBX(%eax),%ebx # retore old registers movl _ECX(%eax),%ecx movl _EDX(%eax),%edx movl _ESI(%eax),%esi movl _EDI(%eax),%edi movl _EBP(%eax),%ebp movl _ESP(%eax),%esp # restore stack pointer movl _PC(%eax),%eax # restore return address into eax movl %eax,4(%esp) # copy over the ret address on the stack movl _eax_save,%eax ret #endif ********************* ****** TIMER.H ****** ********************* // timer.h // Data structures to emulate a hardware timer. // // A hardware timer generates a CPU interrupt every X milliseconds. // This means it can be used for implementing time-slicing, or for // having a thread go to sleep for a specific period of time. // // We emulate a hardware timer by scheduling an interrupt to occur // every time stats->totalTicks has increased by TimerTicks. // // In order to introduce some randomness into time-slicing, if "doRandom" // is set, then the interrupt comes after a random number of ticks. // // DO NOT CHANGE -- part of the machine emulation #ifndef TIMER_H #define TIMER_H #include "utility.h" // The following class defines a hardware timer. class Timer { public: Timer(VoidFunctionPtr timerHandler, int callArg, bool doRandom); // Initialize the timer, to call the interrupt // handler "timerHandler" every time slice. ~Timer() {} // Internal routines to the timer emulation -- DO NOT call these void TimerExpired(); // called internally when the hardware // timer generates an interrupt int TimeOfNextInterrupt(); // figure out when the timer will generate // its next interrupt private: bool randomize; // set if we need to use a random timeout delay VoidFunctionPtr handler; // timer interrupt handler int arg; // argument to pass to interrupt handler }; #endif // TIMER_H ********************** ****** TIMER.CC ****** ********************** // timer.cc // Routines to emulate a hardware timer device. // // A hardware timer generates a CPU interrupt every X milliseconds. // This means it can be used for implementing time-slicing. // // We emulate a hardware timer by scheduling an interrupt to occur // every time stats->totalTicks has increased by TimerTicks. // // In order to introduce some randomness into time-slicing, if "doRandom" // is set, then the interrupt is comes after a random number of ticks. // // Remember -- nothing in here is part of Nachos. It is just // an emulation for the hardware that Nachos is running on top of. // // DO NOT CHANGE -- part of the machine emulation #include "timer.h" #include "system.h" // dummy function because C++ does not allow pointers to member functions static void TimerHandler(int arg) { Timer *p = (Timer *)arg; p->TimerExpired(); } //---------------------------------------------------------------------- // Timer::Timer // Initialize a hardware timer device. Save the place to call // on each interrupt, and then arrange for the timer to start // generating interrupts. // // "timerHandler" is the interrupt handler for the timer device. // It is called with interrupts disabled every time the // the timer expires. // "callArg" is the parameter to be passed to the interrupt handler. // "doRandom" -- if true, arrange for the interrupts to occur // at random, instead of fixed, intervals. //---------------------------------------------------------------------- Timer::Timer(VoidFunctionPtr timerHandler, int callArg, bool doRandom) { randomize = doRandom; handler = timerHandler; arg = callArg; // schedule the first interrupt from the timer device interrupt->Schedule(TimerHandler, (int) this, TimeOfNextInterrupt(), TimerInt); } ****** TIMER.CC ****** //---------------------------------------------------------------------- // Timer::TimerExpired // Routine to simulate the interrupt generated by the hardware // timer device. Schedule the next interrupt, and invoke the // interrupt handler. //---------------------------------------------------------------------- void Timer::TimerExpired() { // schedule the next timer device interrupt interrupt->Schedule(TimerHandler, (int) this, TimeOfNextInterrupt(), TimerInt); // invoke the Nachos interrupt handler for this device (*handler)(arg); } //---------------------------------------------------------------------- // Timer::TimeOfNextInterrupt // Return when the hardware timer device will next cause an interrupt. // If randomize is turned on, make it a (pseudo-)random delay. //---------------------------------------------------------------------- int Timer::TimeOfNextInterrupt() { if (randomize) return 1 + (Random() % (TimerTicks * 2)); else return TimerTicks; } ********************* ****** MAIN.CC ****** ********************* // main.cc // Bootstrap code to initialize the operating system kernel. // // Allows direct calls into internal operating system functions, // to simplify debugging and testing. In practice, the // bootstrap code would just initialize data structures, // and start a user program to print the login prompt. // // Most of this file is not needed until later assignments. // // Usage: nachos -d -rs // -s -x -c // -f -cp // -p -r -l -D -t // -n -m // -o // -z // // -d causes certain debugging messages to be printed (cf. utility.h) // -rs causes Yield to occur at random (but repeatable) spots // -z prints the copyright message // // USER_PROGRAM // -s causes user programs to be executed in single-step mode // -x runs a user program // -c tests the console // // FILESYS // -f causes the physical disk to be formatted // -cp copies a file from UNIX to Nachos // -p prints a Nachos file to stdout // -r removes a Nachos file from the file system // -l lists the contents of the Nachos directory // -D prints the contents of the entire file system // -t tests the performance of the Nachos file system // // NETWORK // -n sets the network reliability // -m sets this machine's host id (needed for the network) // -o runs a simple test of the Nachos network software // // NOTE -- flags are ignored until the relevant assignment. // Some of the flags are interpreted here; some in system.cc. #include "utility.h" #include "system.h" // External functions used by this file extern void ThreadTest(void), Copy(char *unixFile, char *nachosFile); extern void Print(char *file), PerformanceTest(void); extern void StartProcess(char *file), ConsoleTest(char *in, char *out); extern void MailTest(int networkID); ****** MAIN.CC ****** //---------------------------------------------------------------------- // main // Bootstrap the operating system kernel. // // Check command line arguments // Initialize data structures // (optionally) Call test procedure // // "argc" is the number of command line arguments (including the name // of the command) -- ex: "nachos -d +" -> argc = 3 // "argv" is an array of strings, one for each command line argument // ex: "nachos -d +" -> argv = {"nachos", "-d", "+"} //---------------------------------------------------------------------- int main(int argc, char **argv) { int argCount; // the number of arguments // for a particular command DEBUG('t', "Entering main"); (void) Initialize(argc, argv); #ifdef THREADS ThreadTest(); #endif for (argc--, argv++; argc > 0; argc -= argCount, argv += argCount) { argCount = 1; if (!strcmp(*argv, "-z")) // print copyright printf (copyright); #ifdef USER_PROGRAM if (!strcmp(*argv, "-x")) { // run a user program ASSERT(argc > 1); StartProcess(*(argv + 1)); argCount = 2; } else if (!strcmp(*argv, "-c")) { // test the console if (argc == 1) ConsoleTest(NULL, NULL); else { ASSERT(argc > 2); ConsoleTest(*(argv + 1), *(argv + 2)); argCount = 3; } interrupt->Halt(); // once we start the console, then // Nachos will loop forever waiting // for console input } #endif // USER_PROGRAM } currentThread->Finish(); // NOTE: if the procedure "main" // returns, then the program "nachos" // will exit (as any other normal program // would). But there may be other // threads on the ready list. We switch // to those threads by saying that the // "main" thread is finished, preventing // it from returning. return(0); // Not reached... } ********************** ****** SYSTEM.H ****** ********************** // system.h // All global variables used in Nachos are defined here. #ifndef SYSTEM_H #define SYSTEM_H #include "utility.h" #include "thread.h" #include "scheduler.h" #include "interrupt.h" #include "stats.h" #include "timer.h" // Initialization and cleanup routines extern void Initialize(int argc, char **argv); // Initialization, // called before anything else extern void Cleanup(); // Cleanup, called when // Nachos is done. extern Thread *currentThread; // the thread holding the CPU extern Thread *threadToBeDestroyed; // the thread that just finished extern Scheduler *scheduler; // the ready list extern Interrupt *interrupt; // interrupt status extern Statistics *stats; // performance metrics extern Timer *timer; // the hardware alarm clock #ifdef USER_PROGRAM #include "machine.h" extern Machine* machine; // user program memory and registers #endif #ifdef FILESYS_NEEDED // FILESYS or FILESYS_STUB #include "filesys.h" extern FileSystem *fileSystem; #endif #endif // SYSTEM_H *********************** ****** SYSTEM.CC ****** *********************** // system.cc // Nachos initialization and cleanup routines. #include "system.h" // This defines *all* of the global data structures used by Nachos. // These are all initialized and de-allocated by this file. Thread *currentThread; // the thread we are running now Thread *threadToBeDestroyed; // the thread that just finished Scheduler *scheduler; // the ready list Interrupt *interrupt; // interrupt status Statistics *stats; // performance metrics Timer *timer; // the hardware timer device, // for invoking context switches #ifdef FILESYS_NEEDED FileSystem *fileSystem; #endif #ifdef USER_PROGRAM // requires either FILESYS or FILESYS_STUB Machine *machine; // user program memory and registers #endif // External definition, to allow us to take a pointer to this function extern void Cleanup(); //---------------------------------------------------------------------- // TimerInterruptHandler // Interrupt handler for the timer device. The timer device is // set up to interrupt the CPU periodically (once every TimerTicks). // This routine is called each time there is a timer interrupt, // with interrupts disabled. // // Note that instead of calling Yield() directly (which would // suspend the interrupt handler, not the interrupted thread // which is what we wanted to context switch), we set a flag // so that once the interrupt handler is done, it will appear as // if the interrupted thread called Yield at the point it is // was interrupted. // // "dummy" is because every interrupt handler takes one argument, // whether it needs it or not. //---------------------------------------------------------------------- static void TimerInterruptHandler(int dummy) { if (interrupt->getStatus() != IdleMode) interrupt->YieldOnReturn(); } ****** SYSTEM.CC ****** //---------------------------------------------------------------------- // Initialize // Initialize Nachos global data structures. Interpret command // line arguments in order to determine flags for the initialization. // // "argc" is the number of command line arguments (including the name // of the command) -- ex: "nachos -d +" -> argc = 3 // "argv" is an array of strings, one for each command line argument // ex: "nachos -d +" -> argv = {"nachos", "-d", "+"} //---------------------------------------------------------------------- void Initialize(int argc, char **argv) { int argCount; char* debugArgs = ""; bool randomYield = FALSE; #ifdef USER_PROGRAM bool debugUserProg = FALSE; // single step user program #endif #ifdef FILESYS_NEEDED bool format = FALSE; // format disk #endif for (argc--, argv++; argc > 0; argc -= argCount, argv += argCount) { argCount = 1; if (!strcmp(*argv, "-d")) { if (argc == 1) debugArgs = "+"; // turn on all debug flags else { debugArgs = *(argv + 1); argCount = 2; } } else if (!strcmp(*argv, "-rs")) { ASSERT(argc > 1); RandomInit(atoi(*(argv + 1))); // initialize pseudo-random // number generator randomYield = TRUE; argCount = 2; } #ifdef USER_PROGRAM if (!strcmp(*argv, "-s")) debugUserProg = TRUE; #endif #ifdef FILESYS_NEEDED if (!strcmp(*argv, "-f")) format = TRUE; #endif } ****** SYSTEM.CC ****** DebugInit(debugArgs); // initialize DEBUG messages stats = new Statistics(); // collect statistics interrupt = new Interrupt; // start up interrupt handling scheduler = new Scheduler(); // initialize the ready queue if (randomYield) // start the timer (if needed) timer = new Timer(TimerInterruptHandler, 0, randomYield); threadToBeDestroyed = NULL; // We didn't explicitly allocate the current thread we are running in. // But if it ever tries to give up the CPU, we better have a Thread // object to save its state. currentThread = new Thread("main"); currentThread->setStatus(RUNNING); interrupt->Enable(); CallOnUserAbort(Cleanup); // if user hits ctl-C #ifdef USER_PROGRAM machine = new Machine(debugUserProg); // this must come first #endif #ifdef FILESYS_NEEDED fileSystem = new FileSystem(format); #endif } //---------------------------------------------------------------------- // Cleanup // Nachos is halting. De-allocate global data structures. //---------------------------------------------------------------------- void Cleanup() { printf("\nCleaning up...\n"); #ifdef USER_PROGRAM delete machine; #endif #ifdef FILESYS_NEEDED delete fileSystem; #endif delete timer; delete scheduler; delete interrupt; Exit(0); } ****** ****** SYNCH.H ****** // synch.h // Data structures for synchronizing threads. // // Three kinds of synchronization are defined here: semaphores, // locks, and condition variables. The implementation for // semaphores is given; for the latter two, only the procedure // interface is given -- they are to be implemented as part of // the first assignment. // // Note that all the synchronization objects take a "name" as // part of the initialization. This is solely for debugging purposes. #ifndef SYNCH_H #define SYNCH_H #include "thread.h" #include "list.h" // The following class defines a "semaphore" whose value is a non-negative // integer. The semaphore has only two operations P() and V(): // // P() -- waits until value > 0, then decrement // // V() -- increment, waking up a thread waiting in P() if necessary // // Note that the interface does *not* allow a thread to read the value of // the semaphore directly -- even if you did read the value, the // only thing you would know is what the value used to be. You don't // know what the value is now, because by the time you get the value // into a register, a context switch might have occurred, // and some other thread might have called P or V, so the true value might // now be different. class Semaphore { public: Semaphore(char* debugName, int initialValue); // set initial value ~Semaphore(); // de-allocate semaphore char* getName() { return name;} // debugging assist void P(); // these are the only operations on a semaphore void V(); // they are both *atomic* private: char* name; // useful for debugging int value; // semaphore value, always >= 0 List *queue; // threads waiting in P() for the value to be > 0 }; ****** SYNCH.H ****** // The following class defines a "lock". A lock can be BUSY or FREE. // There are only two operations allowed on a lock: // // Acquire -- wait until the lock is FREE, then set it to BUSY // // Release -- set lock to be FREE, waking up a thread waiting // in Acquire if necessary // // In addition, by convention, only the thread that acquired the lock // may release it. As with semaphores, you can't read the lock value // (because the value might change immediately after you read it). class Lock { public: Lock(char* debugName); // initialize lock to be FREE ~Lock(); // deallocate lock char* getName() { return name; } // debugging assist void Acquire(); // these are the only operations on a lock void Release(); // they are both *atomic* bool isHeldByCurrentThread(); // true if the current thread // holds this lock. Useful for // checking in Release, and in // Condition variable ops below. private: char* name; // for debugging // plus some other stuff you'll need to define }; // The following class defines a "condition variable". A condition // variable does not have a value, but threads may be queued, waiting // on the variable. These are only operations on a condition variable: // // Wait() -- release the lock, relinquish the CPU until signaled, // then re-acquire the lock // // Signal() -- wake up a thread, if there are any waiting on // the condition // // Broadcast() -- wake up all threads waiting on the condition // // All operations on a condition variable must be made while // the current thread has acquired a lock. Indeed, all accesses // to a given condition variable must be protected by the same lock. // In other words, mutual exclusion must be enforced among threads calling // the condition variable operations. // // In Nachos, condition variables are assumed to obey *Mesa*-style // semantics. When a Signal or Broadcast wakes up another thread, // it simply puts the thread on the ready list, and it is the responsibility // of the woken thread to re-acquire the lock (this re-acquire is // taken care of within Wait()). By contrast, some define condition // variables according to *Hoare*-style semantics -- where the signalling // thread gives up control over the lock and the CPU to the woken thread, // which runs immediately and gives back control over the lock to the // signaller when the woken thread leaves the critical section. // // The consequence of using Mesa-style semantics is that some other thread // can acquire the lock, and change data structures, before the woken // thread gets a chance to run. ****** SYNCH.H ****** class Condition { public: Condition(char* debugName); // initialize condition to // "no one waiting" ~Condition(); // deallocate the condition char* getName() { return (name); } void Wait(Lock *conditionLock); // these are the 3 operations on // condition variables; releasing the // lock and going to sleep are // *atomic* in Wait() void Signal(Lock *conditionLock); // conditionLock must be held by void Broadcast(Lock *conditionLock);// the currentThread for all of // these operations private: char* name; // plus some other stuff you'll need to define }; #endif // SYNCH_H ********************** ****** SYNCH.CC ****** ********************** // synch.cc // Routines for synchronizing threads. Three kinds of // synchronization routines are defined here: semaphores, locks // and condition variables (the implementation of the last two // are left to the reader). // // Any implementation of a synchronization routine needs some // primitive atomic operation. We assume Nachos is running on // a uniprocessor, and thus atomicity can be provided by // turning off interrupts. While interrupts are disabled, no // context switch can occur, and thus the current thread is guaranteed // to hold the CPU throughout, until interrupts are reenabled. // // Because some of these routines might be called with interrupts // already disabled (Semaphore::V for one), instead of turning // on interrupts at the end of the atomic operation, we always simply // re-set the interrupt state back to its original value (whether // that be disabled or enabled). #include "synch.h" #include "system.h" //---------------------------------------------------------------------- // Semaphore::Semaphore // Initialize a semaphore, so that it can be used for synchronization. // // "debugName" is an arbitrary name, useful for debugging. // "initialValue" is the initial value of the semaphore. //---------------------------------------------------------------------- Semaphore::Semaphore(char* debugName, int initialValue) { name = debugName; value = initialValue; queue = new List; } ****** SYNCH.CC ****** //---------------------------------------------------------------------- // Semaphore::~Semaphore // De-allocate semaphore, when no longer needed. Assume no one // is still waiting on the semaphore! //---------------------------------------------------------------------- Semaphore::~Semaphore() { delete queue; } //---------------------------------------------------------------------- // Semaphore::P // Wait until semaphore value > 0, then decrement. Checking the // value and decrementing must be done atomically, so we // need to disable interrupts before checking the value. // // Note that Thread::Sleep assumes that interrupts are disabled // when it is called. //---------------------------------------------------------------------- void Semaphore::P() { IntStatus oldLevel = interrupt->SetLevel(IntOff); // disable interrupts while (value == 0) { // semaphore not available queue->Append((void *)currentThread); // so go to sleep currentThread->Sleep(); } value--; // semaphore available, // consume its value (void) interrupt->SetLevel(oldLevel); // re-enable interrupts } //---------------------------------------------------------------------- // Semaphore::V // Increment semaphore value, waking up a waiter if necessary. // As with P(), this operation must be atomic, so we need to disable // interrupts. Scheduler::ReadyToRun() assumes that threads // are disabled when it is called. //---------------------------------------------------------------------- void Semaphore::V() { Thread *thread; IntStatus oldLevel = interrupt->SetLevel(IntOff); thread = (Thread *)queue->Remove(); if (thread != NULL) // make thread ready, consuming the V immediately scheduler->ReadyToRun(thread); value++; (void) interrupt->SetLevel(oldLevel); } ************************* ****** ADDRSPACE.H ****** ************************* // addrspace.h // Data structures to keep track of executing user programs // (address spaces). // // For now, we don't keep any information about address spaces. // The user level CPU state is saved and restored in the thread // executing the user program (see thread.h). #ifndef ADDRSPACE_H #define ADDRSPACE_H #include "filesys.h" #define UserStackSize 1024 // increase this as necessary! class AddrSpace { public: AddrSpace(OpenFile *executable); // Create an address space, // initializing it with the program // stored in the file "executable" ~AddrSpace(); // De-allocate an address space void InitRegisters(); // Initialize user-level CPU registers, // before jumping to user code void SaveState(); // Save/restore address space-specific void RestoreState(); // info on a context switch private: TranslationEntry *pageTable; // Assume linear page table translation // for now! unsigned int numPages; // Number of pages in the virtual // address space }; #endif // ADDRSPACE_H ************************** ****** ADDRSPACE.CC ****** ************************** // addrspace.cc // Routines to manage address spaces (executing user programs). // // In order to run a user program, you must: // // 1. link with the -N -T 0 option // 2. run coff2noff to convert the object file to Nachos format // (Nachos object code format is essentially just a simpler // version of the UNIX executable object code format) // 3. load the NOFF file into the Nachos file system // (if you haven't implemented the file system yet, you // don't need to do this last step) #include "system.h" #include "addrspace.h" #include "noff.h" //---------------------------------------------------------------------- // SwapHeader // Do little endian to big endian conversion on the bytes in the // object file header, in case the file was generated on a little // endian machine, and we're now running on a big endian machine. //---------------------------------------------------------------------- static void SwapHeader (NoffHeader *noffH) { noffH->noffMagic = WordToHost(noffH->noffMagic); noffH->code.size = WordToHost(noffH->code.size); noffH->code.virtualAddr = WordToHost(noffH->code.virtualAddr); noffH->code.inFileAddr = WordToHost(noffH->code.inFileAddr); noffH->initData.size = WordToHost(noffH->initData.size); noffH->initData.virtualAddr = WordToHost(noffH->initData.virtualAddr); noffH->initData.inFileAddr = WordToHost(noffH->initData.inFileAddr); noffH->uninitData.size = WordToHost(noffH->uninitData.size); noffH->uninitData.virtualAddr = WordToHost(noffH->uninitData.virtualAddr); noffH->uninitData.inFileAddr = WordToHost(noffH->uninitData.inFileAddr); } //---------------------------------------------------------------------- // AddrSpace::AddrSpace // Create an address space to run a user program. // Load the program from a file "executable", and set everything // up so that we can start executing user instructions. // // Assumes that the object code file is in NOFF format. // // First, set up the translation from program memory to physical // memory. For now, this is really simple (1:1), since we are // only uniprogramming, and we have a single unsegmented page table // // "executable" is the file containing the object code to load into memory //---------------------------------------------------------------------- ****** ADDRSPACE.CC ****** AddrSpace::AddrSpace(OpenFile *executable) { NoffHeader noffH; unsigned int i, size; executable->ReadAt((char *)&noffH, sizeof(noffH), 0); if ((noffH.noffMagic != NOFFMAGIC) && (WordToHost(noffH.noffMagic) == NOFFMAGIC)) SwapHeader(&noffH); ASSERT(noffH.noffMagic == NOFFMAGIC); // how big is address space? size = noffH.code.size + noffH.initData.size + noffH.uninitData.size + UserStackSize; // we need to increase the size // to leave room for the stack numPages = divRoundUp(size, PageSize); size = numPages * PageSize; ASSERT(numPages <= NumPhysPages); // check we're not trying // to run anything too big -- // at least until we have // virtual memory DEBUG('a', "Initializing address space, num pages %d, size %d\n", numPages, size); // first, set up the translation pageTable = new TranslationEntry[numPages]; for (i = 0; i < numPages; i++) { pageTable[i].virtualPage = i; // for now, virtual page # = phys page # pageTable[i].physicalPage = i; pageTable[i].valid = TRUE; pageTable[i].use = FALSE; pageTable[i].dirty = FALSE; pageTable[i].readOnly = FALSE; // if the code segment was entirely on // a separate page, we could set its // pages to be read-only } // zero out the entire address space, to zero the unitialized data segment // and the stack segment bzero(machine->mainMemory, size); // then, copy in the code and data segments into memory if (noffH.code.size > 0) { DEBUG('a', "Initializing code segment, at 0x%x, size %d\n", noffH.code.virtualAddr, noffH.code.size); executable->ReadAt(&(machine->mainMemory[noffH.code.virtualAddr]), noffH.code.size, noffH.code.inFileAddr); } if (noffH.initData.size > 0) { DEBUG('a', "Initializing data segment, at 0x%x, size %d\n", noffH.initData.virtualAddr, noffH.initData.size); executable->ReadAt(&(machine->mainMemory[noffH.initData.virtualAddr]), noffH.initData.size, noffH.initData.inFileAddr); } } //---------------------------------------------------------------------- // AddrSpace::~AddrSpace // Dealloate an address space. Nothing for now! //---------------------------------------------------------------------- ****** ADDRSPACE.CC ****** AddrSpace::~AddrSpace() { delete pageTable; } //---------------------------------------------------------------------- // AddrSpace::InitRegisters // Set the initial values for the user-level register set. // // We write these directly into the "machine" registers, so // that we can immediately jump to user code. Note that these // will be saved/restored into the currentThread->userRegisters // when this thread is context switched out. //---------------------------------------------------------------------- void AddrSpace::InitRegisters() { int i; for (i = 0; i < NumTotalRegs; i++) machine->WriteRegister(i, 0); // Initial program counter -- must be location of "Start" machine->WriteRegister(PCReg, 0); // Need to also tell MIPS where next instruction is, because // of branch delay possibility machine->WriteRegister(NextPCReg, 4); // Set the stack register to the end of the address space, where we // allocated the stack; but subtract off a bit, to make sure we don't // accidentally reference off the end! machine->WriteRegister(StackReg, numPages * PageSize - 16); DEBUG('a', "Initializing stack register to %d\n", numPages * PageSize - 16); } //---------------------------------------------------------------------- // AddrSpace::SaveState // On a context switch, save any machine state, specific // to this address space, that needs saving. // // For now, nothing! //---------------------------------------------------------------------- void AddrSpace::SaveState() {} //---------------------------------------------------------------------- // AddrSpace::RestoreState // On a context switch, restore the machine state so that // this address space can run. // // For now, tell the machine where to find the page table. //---------------------------------------------------------------------- void AddrSpace::RestoreState() { machine->pageTable = pageTable; machine->pageTableSize = numPages; } *********************** ****** SYSCALL.H ****** *********************** /* syscall.h * Nachos system call interface. These are Nachos kernel operations * that can be invoked from user programs, by trapping to the kernel * via the "syscall" instruction. * * This file is included by user programs and by the Nachos kernel. */ #ifndef SYSCALLS_H #define SYSCALLS_H /* system call codes -- used by the stubs to tell the kernel which system call * is being asked for */ #define SC_Halt 0 #define SC_Exit 1 #define SC_Exec 2 #define SC_Join 3 #define SC_Create 4 #define SC_Open 5 #define SC_Read 6 #define SC_Write 7 #define SC_Close 8 #define SC_Fork 9 #define SC_Yield 10 #ifndef IN_ASM /* The system call interface. These are the operations the Nachos * kernel needs to support, to be able to run user programs. * * Each of these is invoked by a user program by simply calling the * procedure; an assembly language stub stuffs the system call code * into a register, and traps to the kernel. The kernel procedures * are then invoked in the Nachos kernel, after appropriate error checking, * from the system call entry point in exception.cc. */ /* Stop Nachos, and print out performance stats */ void Halt(); /* Address space control operations: Exit, Exec, and Join */ /* This user program is done (status = 0 means exited normally). */ void Exit(int status); /* A unique identifier for an executing user program (address space) */ typedef int SpaceId; /* Run the executable, stored in the Nachos file "name", and return the * address space identifier */ SpaceId Exec(char *name); /* Only return once the the user program "id" has finished. * Return the exit status. */ int Join(SpaceId id); ****** SYSCALL.H ****** /* File system operations: Create, Open, Read, Write, Close * These functions are patterned after UNIX -- files represent * both files *and* hardware I/O devices. * * If this assignment is done before doing the file system assignment, * note that the Nachos file system has a stub implementation, which * will work for the purposes of testing out these routines. */ /* A unique identifier for an open Nachos file. */ typedef int OpenFileId; /* when an address space starts up, it has two open files, representing * keyboard input and display output (in UNIX terms, stdin and stdout). * Read and Write can be used directly on these, without first opening * the console device. */ #define ConsoleInput 0 #define ConsoleOutput 1 /* Create a Nachos file, with "name" */ void Create(char *name); /* Open the Nachos file "name", and return an "OpenFileId" that can * be used to read and write to the file. */ OpenFileId Open(char *name); /* Write "size" bytes from "buffer" to the open file. */ void Write(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). */ int Read(char *buffer, int size, OpenFileId id); /* Close the file, we're done reading and writing to it. */ void Close(OpenFileId id); /* User-level thread operations: Fork and Yield. To allow multiple * threads to run within a user program. */ /* Fork a thread to run a procedure ("func") in the *same* address space * as the current thread. */ void Fork(void (*func)()); /* Yield the CPU to another runnable thread, whether in this address space * or not. */ void Yield(); #endif /* IN_ASM */ #endif /* SYSCALLS_H */ ************************** ****** EXCEPTION.CC ****** ************************** // exception.cc // Entry point into the Nachos kernel from user programs. // There are two kinds of things that can cause control to // transfer back to here from user code: // // syscall -- The user code explicitly requests to call a procedure // in the Nachos kernel. Right now, the only function we support is // "Halt". // // exceptions -- The user code does something that the CPU can't handle. // For instance, accessing memory that doesn't exist, arithmetic errors, // etc. // // Interrupts (which can also cause control to transfer from user // code into the Nachos kernel) are handled elsewhere. // // For now, this only handles the Halt() system call. // Everything else core dumps. #include "system.h" #include "syscall.h" //---------------------------------------------------------------------- // ExceptionHandler // Entry point into the Nachos kernel. Called when a user program // is executing, and either does a syscall, or generates an addressing // or arithmetic exception. // // For system calls, the following is the calling convention: // // system call code -- r2 // arg1 -- r4 // arg2 -- r5 // arg3 -- r6 // arg4 -- r7 // // The result of the system call, if any, must be put back into r2. // // And don't forget to increment the pc before returning. (Or else you'll // loop making the same system call forever! // // "which" is the kind of exception. The list of possible exceptions // are in machine.h. //---------------------------------------------------------------------- void ExceptionHandler(ExceptionType which) { int type = machine->ReadRegister(2); if ((which == SyscallException) && (type == SC_Halt)) { DEBUG('a', "Shutdown, initiated by user program.\n"); interrupt->Halt(); } else { printf("Unexpected user mode exception %d %d\n", which, type); ASSERT(FALSE); } } ********************** ******FILESYS.H ****** ********************** // filesys.h // Data structures to represent the Nachos file system. // // A file system is a set of files stored on disk, organized // into directories. Operations on the file system have to // do with "naming" -- creating, opening, and deleting files, // given a textual file name. Operations on an individual // "open" file (read, write, close) are to be found in the OpenFile // class (openfile.h). // // We define two separate implementations of the file system. // The "STUB" version just re-defines the Nachos file system // operations as operations on the native UNIX file system on the machine // running the Nachos simulation. This is provided in case the // multiprogramming and virtual memory assignments (which make use // of the file system) are done before the file system assignment. // // The other version is a "real" file system, built on top of // a disk simulator. The disk is simulated using the native UNIX // file system (in a file named "DISK"). // // In the "real" implementation, there are two key data structures used // in the file system. There is a single "root" directory, listing // all of the files in the file system; unlike UNIX, the baseline // system does not provide a hierarchical directory structure. // In addition, there is a bitmap for allocating // disk sectors. Both the root directory and the bitmap are themselves // stored as files in the Nachos file system -- this causes an interesting // bootstrap problem when the simulated disk is initialized. #ifndef FS_H #define FS_H #include "openfile.h" #ifdef FILESYS_STUB // Temporarily implement file system calls as // calls to UNIX, until the real file system // implementation is available class FileSystem { public: FileSystem(bool format) {} bool Create(char *name, int initialSize) { int fileDescriptor = OpenForWrite(name); if (fileDescriptor == -1) return FALSE; Close(fileDescriptor); return TRUE; } OpenFile* Open(char *name) { int fileDescriptor = OpenForReadWrite(name, FALSE); if (fileDescriptor == -1) return NULL; return new OpenFile(fileDescriptor); } bool Remove(char *name) { return Unlink(name) == 0; } }; #endif // FILESYS_STUB #endif // FS_H ************************ ****** OPENFILE.H ****** ************************ // openfile.h // Data structures for opening, closing, reading and writing to // individual files. The operations supported are similar to // the UNIX ones -- type 'man open' to the UNIX prompt. // // There are two implementations. One is a "STUB" that directly // turns the file operations into the underlying UNIX operations. // (cf. comment in filesys.h). // // The other is the "real" implementation, that turns these // operations into read and write disk sector requests. // In this baseline implementation of the file system, we don't // worry about concurrent accesses to the file system // by different threads -- this is part of the assignment. #ifndef OPENFILE_H #define OPENFILE_H #include "utility.h" #ifdef FILESYS_STUB // Temporarily implement calls to // Nachos file system as calls to UNIX! // See definitions listed under #else class OpenFile { public: OpenFile(int f) { file = f; currentOffset = 0; } // open the file ~OpenFile() { Close(file); } // close the file int ReadAt(char *into, int numBytes, int position) { Lseek(file, position, 0); return ReadPartial(file, into, numBytes); } int WriteAt(char *from, int numBytes, int position) { Lseek(file, position, 0); WriteFile(file, from, numBytes); return numBytes; } int Read(char *into, int numBytes) { int numRead = ReadAt(into, numBytes, currentOffset); currentOffset += numRead; return numRead; } int Write(char *from, int numBytes) { int numWritten = WriteAt(from, numBytes, currentOffset); currentOffset += numWritten; return numWritten; } int Length() { Lseek(file, 0, 2); return Tell(file); } private: int file; int currentOffset; }; #endif // FILESYS_STUB #endif // OPENFILE_H ************************* ****** OPENFILE.CC ****** ************************* // openfile.cc // Routines to manage an open Nachos file. As in UNIX, a // file must be open before we can read or write to it. // Once we're all done, we can close it (in Nachos, by deleting // the OpenFile data structure). // // Also as in UNIX, for convenience, we keep the file header in // memory while the file is open. #include "filehdr.h" #include "openfile.h" #include "system.h" //---------------------------------------------------------------------- // OpenFile::OpenFile // Open a Nachos file for reading and writing. Bring the file header // into memory while the file is open. // // "sector" -- the location on disk of the file header for this file //---------------------------------------------------------------------- OpenFile::OpenFile(int sector) { hdr = new FileHeader; hdr->FetchFrom(sector); seekPosition = 0; } //---------------------------------------------------------------------- // OpenFile::~OpenFile // Close a Nachos file, de-allocating any in-memory data structures. //---------------------------------------------------------------------- OpenFile::~OpenFile() { delete hdr; } //---------------------------------------------------------------------- // OpenFile::Seek // Change the current location within the open file -- the point at // which the next Read or Write will start from. // // "position" -- the location within the file for the next Read/Write //---------------------------------------------------------------------- void OpenFile::Seek(int position) { seekPosition = position; } ****** OPENFILE.CC ****** //---------------------------------------------------------------------- // OpenFile::Read/Write // Read/write a portion of a file, starting from seekPosition. // Return the number of bytes actually written or read, and as a // side effect, increment the current position within the file. // // Implemented using the more primitive ReadAt/WriteAt. // // "into" -- the buffer to contain the data to be read from disk // "from" -- the buffer containing the data to be written to disk // "numBytes" -- the number of bytes to transfer //---------------------------------------------------------------------- int OpenFile::Read(char *into, int numBytes) { int result = ReadAt(into, numBytes, seekPosition); seekPosition += result; return result; } int OpenFile::Write(char *into, int numBytes) { int result = WriteAt(into, numBytes, seekPosition); seekPosition += result; return result; } //---------------------------------------------------------------------- // OpenFile::ReadAt/WriteAt // Read/write a portion of a file, starting at "position". // Return the number of bytes actually written or read, but has // no side effects (except that Write modifies the file, of course). // // There is no guarantee the request starts or ends on an even disk sector // boundary; however the disk only knows how to read/write a whole disk // sector at a time. Thus: // // For ReadAt: // We read in all of the full or partial sectors that are part of the // request, but we only copy the part we are interested in. // For WriteAt: // We must first read in any sectors that will be partially written, // so that we don't overwrite the unmodified portion. We then copy // in the data that will be modified, and write back all the full // or partial sectors that are part of the request. // // "into" -- the buffer to contain the data to be read from disk // "from" -- the buffer containing the data to be written to disk // "numBytes" -- the number of bytes to transfer // "position" -- the offset within the file of the first byte to be // read/written //---------------------------------------------------------------------- ****** OPENFILE.CC ****** int OpenFile::ReadAt(char *into, int numBytes, int position) { int fileLength = hdr->FileLength(); int i, firstSector, lastSector, numSectors; char *buf; if ((numBytes <= 0) || (position >= fileLength)) return 0; // check request if ((position + numBytes) > fileLength) numBytes = fileLength - position; DEBUG('f', "Reading %d bytes at %d, from file of length %d.\n", numBytes, position, fileLength); firstSector = divRoundDown(position, SectorSize); lastSector = divRoundDown(position + numBytes - 1, SectorSize); numSectors = 1 + lastSector - firstSector; // read in all the full and partial sectors that we need buf = new char[numSectors * SectorSize]; for (i = firstSector; i <= lastSector; i++) synchDisk->ReadSector(hdr->ByteToSector(i * SectorSize), &buf[(i - firstSector) * SectorSize]); // copy the part we want bcopy(&buf[position - (firstSector * SectorSize)], into, numBytes); delete [] buf; return numBytes; } ****** OPENFILE.CC ****** int OpenFile::WriteAt(char *from, int numBytes, int position) { int fileLength = hdr->FileLength(); int i, firstSector, lastSector, numSectors; bool firstAligned, lastAligned; char *buf; if ((numBytes <= 0) || (position >= fileLength)) return 0; // check request if ((position + numBytes) > fileLength) numBytes = fileLength - position; DEBUG('f', "Writing %d bytes at %d, from file of length %d.\n", numBytes, position, fileLength); firstSector = divRoundDown(position, SectorSize); lastSector = divRoundDown(position + numBytes - 1, SectorSize); numSectors = 1 + lastSector - firstSector; buf = new char[numSectors * SectorSize]; firstAligned = (position == (firstSector * SectorSize)); lastAligned = ((position + numBytes) == ((lastSector + 1) * SectorSize)); // read in first and last sector, if they are to be partially modified if (!firstAligned) ReadAt(buf, SectorSize, firstSector * SectorSize); if (!lastAligned && ((firstSector != lastSector) || firstAligned)) ReadAt(&buf[(lastSector - firstSector) * SectorSize], SectorSize, lastSector * SectorSize); // copy in the bytes we want to change bcopy(from, &buf[position - (firstSector * SectorSize)], numBytes); // write modified sectors back for (i = firstSector; i <= lastSector; i++) synchDisk->WriteSector(hdr->ByteToSector(i * SectorSize), &buf[(i - firstSector) * SectorSize]); delete [] buf; return numBytes; } //---------------------------------------------------------------------- // OpenFile::Length // Return the number of bytes in the file. //---------------------------------------------------------------------- int OpenFile::Length() { return hdr->FileLength(); } ********************** ****** SYSDEP.H ****** ********************** // sysdep.h // System-dependent interface. Nachos uses the routines defined here, rather // than directly calling the UNIX library functions, to simplify porting between // versions of UNIX, and even to other systems, such as MSDOS and the Macintosh. #ifndef SYSDEP_H #define SYSDEP_H // Check file to see if there are any characters to be read. // If no characters in the file, return without waiting. extern bool PollFile(int fd); // File operations: open/read/write/lseek/close, and check for error // For simulating the disk and the console devices. extern int OpenForWrite(char *name); extern int OpenForReadWrite(char *name, bool crashOnError); extern void Read(int fd, char *buffer, int nBytes); extern int ReadPartial(int fd, char *buffer, int nBytes); extern void WriteFile(int fd, char *buffer, int nBytes); extern void Lseek(int fd, int offset, int whence); extern int Tell(int fd); extern void Close(int fd); extern bool Unlink(char *name); // Interprocess communication operations, for simulating the network extern int OpenSocket(); extern void CloseSocket(int sockID); extern void AssignNameToSocket(char *socketName, int sockID); extern void DeAssignNameToSocket(char *socketName); extern bool PollSocket(int sockID); extern void ReadFromSocket(int sockID, char *buffer, int packetSize); extern void SendToSocket(int sockID, char *buffer, int packetSize,char *toName); // Process control: abort, exit, and sleep extern void Abort(); extern void Exit(int exitCode); extern void Delay(int seconds); // Initialize system so that cleanUp routine is called when user hits ctl-C extern void CallOnUserAbort(VoidNoArgFunctionPtr cleanUp); // Initialize the pseudo random number generator extern void RandomInit(unsigned seed); extern int Random(); // Allocate, de-allocate an array, such that de-referencing // just beyond either end of the array will cause an error extern char *AllocBoundedArray(int size); extern void DeallocBoundedArray(char *p, int size); // Other C library routines that are used by Nachos. // These are assumed to be portable, so we don't include a wrapper. #define __CP_STRING_H__ extern "C" { int atoi(const char *str); double atof(const char *str); int abs(int i); #include // for printf, fprintf #include // local string.h for DEBUG, etc. } #endif // SYSDEP_H *********************** ****** SYSDEP.CC ****** *********************** // sysdep.cc // Implementation of system-dependent interface. Nachos uses the // routines defined here, rather than directly calling the UNIX library, // to simplify porting between versions of UNIX, and even to // other systems, such as MSDOS. // // On UNIX, almost all of these routines are simple wrappers // for the underlying UNIX system calls. // // NOTE: all of these routines refer to operations on the underlying // host machine (e.g., the DECstation, SPARC, etc.), supporting the // Nachos simulation code. Nachos implements similar operations, // (such as opening a file), but those are implemented in terms // of hardware devices, which are simulated by calls to the underlying // routines in the host workstation OS. // // This file includes lots of calls to C routines. C++ requires // us to wrap all C definitions with a "extern "C" block". // This prevents the internal forms of the names from being // changed by the C++ compiler. #define __CP_STRING_H__ extern "C" { #include // IB-- changed this line to include local version of string.h #include #include #include #include #include #include #include #include #ifdef HOST_i386 #include #endif #ifdef HOST_SPARC #include #endif // UNIX routines called by procedures in this file #ifdef HOST_SNAKE // int creat(char *name, unsigned short mode); // int open(const char *name, int flags, ...); #else #ifdef HOST_SPARC // RJF - Added this branch for our SPARCs // int creat(char *name, unsigned short mode); // int open(char *name, int flags, ...); #else int creat(const char *name, unsigned short mode); int open(const char *name, int flags, ...); #endif ****** SYSDEP.CC ****** // void signal(int sig, VoidFunctionPtr func); -- this may work now! #ifdef HOST_i386 int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); #else int select(int numBits, void *readFds, void *writeFds, void *exceptFds, struct timeval *timeout); #endif #endif int unlink(char *name); int read(int filedes, char *buf, int numBytes); int write(int filedes, char *buf, int numBytes); int lseek(int filedes, int offset, int whence); int tell(int filedes); int close(int filedes); int unlink(char *name); // definition varies slightly from platform to platform, so don't // define unless gcc complains // extern int recvfrom(int s, void *buf, int len, int flags, void *from, int *fromlen); // extern int sendto(int s, void *msg, int len, int flags, void *to, int tolen); void srand(unsigned seed); int rand(void); unsigned sleep(unsigned); void abort(); void exit(); int mprotect(char *addr, int len, int prot); int socket(int, int, int); int bind (int, const void*, int); int recvfrom (int, void*, int, int, void*, int *); int sendto (int, const void*, int, int, void*, int); } #include "interrupt.h" #include "system.h" //---------------------------------------------------------------------- // PollFile // Check open file or open socket to see if there are any // characters that can be read immediately. If so, read them // in, and return TRUE. // // In the network case, if there are no threads for us to run, // and no characters to be read, // we need to give the other side a chance to get our host's CPU // (otherwise, we'll go really slowly, since UNIX time-slices // infrequently, and this would be like busy-waiting). So we // delay for a short fixed time, before allowing ourselves to be // re-scheduled (sort of like a Yield, but cast in terms of UNIX). // // "fd" -- the file descriptor of the file to be polled //---------------------------------------------------------------------- ****** SYSDEP.CC ****** bool PollFile(int fd) { int rfd = (1 << fd), wfd = 0, xfd = 0, retVal; struct timeval pollTime; // decide how long to wait if there are no characters on the file pollTime.tv_sec = 0; if (interrupt->getStatus() == IdleMode) pollTime.tv_usec = 20000; // delay to let other nachos run else pollTime.tv_usec = 0; // no delay // poll file or socket #ifdef HOST_i386 retVal = select(32, (fd_set*)&rfd, (fd_set*)&wfd, (fd_set*)&xfd, &pollTime); #else retVal = select(32, &rfd, &wfd, &xfd, &pollTime); #endif ASSERT((retVal == 0) || (retVal == 1)); if (retVal == 0) return FALSE; // no char waiting to be read return TRUE; } //---------------------------------------------------------------------- // OpenForWrite // Open a file for writing. Create it if it doesn't exist; truncate it // if it does already exist. Return the file descriptor. // // "name" -- file name //---------------------------------------------------------------------- int OpenForWrite(char *name) { int fd = open(name, O_RDWR|O_CREAT|O_TRUNC, 0666); ASSERT(fd >= 0); return fd; } //---------------------------------------------------------------------- // OpenForReadWrite // Open a file for reading or writing. // Return the file descriptor, or error if it doesn't exist. // // "name" -- file name //---------------------------------------------------------------------- int OpenForReadWrite(char *name, bool crashOnError) { int fd = open(name, O_RDWR, 0); ASSERT(!crashOnError || fd >= 0); return fd; } ****** SYSDEP.CC ****** //---------------------------------------------------------------------- // Read // Read characters from an open file. Abort if read fails. //---------------------------------------------------------------------- void Read(int fd, char *buffer, int nBytes) { int retVal = read(fd, buffer, nBytes); ASSERT(retVal == nBytes); } //---------------------------------------------------------------------- // ReadPartial // Read characters from an open file, returning as many as are // available. //---------------------------------------------------------------------- int ReadPartial(int fd, char *buffer, int nBytes) { return read(fd, buffer, nBytes); } //---------------------------------------------------------------------- // WriteFile // Write characters to an open file. Abort if write fails. //---------------------------------------------------------------------- void WriteFile(int fd, char *buffer, int nBytes) { int retVal = write(fd, buffer, nBytes); ASSERT(retVal == nBytes); } //---------------------------------------------------------------------- // Lseek // Change the location within an open file. Abort on error. //---------------------------------------------------------------------- void Lseek(int fd, int offset, int whence) { int retVal = lseek(fd, offset, whence); ASSERT(retVal >= 0); } //---------------------------------------------------------------------- // Tell // Report the current location within an open file. //---------------------------------------------------------------------- int Tell(int fd) { #ifdef HOST_i386 return lseek(fd,0,SEEK_CUR); // 386BSD doesn't have the tell() system call #else return tell(fd); #endif } ****** SYSDEP.CC ****** //---------------------------------------------------------------------- // Close // Close a file. Abort on error. //---------------------------------------------------------------------- void Close(int fd) { int retVal = close(fd); ASSERT(retVal >= 0); } //---------------------------------------------------------------------- // Unlink // Delete a file. //---------------------------------------------------------------------- bool Unlink(char *name) { return unlink(name); } // Socket functions deleted by O'D //---------------------------------------------------------------------- // CallOnUserAbort // Arrange that "func" will be called when the user aborts (e.g., by // hitting ctl-C. //---------------------------------------------------------------------- void CallOnUserAbort(VoidNoArgFunctionPtr func) { (void)signal(SIGINT, (VoidFunctionPtr) func); } //---------------------------------------------------------------------- // Sleep // Put the UNIX process running Nachos to sleep for x seconds, // to give the user time to start up another invocation of Nachos // in a different UNIX shell. //---------------------------------------------------------------------- void Delay(int seconds) { (void) sleep((unsigned) seconds); } //---------------------------------------------------------------------- // Abort // Quit and drop core. //---------------------------------------------------------------------- void Abort() { abort(); } ****** SYSDEP.CC ****** //---------------------------------------------------------------------- // Exit // Quit without dropping core. //---------------------------------------------------------------------- void Exit(int exitCode) { exit(exitCode); } //---------------------------------------------------------------------- // RandomInit // Initialize the pseudo-random number generator. We use the // now obsolete "srand" and "rand" because they are more portable! //---------------------------------------------------------------------- void RandomInit(unsigned seed) { srand(seed); } //---------------------------------------------------------------------- // Random // Return a pseudo-random number. //---------------------------------------------------------------------- int Random() { return rand(); } //---------------------------------------------------------------------- // AllocBoundedArray // Return an array, with the two pages just before // and after the array unmapped, to catch illegal references off // the end of the array. Particularly useful for catching overflow // beyond fixed-size thread execution stacks. // // Note: Just return the useful part! // // "size" -- amount of useful space needed (in bytes) //---------------------------------------------------------------------- char * AllocBoundedArray(int size) { int pgSize = getpagesize(); char *ptr = new char[pgSize * 2 + size]; mprotect(ptr, pgSize, 0); mprotect(ptr + pgSize + size, pgSize, 0); return ptr + pgSize; } ****** SYSDEP.CC ****** //---------------------------------------------------------------------- // DeallocBoundedArray // Deallocate an array of integers, unprotecting its two boundary pages. // // "ptr" -- the array to be deallocated // "size" -- amount of useful space in the array (in bytes) //---------------------------------------------------------------------- void DeallocBoundedArray(char *ptr, int size) { int pgSize = getpagesize(); mprotect(ptr - pgSize, pgSize, PROT_READ | PROT_WRITE | PROT_EXEC); mprotect(ptr + size, pgSize, PROT_READ | PROT_WRITE | PROT_EXEC); delete [] (ptr - pgSize); } ************************* ****** TRANSLATE.H ****** ************************* // translate.h // Data structures for managing the translation from // virtual page # -> physical page #, used for managing // physical memory on behalf of user programs. // // The data structures in this file are "dual-use" - they // serve both as a page table entry, and as an entry in // a software-managed translation lookaside buffer (TLB). // Either way, each entry is of the form: // . // // DO NOT CHANGE -- part of the machine emulation #ifndef TLB_H #define TLB_H #include "utility.h" // The following class defines an entry in a translation table -- either // in a page table or a TLB. Each entry defines a mapping from one // virtual page to one physical page. // In addition, there are some extra bits for access control (valid and // read-only) and some bits for usage information (use and dirty). class TranslationEntry { public: int virtualPage; // The page number in virtual memory. int physicalPage; // The page number in real memory (relative to the // start of "mainMemory" bool valid; // If this bit is set, the translation is ignored. // (In other words, the entry hasn't been initialized.) bool readOnly; // If this bit is set, the user program is not allowed // to modify the contents of the page. bool use; // This bit is set by the hardware every time the // page is referenced or modified. bool dirty; // This bit is set by the hardware every time the // page is modified. }; #endif ************************** ****** TRANSLATE.CC ****** ************************** // translate.cc // Routines to translate virtual addresses to physical addresses. Software // sets up a table of legal translations. We look up in the table on // every memory reference to find the true physical memory location. // // Two types of translation are supported here. // // Linear page table -- the virtual page # is used as an index // into the table, to find the physical page #. // // Translation lookaside buffer -- associative lookup in the table to find // an entry with the same virtual page #. If found, this entry is used // for the translation. If not, it traps to software with an exception. // // In practice, the TLB is much smaller than the amount of physical memory // (16 entries is common on a machine that has 1000's of pages). Thus, // there must also be a backup translation scheme (such as page tables), // but the hardware doesn't need to know anything at all about that. // // Note that the contents of the TLB are specific to an address space. // If the address space changes, so does the contents of the TLB! // // DO NOT CHANGE -- part of the machine emulation #include "machine.h" #include "addrspace.h" #include "system.h" // Routines for converting Words and Short Words to and from the // simulated machine's format of little endian. These end up // being NOPs when the host machine is also little endian (DEC and Intel). unsigned int WordToHost(unsigned int word) { #ifdef HOST_IS_BIG_ENDIAN register unsigned long result; result = (word >> 24) & 0x000000ff; result |= (word >> 8) & 0x0000ff00; result |= (word << 8) & 0x00ff0000; result |= (word << 24) & 0xff000000; return result; #else return word; #endif /* HOST_IS_BIG_ENDIAN */ } unsigned short ShortToHost(unsigned short shortword) { #ifdef HOST_IS_BIG_ENDIAN register unsigned short result; result = (shortword << 8) & 0xff00; result |= (shortword >> 8) & 0x00ff; return result; #else return shortword; #endif /* HOST_IS_BIG_ENDIAN */ } unsigned int WordToMachine(unsigned int word) { return WordToHost(word); } unsigned short ShortToMachine(unsigned short shortword) { return ShortToHost(shortword); } ****** TRANSLATE.CC ****** //---------------------------------------------------------------------- // Machine::ReadMem // Read "size" (1, 2, or 4) bytes of virtual memory at "addr" into // the location pointed to by "value". // // Returns FALSE if the translation step from virtual to physical memory // failed. // // "addr" -- the virtual address to read from // "size" -- the number of bytes to read (1, 2, or 4) // "value" -- the place to write the result //---------------------------------------------------------------------- bool Machine::ReadMem(int addr, int size, int *value) { int data; ExceptionType exception; int physicalAddress; DEBUG('a', "Reading VA 0x%x, size %d\n", addr, size); exception = Translate(addr, &physicalAddress, size, FALSE); if (exception != NoException) { machine->RaiseException(exception, addr); return FALSE; } switch (size) { case 1: data = machine->mainMemory[physicalAddress]; *value = data; break; case 2: data = *(unsigned short *) &machine->mainMemory[physicalAddress]; *value = ShortToHost(data); break; case 4: data = *(unsigned int *) &machine->mainMemory[physicalAddress]; *value = WordToHost(data); break; default: ASSERT(FALSE); } DEBUG('a', "\tvalue read = %8.8x\n", *value); return (TRUE); } ****** TRANSLATE.CC ****** //---------------------------------------------------------------------- // Machine::WriteMem // Write "size" (1, 2, or 4) bytes of the contents of "value" into // virtual memory at location "addr". // // Returns FALSE if the translation step from virtual to physical memory // failed. // // "addr" -- the virtual address to write to // "size" -- the number of bytes to be written (1, 2, or 4) // "value" -- the data to be written //---------------------------------------------------------------------- bool Machine::WriteMem(int addr, int size, int value) { ExceptionType exception; int physicalAddress; DEBUG('a', "Writing VA 0x%x, size %d, value 0x%x\n", addr, size, value); exception = Translate(addr, &physicalAddress, size, TRUE); if (exception != NoException) { machine->RaiseException(exception, addr); return FALSE; } switch (size) { case 1: machine->mainMemory[physicalAddress] = (unsigned char) (value & 0xff); break; case 2: *(unsigned short *) &machine->mainMemory[physicalAddress] = ShortToMachine((unsigned short) (value & 0xffff)); break; case 4: *(unsigned int *) &machine->mainMemory[physicalAddress] = WordToMachine((unsigned int) value); break; default: ASSERT(FALSE); } return TRUE; } ****** TRANSLATE.CC ****** //---------------------------------------------------------------------- // Machine::Translate // Translate a virtual address into a physical address, using // either a page table or a TLB. Check for alignment and all sorts // of other errors, and if everything is ok, set the use/dirty bits in // the translation table entry, and store the translated physical // address in "physAddr". If there was an error, returns the type // of the exception. // // "virtAddr" -- the virtual address to translate // "physAddr" -- the place to store the physical address // "size" -- the amount of memory being read or written // "writing" -- if TRUE, check the "read-only" bit in the TLB //---------------------------------------------------------------------- ExceptionType Machine::Translate(int virtAddr, int* physAddr, int size, bool writing) { int i; unsigned int vpn, offset; TranslationEntry *entry; unsigned int pageFrame; DEBUG('a', "\tTranslate 0x%x, %s: ", virtAddr, writing ? "write" : "read"); // check for alignment errors if (((size == 4) && (virtAddr & 0x3)) || ((size == 2) && (virtAddr & 0x1))){ DEBUG('a', "alignment problem at %d, size %d!\n", virtAddr, size); return AddressErrorException; } // we must have either a TLB or a page table, but not both! ASSERT(tlb == NULL || pageTable == NULL); ASSERT(tlb != NULL || pageTable != NULL); ****** continued on next page ****** ****** TRANSLATE.CC ****** // calculate the virtual page number, and offset within the page, // from the virtual address vpn = (unsigned) virtAddr / PageSize; offset = (unsigned) virtAddr % PageSize; if (tlb == NULL) { // => page table => vpn is index into table if (vpn >= pageTableSize) { DEBUG('a', "virtual page # %d too large for page table size %d!\n", virtAddr, pageTableSize); return AddressErrorException; } else if (!pageTable[vpn].valid) { DEBUG('a', "virtual page # %d too large for page table size %d!\n", virtAddr, pageTableSize); return PageFaultException; } entry = &pageTable[vpn]; } else { for (entry = NULL, i = 0; i < TLBSize; i++) if (tlb[i].valid && (tlb[i].virtualPage == vpn)) { entry = &tlb[i]; // FOUND! break; } if (entry == NULL) { // not found DEBUG('a', "*** no valid TLB entry found for this virtual page!\n"); return PageFaultException; // really, this is a TLB fault, // the page may be in memory, // but not in the TLB } } if (entry->readOnly && writing) { // trying to write to a read-only page DEBUG('a', "%d mapped read-only at %d in TLB!\n", virtAddr, i); return ReadOnlyException; } pageFrame = entry->physicalPage; // if the pageFrame is too big, there is something really wrong! // An invalid translation was loaded into the page table or TLB. if (pageFrame >= NumPhysPages) { DEBUG('a', "*** frame %d > %d!\n", pageFrame, NumPhysPages); return BusErrorException; } entry->use = TRUE; // set the use, dirty bits if (writing) entry->dirty = TRUE; *physAddr = pageFrame * PageSize + offset; ASSERT((*physAddr >= 0) && ((*physAddr + size) <= MemorySize)); DEBUG('a', "phys addr = 0x%x\n", *physAddr); return NoException; } *********************** ****** MACHINE.H ****** *********************** // machine.h // Data structures for simulating the execution of user programs // running on top of Nachos. // // User programs are loaded into "mainMemory"; to Nachos, // this looks just like an array of bytes. Of course, the Nachos // kernel is in memory too -- but as in most machines these days, // the kernel is loaded into a separate memory region from user // programs, and accesses to kernel memory are not translated or paged. // // In Nachos, user programs are executed one instruction at a time, // by the simulator. Each memory reference is translated, checked // for errors, etc. // // DO NOT CHANGE -- part of the machine emulation #ifndef MACHINE_H #define MACHINE_H #include "utility.h" #include "translate.h" #include "disk.h" // Definitions related to the size, and format of user memory #define PageSize SectorSize // set the page size equal to // the disk sector size, for // simplicity #define NumPhysPages 32 #define MemorySize (NumPhysPages * PageSize) #define TLBSize 4 // if there is a TLB, make it small enum ExceptionType { NoException, // Everything ok! SyscallException, // A program executed a system call. PageFaultException, // No valid translation found ReadOnlyException, // Write attempted to page marked // "read-only" BusErrorException, // Translation resulted in an // invalid physical address AddressErrorException, // Unaligned reference or one that // was beyond the end of the // address space OverflowException, // Integer overflow in add or sub. IllegalInstrException, // Unimplemented or reserved instr. NumExceptionTypes }; ****** MACHINE.H ****** // User program CPU state. The full set of MIPS registers, plus a few // more because we need to be able to start/stop a user program between // any two instructions (thus we need to keep track of things like load // delay slots, etc.) #define StackReg 29 // User's stack pointer #define RetAddrReg 31 // Holds return address for procedure calls #define NumGPRegs 32 // 32 general purpose registers on MIPS #define HiReg 32 // Double register to hold multiply result #define LoReg 33 #define PCReg 34 // Current program counter #define NextPCReg 35 // Next program counter (for branch delay) #define PrevPCReg 36 // Previous program counter (for debugging) #define LoadReg 37 // The register target of a delayed load. #define LoadValueReg 38 // The value to be loaded by a delayed load. #define BadVAddrReg 39 // The failing virtual address on an exception #define NumTotalRegs 40 // The following class defines an instruction, represented in both // undecoded binary form // decoded to identify // operation to do // registers to act on // any immediate operand value class Instruction { public: void Decode(); // decode the binary representation of the instruction unsigned int value; // binary representation of the instruction char opCode; // Type of instruction. This is NOT the same as the // opcode field from the instruction: see defs in mips.h char rs, rt, rd; // Three registers from instruction. int extra; // Immediate or target or shamt field or offset. // Immediates are sign-extended. }; // The following class defines the simulated host workstation hardware, as // seen by user programs -- the CPU registers, main memory, etc. // User programs shouldn't be able to tell that they are running on our // simulator or on the real hardware, except // we don't support floating point instructions // the system call interface to Nachos is not the same as UNIX // (10 system calls in Nachos vs. 200 in UNIX!) // If we were to implement more of the UNIX system calls, we ought to be // able to run Nachos on top of Nachos! // // The procedures in this class are defined in machine.cc, mipssim.cc, and // translate.cc. ****** MACHINE.H ****** class Machine { public: Machine(bool debug); // Initialize the simulation of the hardware // for running user programs ~Machine(); // De-allocate the data structures // Routines callable by the Nachos kernel void Run(); // Run a user program int ReadRegister(int num); // read the contents of a CPU register void WriteRegister(int num, int value); // store a value into a CPU register // Routines internal to the machine simulation -- DO NOT call these void OneInstruction(Instruction *instr); // Run one instruction of a user program. void DelayedLoad(int nextReg, int nextVal); // Do a pending delayed load (modifying a reg) bool ReadMem(int addr, int size, int* value); bool WriteMem(int addr, int size, int value); // Read or write 1, 2, or 4 bytes of virtual // memory (at addr). Return FALSE if a // correct translation couldn't be found. ExceptionType Translate(int virtAddr, int* physAddr, int size,bool writing); // Translate an address, and check for // alignment. Set the use and dirty bits in // the translation entry appropriately, // and return an exception code if the // translation couldn't be completed. void RaiseException(ExceptionType which, int badVAddr); // Trap to the Nachos kernel, because of a // system call or other exception. void Debugger(); // invoke the user program debugger void DumpState(); // print the user CPU and memory state // Data structures -- all of these are accessible to Nachos kernel code. // "public" for convenience. // // Note that *all* communication between the user program and the kernel // are in terms of these data structures. char *mainMemory; // physical memory to store user program, // code and data, while executing int registers[NumTotalRegs]; // CPU registers, for executing user programs ****** MACHINE.H ****** // NOTE: the hardware translation of virtual addresses in the user program // to physical addresses (relative to the beginning of "mainMemory") // can be controlled by one of: // a traditional linear page table // a software-loaded translation lookaside buffer (tlb) -- a cache of // mappings of virtual page #'s to physical page #'s // // If "tlb" is NULL, the linear page table is used // If "tlb" is non-NULL, the Nachos kernel is responsible for managing // the contents of the TLB. But the kernel can use any data structure // it wants (eg, segmented paging) for handling TLB cache misses. // // For simplicity, both the page table pointer and the TLB pointer are // public. However, while there can be multiple page tables (one per address // space, stored in memory), there is only one TLB (implemented in hardware). // Thus the TLB pointer should be considered as *read-only*, although // the contents of the TLB are free to be modified by the kernel software. TranslationEntry *tlb; // this pointer should be considered // "read-only" to Nachos kernel code TranslationEntry *pageTable; unsigned int pageTableSize; private: bool singleStep; // drop back into the debugger after each // simulated instruction int runUntilTime; // drop back into the debugger when simulated // time reaches this value }; extern void ExceptionHandler(ExceptionType which); // Entry point into Nachos for handling // user system calls and exceptions // Defined in exception.cc // Routines for converting Words and Short Words to and from the // simulated machine's format of little endian. If the host machine // is little endian (DEC and Intel), these end up being NOPs. // // What is stored in each format: // host byte ordering: // kernel data structures // user registers // simulated machine byte ordering: // contents of main memory unsigned int WordToHost(unsigned int word); unsigned short ShortToHost(unsigned short shortword); unsigned int WordToMachine(unsigned int word); unsigned short ShortToMachine(unsigned short shortword); #endif // MACHINE_H ****** ****** UTILITY.H ****** // utility.h // Miscellaneous useful definitions, including debugging routines. // // The debugging routines allow the user to turn on selected // debugging messages, controllable from the command line arguments // passed to Nachos (-d). You are encouraged to add your own // debugging flags. The pre-defined debugging flags are: // // '+' -- turn on all debug messages // 't' -- thread system // 's' -- semaphores, locks, and conditions // 'i' -- interrupt emulation // 'm' -- machine emulation (USER_PROGRAM) // 'd' -- disk emulation (FILESYS) // 'f' -- file system (FILESYS) // 'a' -- address spaces (USER_PROGRAM) // 'n' -- network emulation (NETWORK) #ifndef UTILITY_H #define UTILITY_H // Miscellaneous useful routines // #include -- commented this out - Ilia // Boolean values. // This is the same definition // as in the g++ library. // RJF - Changed this line #define FALSE 0 #define TRUE 1 #define min(a,b) (((a) < (b)) ? (a) : (b)) #define max(a,b) (((a) > (b)) ? (a) : (b)) // Divide and either round up or down #define divRoundDown(n,s) ((n) / (s)) #define divRoundUp(n,s) (((n) / (s)) + ((((n) % (s)) > 0) ? 1 : 0)) // This declares the type "VoidFunctionPtr" to be a "pointer to a // function taking an integer argument and returning nothing". With // such a function pointer (say it is "func"), we can call it like this: // // (*func) (17); // // This is used by Thread::Fork and for interrupt handlers, as well // as a couple of other places. typedef void (*VoidFunctionPtr)(int arg); typedef void (*VoidNoArgFunctionPtr)(); // Include interface that isolates us from the host machine system library. // Requires definition of bool, and VoidFunctionPtr #include "sysdep.h" // Interface to debugging routines. Omitted by O'D. ******************** ****** LIST.H ****** ******************** // list.h // Data structures to manage LISP-like lists. // // As in LISP, a list can contain any type of data structure // as an item on the list: thread control blocks, // pending interrupts, etc. That is why each item is a "void *", // or in other words, a "pointers to anything". #ifndef LIST_H #define LIST_H #include "utility.h" // The following class defines a "list element" -- which is // used to keep track of one item on a list. It is equivalent to a // LISP cell, with a "car" ("next") pointing to the next element on the list, // and a "cdr" ("item") pointing to the item on the list. // // Internal data structures kept public so that List operations can // access them directly. class ListElement { public: ListElement(void *itemPtr, int sortKey); // initialize a list element ListElement *next; // next element on list, // NULL if this is the last int key; // priority, for a sorted list void *item; // pointer to item on the list }; // The following class defines a "list" -- a singly linked list of // list elements, each of which points to a single item on the list. // // By using the "Sorted" functions, the list can be kept in sorted // in increasing order by "key" in ListElement. 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 }; #endif // LIST_H ****** ****** BITMAP.H ****** // bitmap.h // Data structures defining a bitmap -- an array of bits each of which // can be either on or off. // // Represented as an array of unsigned integers, on which we do // modulo arithmetic to find the bit we are interested in. // // The bitmap can be parameterized with with the number of bits being // managed. #ifndef BITMAP_H #define BITMAP_H #include "utility.h" #include "openfile.h" // Definitions helpful for representing a bitmap as an array of integers #define BitsInByte 8 #define BitsInWord 32 // The following class defines a "bitmap" -- an array of bits, // each of which can be independently set, cleared, and tested. // // Most useful for managing the allocation of the elements of an array -- // for instance, disk sectors, or main memory pages. // Each bit represents whether the corresponding sector or page is // in use or free. class BitMap { public: BitMap(int nitems); // Initialize a bitmap, with "nitems" bits // initially, all bits are cleared. ~BitMap(); // De-allocate bitmap void Mark(int which); // Set the "nth" bit void Clear(int which); // Clear the "nth" bit bool Test(int which); // Is the "nth" bit set? int Find(); // Return the # of a clear bit, and as a side // effect, set the bit. // If no bits are clear, return -1. int NumClear(); // Return the number of clear bits void Print(); // Print contents of bitmap // These aren't needed until FILESYS, when we will need to read and // write the bitmap to a file void FetchFrom(OpenFile *file); // fetch contents from disk void WriteBack(OpenFile *file); // write contents to disk private: int numBits; // number of bits in the bitmap int numWords; // number of words of bitmap storage // (rounded up if numBits is not a // multiple of the number of bits in // a word) unsigned int *map; // bit storage }; #endif // BITMAP_H