# OS lab 3 [TOC] ## 1-1. New→Ready ### Kernel::ExecAll() * **execfileNum** represent the file need to be executed. * call **Exec** function to execute every file in execfile table. * call **Finish()** to terminate current thread. ```cpp= void Kernel::ExecAll() { for (int i=1;i<=execfileNum;i++) { int a = Exec(execfile[i]); } currentThread->Finish(); //Kernel::Exec(); } ``` ### Kernel::Exec() * return the id of executing thread. * create new thread. * Because creating a new thread,the thread number increment by 1. * create address space for new thread. * call **Thread::Fork()**。 ```cpp= int Kernel::Exec(char* name) { t[threadNum] = new Thread(name, threadNum); t[threadNum]->space = new AddrSpace(); t[threadNum]->Fork((VoidFunctionPtr) &ForkExecute, (void *)t[threadNum]); threadNum++; return threadNum-1; /* cout << "Total threads number is " << execfileNum << endl; for (int n=1;n<=execfileNum;n++) { t[n] = new Thread(execfile[n]); t[n]->space = new AddrSpace(); t[n]->Fork((VoidFunctionPtr) &ForkExecute, (void *)t[n]); cout << "Thread " << execfile[n] << " is executing." << endl; } cout << "debug Kernel::Run finished.\n"; */ // Thread *t1 = new Thread(execfile[1]); // Thread *t1 = new Thread("../test/test1"); // Thread *t2 = new Thread("../test/test2"); // AddrSpace *halt = new AddrSpace(); // t1->space = new AddrSpace(); // t2->space = new AddrSpace(); // halt->Execute("../test/halt"); // t1->Fork((VoidFunctionPtr) &ForkExecute, (void *)t1); // t2->Fork((VoidFunctionPtr) &ForkExecute, (void *)t2); // currentThread->Finish(); // Kernel::Run(); // cout << "after ThreadedKernel:Run();" << endl; // unreachable } ``` ### Thread::Fork(VoidFunctionPtr func, void *arg) * **func** store the function pointer. * call **StackAllocate(func, arg)** to allocate stack size. * call **interrupt->SetLevel()** to disable interrupt before calling scheduler. * Calling ReadyToRun to fork. * Enable interrupt. ```cpp= void Thread::Fork(VoidFunctionPtr func, void *arg) { Interrupt *interrupt = kernel->interrupt; Scheduler *scheduler = kernel->scheduler; IntStatus oldLevel; DEBUG(dbgThread, "Forking thread: " << name << " f(a): " << (int) func << " " << arg); StackAllocate(func, arg); oldLevel = interrupt->SetLevel(IntOff); scheduler->ReadyToRun(this); // ReadyToRun assumes that interrupts // are disabled! (void) interrupt->SetLevel(oldLevel); } ``` ### Thread::StackAllocate() * call AllocBoundedArray system call to assign stack * initalizing all registers in thread depending on the instruction set. ```cpp= void Thread::StackAllocate (VoidFunctionPtr func, void *arg) { stack = (int *) AllocBoundedArray(StackSize * sizeof(int)); #ifdef PARISC // HP stack works from low addresses to high addresses // everyone else works the other way: from high addresses to low addresses stackTop = stack + 16; // HP requires 64-byte frame marker stack[StackSize - 1] = STACK_FENCEPOST; #endif #ifdef SPARC stackTop = stack + StackSize - 96; // SPARC stack must contains at // least 1 activation record // to start with. *stack = STACK_FENCEPOST; #endif #ifdef PowerPC // RS6000 stackTop = stack + StackSize - 16; // RS6000 requires 64-byte frame marker *stack = STACK_FENCEPOST; #endif #ifdef DECMIPS stackTop = stack + StackSize - 4; // -4 to be on the safe side! *stack = STACK_FENCEPOST; #endif #ifdef ALPHA stackTop = stack + StackSize - 8; // -8 to be on the safe side! *stack = STACK_FENCEPOST; #endif #ifdef x86 // the x86 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 = stack + StackSize - 4; // -4 to be on the safe side! *(--stackTop) = (int) ThreadRoot; *stack = STACK_FENCEPOST; #endif #ifdef PARISC machineState[PCState] = PLabelToAddr(ThreadRoot); machineState[StartupPCState] = PLabelToAddr(ThreadBegin); machineState[InitialPCState] = PLabelToAddr(func); machineState[InitialArgState] = arg; machineState[WhenDonePCState] = PLabelToAddr(ThreadFinish); #else machineState[PCState] = (void*)ThreadRoot; machineState[StartupPCState] = (void*)ThreadBegin; machineState[InitialPCState] = (void*)func; machineState[InitialArgState] = (void*)arg; machineState[WhenDonePCState] = (void*)ThreadFinish; #endif } ``` ## 1-2. Running→Ready ### Machine::Run() * set mode to user mode * execute one instrucion once a time * call **OneTick()** to advance timer ```cpp void Machine::Run() { Instruction *instr = new Instruction; // storage for decoded instruction if (debug->IsEnabled('m')) { cout << "Starting program in thread: " << kernel->currentThread->getName(); cout << ", at time: " << kernel->stats->totalTicks << "\n"; } kernel->interrupt->setStatus(UserMode); for (;;) { DEBUG(dbgTraCode, "In Machine::Run(), into OneInstruction " << "== Tick " << kernel->stats->totalTicks << " =="); OneInstruction(instr); DEBUG(dbgTraCode, "In Machine::Run(), return from OneInstruction " << "== Tick " << kernel->stats->totalTicks << " =="); DEBUG(dbgTraCode, "In Machine::Run(), into OneTick " << "== Tick " << kernel->stats->totalTicks << " =="); kernel->interrupt->OneTick(); DEBUG(dbgTraCode, "In Machine::Run(), return from OneTick " << "== Tick " << kernel->stats->totalTicks << " =="); if (singleStep && (runUntilTime <= kernel->stats->totalTicks)) Debugger(); } } ``` ### Interrupt::OneTick() * get the needed statistics from kernel * update total ticks,system tick and user ticks * disable interrupt * check any pending interrupts are ready to execute * enable interrupt * if **yieldOnReturn** is true,it means we need to context switch,then we call **Yield()**。 ```cpp= //---------------------------------------------------------------------- // Interrupt::OneTick // Advance simulated time and check if there are any pending // interrupts to be called. // // Two things can cause OneTick to be called: // interrupts are re-enabled // a user instruction is executed //---------------------------------------------------------------------- void Interrupt::OneTick() { MachineStatus oldStatus = status; Statistics *stats = kernel->stats; // advance simulated time if (status == SystemMode) { stats->totalTicks += SystemTick; stats->systemTicks += SystemTick; } else { stats->totalTicks += UserTick; stats->userTicks += UserTick; } DEBUG(dbgInt, "== Tick " << stats->totalTicks << " =="); // check any pending interrupts are now ready to fire ChangeLevel(IntOn, IntOff); // first, turn off interrupts // (interrupt handlers run with // interrupts disabled) CheckIfDue(FALSE); // check for pending interrupts ChangeLevel(IntOff, IntOn); // re-enable interrupts if (yieldOnReturn) { // if the timer device handler asked // for a context switch, ok to do it now yieldOnReturn = FALSE; status = SystemMode; // yield is a kernel routine kernel->currentThread->Yield(); status = oldStatus; } } ``` ### Thread::Yield() * disable interrupt * call **FindNextToRun()** to get next thread to execute * put current thread to ready queue and execute next thread * enable interrupt ```cpp= void Thread::Yield() { Thread *nextThread; IntStatus oldLevel = kernel->interrupt->SetLevel(IntOff); ASSERT(this == kernel->currentThread); DEBUG(dbgThread, "Yielding thread: " << name); nextThread = kernel->scheduler->FindNextToRun(); if (nextThread != NULL) { kernel->scheduler->ReadyToRun(this); kernel->scheduler->Run(nextThread, FALSE); } (void)kernel->interrupt->SetLevel(oldLevel); } ``` ### Scheduler::FindNextToRun () * If ready list is empty ,return NULL * Otherwise,remove the first element in the list and return it。 ```cpp= Thread * Scheduler::FindNextToRun () { ASSERT(kernel->interrupt->getLevel() == IntOff); if (readyList->IsEmpty()) { return NULL; } else { return readyList->RemoveFront(); } } ``` ### Scheduler::ReadyToRun() * Using **ASSERT** to check whether kernel disable interrrupt. * set thread status to ready. * append this thread to readyList. ```cpp= void Scheduler::ReadyToRun (Thread *thread) { ASSERT(kernel->interrupt->getLevel() == IntOff); DEBUG(dbgThread, "Putting thread on ready list: " << thread->getName()); //cout << "Putting thread on ready list: " << thread->getName() << endl ; thread->setStatus(READY); readyList->Append(thread); } ``` ### Scheduler::Run () * **oldThread**: represents current runing thread. * if oldThread space does not equal to null,call **SaveUserState()** and **SaveState()** to save current memory content. * check whether **oldThread** is overflow. * updating current running thread to **nextThread**,**nextThread** is the upcoming thread to execute. * call **SWITCH** to do context switch. * set the nextThread to status of running. * call **CheckToBeDestroyed()** to delete oldThread if the thread had finished its job. ```cpp= void Scheduler::Run () { Thread *oldThread = kernel->currentThread; ASSERT(kernel->interrupt->getLevel() == IntOff); if (finishing) { // mark that we need to delete current thread ASSERT(toBeDestroyed == NULL); toBeDestroyed = oldThread; } if (oldThread->space != NULL) { // if this thread is a user program, oldThread->SaveUserState(); // save the user's CPU registers oldThread->space->SaveState(); } oldThread->CheckOverflow(); // check if the old thread // had an undetected stack overflow kernel->currentThread = nextThread; // switch to the next thread nextThread->setStatus(RUNNING); // nextThread is now running DEBUG(dbgThread, "Switching from: " << oldThread->getName() << " to: " << 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); // we're back, running oldThread // interrupts are off when we return from switch! ASSERT(kernel->interrupt->getLevel() == IntOff); DEBUG(dbgThread, "Now in thread: " << oldThread->getName()); CheckToBeDestroyed(); // check if thread we were running // before this one has finished // and needs to be cleaned up if (oldThread->space != NULL) { // if there is an address space oldThread->RestoreUserState(); // to restore, do it. oldThread->space->RestoreState(); } } ``` ## 1-3. Running→Waiting (Note: only need to consider console output as an example) ### SynchConsoleOutput::PutChar(char) * ```cpp= void SynchConsoleOutput::PutChar(char ch) { lock->Acquire(); consoleOutput->PutChar(ch); waitFor->P(); lock->Release(); } ``` ### Semaphore::P() * If semaphore is available,kernel will consumes its value * ```cpp= void Semaphore::P() { DEBUG(dbgTraCode, "In Semaphore::P(), " << kernel->stats->totalTicks); Interrupt *interrupt = kernel->interrupt; Thread *currentThread = kernel->currentThread; // disable interrupts IntStatus oldLevel = interrupt->SetLevel(IntOff); while (value == 0) { // semaphore not available queue->Append(currentThread); // so go to sleep currentThread->Sleep(FALSE); } value--; // semaphore available, consume its value // re-enable interrupts (void) interrupt->SetLevel(oldLevel); } ``` ### Append(T) * insert item to the end of list ```cpp= template <class T> void List<T>::Append(T item) { ListElement<T> *element = new ListElement<T>(item); ASSERT(!IsInList(item)); if (IsEmpty()) { // list is empty first = element; last = element; } else { // else put it after last last->next = element; last = element; } numInList++; ASSERT(IsInList(item)); } ``` ### Thread::Sleep(bool) * When entering this function ,interrupt should be disable,we use ASSERT to check that. * set thread status to **BLOCKED** * If the scheduler find out that the ready queue is empty,it will let machine idle. * Otherwise ,call **Scheduler::Run()**. ```cpp= void Thread::Sleep(bool finishing) { Thread *nextThread; ASSERT(this == kernel->currentThread); ASSERT(kernel->interrupt->getLevel() == IntOff); DEBUG(dbgThread, "Sleeping thread: " << name); DEBUG(dbgTraCode, "In Thread::Sleep, Sleeping thread: " << name << ", " << kernel->stats->totalTicks); status = BLOCKED; // cout << "debug Thread::Sleep " << name << "wait for Idle\n"; while ((nextThread = kernel->scheduler->FindNextToRun()) == NULL) { kernel->interrupt->Idle(); // no one to run, wait for an interrupt } // returns when it's time for us to run kernel->scheduler->Run(nextThread, finishing); } ``` ### Scheduler::FindNextToRun() omitted。 ### Scheduler::Run () * **oldThread**: represents current runing thread. * if oldThread space does not equal to null,call **SaveUserState()** and **SaveState()** to save current memory content. * check whether **oldThread** is overflow. * updating current running thread to **nextThread**,**nextThread** is the upcoming thread to execute. * call SWITCH ... * set the nextThread to status of running. * call **CheckToBeDestroyed()** to delete oldThread. ```cpp= void Scheduler::Run () { Thread *oldThread = kernel->currentThread; ASSERT(kernel->interrupt->getLevel() == IntOff); if (finishing) { // mark that we need to delete current thread ASSERT(toBeDestroyed == NULL); toBeDestroyed = oldThread; } if (oldThread->space != NULL) { // if this thread is a user program, oldThread->SaveUserState(); // save the user's CPU registers oldThread->space->SaveState(); } oldThread->CheckOverflow(); // check if the old thread // had an undetected stack overflow kernel->currentThread = nextThread; // switch to the next thread nextThread->setStatus(RUNNING); // nextThread is now running DEBUG(dbgThread, "Switching from: " << oldThread->getName() << " to: " << 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); // we're back, running oldThread // interrupts are off when we return from switch! ASSERT(kernel->interrupt->getLevel() == IntOff); DEBUG(dbgThread, "Now in thread: " << oldThread->getName()); CheckToBeDestroyed(); // check if thread we were running // before this one has finished // and needs to be cleaned up if (oldThread->space != NULL) { // if there is an address space oldThread->RestoreUserState(); // to restore, do it. oldThread->space->RestoreState(); } } ``` ## 1-4. Waiting→Ready (Note: only need to consider console output as an example) ### Semaphore::V() * increase the value of semaphore。 * if waiting queue of semaphore is not empty,call **ReadyToRun** to execute first element in the queue and remove it from the queue. ```cpp= void Semaphore::V() { DEBUG(dbgTraCode, "In Semaphore::V(), " << kernel->stats->totalTicks); Interrupt *interrupt = kernel->interrupt; // disable interrupts IntStatus oldLevel = interrupt->SetLevel(IntOff); if (!queue->IsEmpty()) { // make thread ready. kernel->scheduler->ReadyToRun(queue->RemoveFront()); } value++; // re-enable interrupts (void) interrupt->SetLevel(oldLevel); } ``` ## Scheduler::ReadyToRun(Thread*) omitted. ## 1-5. Running→Terminated (Note: start from the Exit system call is called) * call thread->Finish() ### SC_Exit: ```cpp= case SC_Exit: DEBUG(dbgAddr, "Program exit\n"); val = kernel->machine->ReadRegister(4); cout << "return value:" << val << endl; kernel->currentThread->Finish(); break; ``` ### Thread::Finish () * disable interrupt. * call **Thread::Sleep()** to current thread. ```cpp= void Thread::Finish () { (void) kernel->interrupt->SetLevel(IntOff); ASSERT(this == kernel->currentThread); DEBUG(dbgThread, "Finishing thread: " << name); Sleep(TRUE); // invokes SWITCH // not reached } ``` ### Thread::Sleep(bool) as above mentioned. ### Scheduler::FindNextToRun() as above mentioned. ### Scheduler::Run() as above mentioned. ## 1-6. Ready→Running ### Scheduler::FindNextToRun() as above mentioned. ### SWITCH(Thread*, Thread*) * ## 附錄 ### stats.h ```cpp= // stats.h // Data structures for gathering statistics about Nachos performance. // // DO NOT CHANGE -- these stats are maintained by the machine emulation // // // 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. #ifndef STATS_H #define STATS_H #include "copyright.h" // The following class defines the statistics that are to be kept // about Nachos behavior -- how much time (ticks) elapsed, how // many user instructions executed, etc. // // The fields in this class are public to make it easier to update. class Statistics { public: int totalTicks; // Total time running Nachos int idleTicks; // Time spent idle (no threads to run) int systemTicks; // Time spent executing system code int userTicks; // Time spent executing user code // (this is also equal to # of // user instructions executed) int numDiskReads; // number of disk read requests int numDiskWrites; // number of disk write requests int numConsoleCharsRead; // number of characters read from the keyboard int numConsoleCharsWritten; // number of characters written to the display int numPageFaults; // number of virtual memory page faults int numPacketsSent; // number of packets sent over the network int numPacketsRecvd; // number of packets received over the network Statistics(); // initialize everything to zero void Print(); // print collected statistics }; // Constants used to reflect the relative time an operation would // take in a real system. A "tick" is a just a unit of time -- if you // like, a microsecond. // // Since Nachos kernel code is directly executed, and the time spent // in the kernel measured by the number of calls to enable interrupts, // these time constants are none too exact. const int UserTick = 1; // advance for each user-level instruction const int SystemTick = 10; // advance each time interrupts are enabled const int RotationTime = 500; // time disk takes to rotate one sector const int SeekTime = 500; // time disk takes to seek past one track const int ConsoleTime = 100; // time to read or write one character const int NetworkTime = 100; // time to send or receive one packet const int TimerTicks = 100; // (average) time between timer interrupts #endif // STATS_H ```