380 likes | 467 Views
Dive into Nachos Operating System to gain hands-on experience and understand OS features, system calls, and implementation. Explore kernel threads, user processes, and MIPS instruction execution. Step-by-step guide to install and navigate Nachos code directory for projects. Learn thread management, scheduling, locks, and synchronization. Create and manage threads using Nachos structure. Understand Nachos thread states and transitions for efficient execution. Master Semaphore implementation for thread synchronization and mutual exclusion. Get a walkthrough of reading Nachos source code, compiling, running, and tracing execution. Achieve comprehensive understanding of OS concepts with Nachos.
E N D
Nachos Instructional OS and Project 1 CS 170, Tao Yang
Why Nachos OS? • Learn by reading and modifying simple operating system code • Extremely important OS experience • Deep understanding of OS features/system calls • Where/when they can fail, or have poor performance • A skeletal OS that supports • kernel threads, user-level processes • MIPS instruction execution as a virtual machine • ~9K lines of C++ code.
System Layers User process User process Nachos kernel threads Thread 1 Thread 2 Thread N Nachos OS modules (Threads mgm, File System, Code execution/memory mapping, System calls/Interrupt) Simulated MIPS Machine (CPU, Memory, Disk, Console) Base Operating System (Linux for our class)
Project 1B Tasks 1-3 main() Nachos threads ThreadTest() Thread 1 Thread 2 Thread N Nachos OS modules Simulated MIPS Machine Linux
Steps to Install Nachos • Obtain and install Nachos source code. • Copy the source code from ~cs170/nachos2015.tar • Compile the source code with Makefile • Run Nachos demo • Under the threads subdirectory (run threads) • Under the userprog subdirectory (compile/execute binary code)
Nachos code directory • machine --- Basic machine specification (MIPS simulator as a virtual machine). • threads --- threads management (Project 1). • userprog -- binary code execution and system calls (Project 2). • vm -- virtual memory (empty, Project 3A). • filesys -- file system (Project 3B) • test -- binary code to be executed in this virtual machine • network -- networking protocol (not used in this class) • bin -- utilities/tools (binary format conversion)
Source code reading and Project 1 Objectives of next 2 weeks • Scan through ~1,000 lines of code under threads directory • Learn how context switch is accomplished among threads. • Learn how thread scheduling is done. • Learn how locks/synchronization are implemented and used. • Use Linux pthreads. • Complete Project 1
Sample Example of Nacho Threads int shared=1; main () { Thread *t1 = new Thread("forked thread1"); Thread *t2 = new Thread("forked thread2"); t1->Fork(SimpleThread, 1); t2->Fork(SimpleThread, 2); SimpleThread(3); } SimpleThread(int i) { printf(“Hello %d. Shared %d\n”, i, shared); currentThread->Yield(); } Create 2 new threads. Start to fork and execute a function in each child thread. Parent also executes the same function Function executed by threads
Nachos Threads • Each thread has the following TCB (thread control block) from thread.h int *stackTop; // stack pointer int machineState[18]; // copy of registers int *stack // bottom of stack ThreadStatus status; //ready, running, or blocked char *name; • Thread operations: • Thread(char *debugName). Create a thread. • Fork(VoidFunctionPtr func, int arg). Let a thread execute a function. • Yield(). Suspend the calling thread and select a new one for execution. • Sleep(). Suspend the current thread, change its state to BLOCKED, and remove it from the ready list • Finish()
Nachos Thread States and Transitions Thread::Yield running (kernel) Thread::Sleep Scheduler::Run blocked ready Scheduler::ReadyToRun
Semaphore object for thread synchronization • Disable and re-enable interrupts to achieve mutual exclusion (e.g., by calling Interrupt::SetLevel()). • Operations for a Semaphore object: • Semaphore(char* debugName, int initialValue) • P(): Decrement the semaphore's count, blocking the caller if the count is zero. • V() :Increment the semaphore's count, releasing one thread if any are blocked waiting on the count.
Semaphore Implementation with no busy waiting • Each semaphore has a waiting queue. • Two internal operations: • Sleep– place the thread invoking the operation on the waiting queue. • Nachos: Thread:Sleep() • wakeup– remove one thread in the waiting queue and place it in the ready queue.
Semaphore Implementation with no Busy waiting (Cont.) • P(semaphore *S) • Disable-interrupt S->value--; if (S->value < 0) { Add this thread to waiting-queue; sleep(); } Enable-interrupt • V(semaphore *S) • Disable-interrupt S->value++; if (S->value <= 0) { Remove x from waiting-queue; wakeup(x); } Enable-interrupt
Reading source code • Have a picture of overall components and their roles. • Be able to compile and run a simple case. • Trace the code execution through a simple case • Basic/high level mechanism involved in execution. • Walk through a simple thread test case in Nachos. Week 2 link in http://www.cs.ucsb.edu/~cs170/refer.html
Project 1A: Concurrent Hash Table Many pthreads
Many pthreads What each thread test calls? void *tfunc(void *arg){ long k= (long) arg; //thread ID inti, b=NUMKEYS/NumberofThread; for (i= b *k; i< b*k+b && i<NUMKEYS; i++){ test1(&hash, NUMKEYS, i,2); } } void test1(HashMap *htable, int n, int k, int w){ int i, errorflag=0; for (i=0; i<n; i++) (*htable).get(i); for (i=k-w; i<k+w; i++) (*htable).put(i,i); if((*htable).get(k)==-1) errorflag=1; (*htable).remove(k); for (i=0; i<n; i++) (*htable).get(i); (*htable).put(k,k); for (i=0; i<n; i++) (*htable).get(i);} get() put() remove()
Coarse-grain synchronization of concurrent hash table access Many pthreads
Fine-grain synchronization Many pthreads
Part B of Project 1 Objectives • Tasks 1-3 under threads subdirectory. • Gain experience with simple thread programming (execute multiple Nachos threads with synchronization). • Implement locks and condition variables (missing from the file synch.cc). • Use for concurrent access of a hash table
Project 1B Tasks 1-3 main() Nachos threads ThreadTest() Thread 1 Thread 2 Thread N Nachos OS modules Hashtable Simulated MIPS Machine Linux
Tasks 1/2/3: Nachos Files involved Key files to read and modify (modify 3 files in red) • main.cc, threadtest.cc -- a simple test of our thread routines. • TA Varun provides a sample change template for threadtest.cc • thread.h thread.cc -- Nachos thread data structure and operations. • scheduler.h scheduler.cc -- The thread ready list. • synch.h synch.cc -- synchronization routines. • system.h, system.cc -- Nachos startup initialization /shutdown • switch.h, switch.s -- assembly code for thread switching. Other related files: • synchlist.h, synchlist.cc -- synchronized access to lists using locks/conditions (useful examples for your programming). • list.h list.cc -- generic list management. • utility.h utility.cc -- some useful definitions and debugging routines. • interrupt.h interrupt.cc -- manage interrupts. • time.h timer.cc -- clock emulation. • stats.h stats.cc -- collect interesting statistics.
Tasks of Project 1 (Part B) • Task 1 • Synchronize hash table (fine-grain) with semaphore • Task 2. • Implement Lock code in synch.cc similar as Nachos semaphore code • Follow the Nachos semaphore implementation • Synchronize hash table (fine-grain) • Task 3 • Implement conditional variables • Synchronize hash table (fine-grain)
Key steps when Nachos executes After you type ``nachos'' under threads subdirectory: • It is executing as a single Linux process. • The main() calls • Initialize() to start up interrupt handling, create a scheduler for managing the ready queue. • ThreadTest() • currentThread->Finish() to let other threads continue to run.
Current Nachos Threadtest.cc void ThreadTest1() { Thread *t = new Thread("forked thread"); t->Fork(SimpleThread, 1); SimpleThread(0); } void SimpleThread(int which) { int num, val; for(num = 0; num < 5; num++) { printf("*** thread %d looped %d times\n", which, num); c currentThread->Yield(); } } *** thread 0 looped 0 times*** thread 1 looped 0 times*** thread 0 looped 1 times*** thread 1 looped 1 times*** thread 0 looped 2 times*** thread 1 looped 2 times*** thread 0 looped 3 times*** thread 1 looped 3 times*** thread 0 looped 4 times*** thread 1 looped 4 timesNo threads ready or runnable, and no pending interrupts.Assuming the program completed.Machine halting
Key calling graph when Nachos executes under thread directory StackAllocate() in thread.cc All files are in threads directory. Thread:Fork () in thread.cc Initialize() in system.cc SWITCH () in switch.s FindNextToRun () in scheduler.cc Thread:Yield () in thread.cc main() in main.cc ThreadRoot () in switch.s ReadyToRun () in scheduler.cc ThreadTest () in threadtest.cc func() such as SimpleThread() in ThreadTest.cc Run () in scheduler.cc currentThread->Finish () in threadtest.cc
Key Calling graph for Project 1 Task 1 All files are in thread directory. Thread:Fork () in thread.cc Spawn multiple threads similar to ptest.cc in Part A (hashtable test) Insert some Yield() Initialize() in system.cc Thread:Yield () in thread.cc main() in main.cc ThreadTest () in threadtest.cc Semaphore P()/V() in synch.cc currentThread->Finish () in threadtest.cc
Nachos Scheduler object for thread scheduling • Decide which thread to run next • Invoked when the current thread gives up CPU. • The current Nachos scheduling policy is round-robin: • Select the front of ready queue list • Append new threads to the end. • Key operations: • ReadyToRun(Thread *thread). Make thread ready to run and add to ready list. • Thread *FindNextToRun() • Run(Thread *nextThread) Nachos scheduler
Thread Switching • Suspend current thread, save its state, and restore the state of new thread. • Switch(oldThread, newThread): • Save all registers in oldThread's TCB. • to be used when the old thread is resumed. • Load new values into the registers from TCB of the new thread.
Key operations of Nachos’ function SWITCH() Thread->Fork(func(), arg) Save current thread context Call InterruptEnable() SWITCH () in switch.s Load target thread context func(arg) Call func(arg) Call ThreadRoot () in switch.s Call ThreadFinish()
TCB of Nachos Threads • Each thread has the following TCB (thread control block) from thread.h int *stackTop; // stack pointer int machineState[18]; // copy of registers int *stack // bottom of stack ThreadStatus status; //ready, running, or blocked char *name; • Thread operations: • Thread(char *debugName). Create a thread. • Fork(VoidFunctionPtr func, int arg). Let a thread execute a function. • Yield(). Suspend the calling thread and select a new one for execution. • Sleep(). Suspend the current thread, change its state to BLOCKED, and remove it from the ready list • Finish()
Thread Switching int *stackTop; int machineState[18] int *stack ThreadStatus status; int *stackTop; int machineState[18] int *stack ThreadStatus status; • Switch(oldThread, newThread): • Save all registers in oldThread's TCB. • to be used when the old thread is resumed. • Load new values into the registers from TCB of the new thread.
SWITCH(oldThread a0, nextThread a1)Save register values to current thread’s TCB • # a0 -- pointer to old thread’s TCB sw sp, SP(a0) # save new stack pointer sw s0, S0(a0) 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) sw ra, PC(a0) # save return address TCB MIPS Registers save sp stackTop machineState[0] machineState[1] machineState[2] … save s0 save s1 save s2 …
SWITCH(oldThread a0, nextThread a1) What is value of SP and S0? sw sp, SP(a0) # save new stack pointer sw s0, S0(a0) 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) sw ra, PC(a0) # save return address TCB MIPS Registers save sp switch.h #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 stackTop machineState[0] machineState[1] machineState[2] … save s0 save s1 save s2 …
Load register values from new thread’s TCB • a1 -- pointer to new thread’sTCB lw sp, SP(a1) # load the new stack pointer lw s0, S0(a1) lw s1, S1(a1) lw s2, S2(a1) … lw s7, S7(a1) lw fp, FP(a1) lw ra, PC(a1) # load the return address j ra #Call ra which is ThreadRoot(); sp stackTop machineState[0] machineState[1] machineState[2] machineState[3] … machineState[9] s0 s1 s2 s3 ra
Key operations of Nachos’ function SWITCH() Thread->Fork(func(), arg) Save current thread context ThreadRoot Call InterruptEnable() SWITCH () in switch.s Load target thread context func(arg) Call func(arg) Call ThreadRoot () in switch.s Call ThreadFinish()
How TCB is initialized? • # a0 -- pointer to old thread’s TCB a1 -- pointer to new thread’sTCB lw sp, SP(a1) # load the new stack pointer lw s0, S0(a1) lw s1, S1(a1) lw s2, S2(a1) … lw s7, S7(a1) lw fp, FP(a1) lw ra, PC(a1) # load the return address j ra #Call ra which is ThreadRoot(); a1 sp stackTop func() arg ThreadFinish() InterruptEnable() … ThreadRoot() addr s0 s1 s2 s3 ra
Which routine fills values of the new thread TCB? • StackAllocate() in Thread::Fork() machineState[PCState] = (int) ThreadRoot; // PCState=9 machineState[StartupPCState] = (int) InterruptEnable; //StartupPCState=3 =>s3 machineState[InitialPCState] = (int) func; //InitialPCState=0 =>s0 machineState[InitialArgState] = arg; //InitialArgState=1 =>s1 machineState[WhenDonePCState] = (int) ThreadFinish; //WhenDonePCState=2 =>s2
ThreadRoot() uses registers s0, s1,s2,s3 to execute targeted func() jal StartupPC # call InterruptEnable() stored in register s3 move a0, InitialArg #move argument in s1 to a0 jal InitialPC # call main thread procedure func() stored in s0 jal WhenDonePC # when done, call ThreadFinish() in s2