540 likes | 780 Views
Nachos Instructional OS: Part 2. CS 270, Tao Yang, Spring 2011. Topics. Program execution in Nachos Given a C program, produce binary MIPS code. Execute this MIPS code. Machine object How to load a program How to execute a program: process-level flow.
E N D
Nachos Instructional OS: Part 2 CS 270, Tao Yang, Spring 2011
Topics • Program execution in Nachos • Given a C program, produce binary MIPS code. • Execute this MIPS code. • Machine object • How to load a program • How to execute a program: process-level flow. • Demo of single-process execution flow. • Programming Assignment 2. • Support multiprogramming. • System calls for process execution. • System calls for a simple file system interface.
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)
Machine • Source code: under machine subdirectory. • Roughly approximates the MIPS architecture • Machine has registers, memory, a CPU and a clock. • Simulated clock used for event scheduling and execution (interrupts). • Can execute an arbitrary program with a sequence of MIPS instructions.
Machine: Code execution Steps • Load instructions into the machine's memory. • Initialize registers (including the program counter PCReg). • Tell the machine to start executing instructions. • The machine then fetches the instruction PCReg points at, decodes it, and executes it. • The process is repeated until an illegal operation is performed or an interrupt is generated. • When a trap or interrupt takes place, an interrupt service routine deals with the condition.
Two modes of executions • User mode (executing MIPS instructions of a user program) • Example: Halt code in test directory • User-programs can only access the memory associated with the simulated machine.
Two modes of executions • Kernel mode • kernel executes when Nachos first starts up • or when a user-program executes an instruction that causes a ``hardware’’ trap • illegal instruction, • page fault • system call
Machine Object: Implement a MIPS machine • an instance created when Nachos starts up. • Supported public variables: • Registers: 40 registers. • Memory: Byte-addressable. • Virtual memory: use a single linear page table or a software-managed TLB.
Machine Object: Supported operations • Machine(bool debug). • Translate(int virtAddr, int* physAddr, int size, bool writing). • OneInstruction(). • Run() • ReadRegister(int num) • WriteRegister(int num, int value) • ReadMem(int addr, int size, int* value) • WriteMem(int addr, int size, int value)
Machine Object: Supported operations • Machine(bool debug). • Translate(int virtAddr, int* physAddr, int size, bool writing). • OneInstruction(). • Run() • ReadRegister(int num) • WriteRegister(int num, int value) • ReadMem(int addr, int size, int* value) • WriteMem(int addr, int size, int value)
Interrupt Object • Maintain an event queue together with a simulated clock. • Supported operations: • Schedule(VoidFunctionPtr handler, int arg, int when, IntType type) Schedule a future event to take place at time ``when''. • Usage: schedule a yield at random interval. • SetLevel(IntStatus level). Used to temporarily disable and re-enable interrupts. Two levels are supported: IntOn and IntOff. • OneTick()-- • CheckIfDue(bool advanceClock). Examines if some event should be serviced. • Idle(). ``advances'' to the clock to the time of the next scheduled event
Interrupt::OneTick() • Software managed clock. • The clock advances 1 tick for user mode, 10 for system mode • after every restored interrupt (disable/enable Interrupt) • or after the MIPS simulator executes one instruction. • When the ready list is empty, fast-advance ticks until the next scheduled event happens.
Timer object • Generate interrupts at regular or random intervals • Then Nachos invokes the predefined clock event handling procedure. • Supported operation: • Timer(VoidFunctionPtr timerHandler, int callArg, bool doRandom). • Create a real-time clock that interrupts • every TimerTicks (100) time units • Or set this a random number for random mode
Console Object • Simulates the behavior of a character-oriented CRT device • Data can be written to the device one character at a time through the PutChar() routine. • Input characters arrive one-at-a-time. They can be retrieved by GetChar(). • Supported operations: • Console(char *readFile, char *writeFile, VoidFunctionPtr readAvail,VoidFunctionPtr writeDone, int callArg). Create a console instance.``readFile'' is the Unix file of where the data is to be read from; if NULL, standard input is assumed. • PutChar(char ch) • GetChar()
Disk Object • Simulates the behavior of a real disk. • The disk has only a single platter, with multiple tracks (32). • Each track contains the same number of sectors (32). • Allow only one pending operation at a time. • Contain a ``track buffer'' cache. Immediately after seeking to a new track, the disk starts reading sectors, placing them in the track buffer. • Supported operations: • Disk(char *name, VoidFunctionPtr callWhenDone, int callArg) • ReadRequest(int sectorNumber, char *data) • WriteRequest(int sectorNumber, char *data) • ComputeLatency(int newSector, bool writing)
data data Executing a user program halt shell user space MIPS instructions executed by the emulator ExceptionHandler() Nachoskernel MIPS emulator Machine::Run() fetch/execute examine/deposit SaveState/RestoreState examine/deposit Rn page table process page tables Machine object SP PC memory registers
data data data data data From C program to MIPS binary myprogram.c myprogram.o object file int j; char* s = “hello\n”; int p() { j = write(1, s, 6); return(j); } assembler libraries and other objects linker ….. p: store this store that push jsr _write ret etc. gcc compiler program myprogram.s myprogram (executable file)
Binary code format (Noff) Source code: under userprog subdirectory. • The current Nachos can run a single MIPS binary (Noff format) e.g. type ``nachos -x ../test/halt''. • A user program must be compiled using a cross-platform gcc compiler that generates MIPS code. • A Noff-format file contains (bin/noff.h) • the Noff header, describes the contents of the rest of the file • executable code segment (TEXT) • initialized data segment (DATA) • uninitialized data segment (BSS).
STACK HEAP BSS DATA TEXT Space usage during execution of a C program Stack grows from top-down. Heap grows bottom-up Uninitialized data Initialized data Code
TEXT, DATA, BSS, HEAP, STACK in C Int f3=3; /* DATA segment */ Int f1; /*BSS segment*/ def[] = "1"; /* DATA segment */ int main(void){static char abc[12], /* BSS segment */static float pi = 3.14159; /* DATA segment */int i = 3; /* Stack*/ char *cp; /*stack*/ cp= malloc(10); /*malloc allocates space from HEAP*/ f1= i+f3; /* code is in TEXT*/ strcpy(abc , "Test" ); /* “Test” is located in DATA segment */ }
Noff format . Each segment has the following information: • virtualAddr: virtual address that segment begins at. • inFileAddr: Pointer within the Noff file where that section actually begins. • The size (in bytes) of that segment.
User process for executing a program A Nachos thread is extended as a process • Each process has its own address space containing • Executable code (Code segment) • Initialized data (Data segment) • Uninitialized data (BSS) • Stack space for function calls/local variables • how big is address space? • A process owns some other objects, such as open file descriptors.
Steps in User process creation Currently only execute a single user program. • Create an address space. • Zero out all of physical memory (machine->mainMemory) • Read the binary into physical memory and initialize data segment. • Initialize the translation tables to do a one-to-one mapping between virtual and physical addresses. • Zero all registers, setting PCReg and NextPCReg to 0 and 4 respectively. • Set the stackpointer to the largest virtual address of the process (stack grows downward).
Key Calling graph when Nachos executes under userprog directory Executable file ReadAt() Space= New AddrSpace() in addrspace.cc Initialize() in system.cc Machine-> WriteRegister() Space-> InitRegisters() main() in main.cc StartProcess () in progtest.cc Space ->RestoreState() Machine-> OneInstruction() Machine->Run () in mipssim.cc Interupt-> OneTick() In Interupt.cc
Creating a Nachos Process (userprog/progtest.cc) Create a handle for reading text and initial data out of the executable file. void StartProcess(char *filename) { OpenFile *executable; AddrSpace *space; executable = fileSystem->Open(filename); if (executable == NULL) { printf("Unable to open file %s\n", filename); return; } space = new AddrSpace(executable); currentThread->space = space; delete executable; // close file space->InitRegisters(); space->RestoreState(); machine->Run(); ASSERT(FALSE); } Create an AddrSpace object, allocating physical memory and setting up the process page table. Set address space of current thread/process. Initialize registers, load pagetable, and begin execution in user mode.
Creating a Nachos Address Space (userprog/addrspace.cc) AddrSpace::AddrSpace(OpenFile *executable) { NoffHeader noffH; unsigned int i, size; executable->ReadAt((char *)&noffH, sizeof(noffH), 0); // 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; 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; } .... Read the header of binary file Compute address space need Setup a page table for address translation
Initializing a Nachos Address Space Zero out memory allocated bzero(machine->mainMemory, size); // copy in the code and data segments into memory if (noffH.code.size > 0) { executable->ReadAt(&(machine->mainMemory[noffH.code.virtualAddr]), noffH.code.size, noffH.code.inFileAddr); } if (noffH.initData.size > 0) { executable->ReadAt(&(machine->mainMemory[noffH.initData.virtualAddr]), noffH.initData.size, noffH.initData.inFileAddr); } Copy code segment to memory Copy initialized data segment to memory
System Calls & Exception Handling • User programs invoke system calls by executing the MIPS ``syscall'' instruction • ``syscall'' generates a hardware trap into the Nachos kernel. • A trap is implemented by invoking RaiseException() with arguments indicating the trap cause. • RaiseException() calls ExceptionHandler() to take care of the • specific problem. • The system call number is stored in Register 2 and the return address is in Register 31. Assembly code for Halt(): Halt: addiu $2,$0,SC_Halt syscall j $31 .end Halt
When typing “nachos –x halt” • The main thread starts by running function StartProcess() in file progtest.cc. This thread is used to run halt binary. • StartProcess() allocates a new address space and loads the halt binary. It also initializes registers and sets up the page table. • Call Machine::Run() to execute the halt binary using the MPIS emulator. • The halt binary invokes the system call Halt(), which causes a trap back to the Nachos kernel via functions RaiseException() and ExceptionHandler(). • The exception handler determines that a Halt() system call was requested from user mode, and it halts Nachos.
Nachos –x halt using halt.c in test directory Machine-> RaiseException(SyscallException) Machine-> OneInstruction() Machine->Run () in mipssim.cc ExceptionHandler(SyscallException) Interrupt-> Halt() In Interupt.cc
Assignment 2: Multiprogramming&System Calls • Modify source code under userprog subdirectory. • ~1200 lines of code. • The crossplatform compiler is under ~cs270t/gcc. • This compiler on x86 machines produces a.out with the coff format. • Use utility coff2noff (under nachos’ bin directory) to convert it as Noff. • Check the makefile under test subdirectory on how to use gcc and coff2noff. • System calls to be implemented: • Multiprogramming: Fork(), Yield(), Exit(), Exec() and Join(). • File and console I/O: Creat(), Open(), Read(), Write(), and Close().
0 0 0 text text text data data data data data data BSS BSS BSS user stack user stack user stack args/env args/env args/env kernel area kernel area kernel area Multi-Processes and the Kernel 2n-1 2n-1 Nachos kernel 2n-1
To run multiple processes Nachos should • Provide the physical memory management; • Set up an address translation table with linear page tables; • Save/restore address-space related state during process switching (AddrSpace::SaveUserState() and AddrSpace:RestoreUserState() are called).
Address translation with linear page tables • A virtual address is split into page number and page offset components • Machine->pageTable points to the page table to be used. • Machine->pageTableSize -- the actual size of the page table. • Process switching requires to set the above pointer properly for each user. • Physical page 0 starts at machine-> mainMemory. • Each page has 128-bytes. The actual number of physical pages is NumPhysPages (32).
A Simple Page Table Each process/VAS has its own page table. Virtual addresses are translated relative to the current page table. process page table PFN 0 PFN 1 PFN i In this example, each VPN j maps to PFN j, but in practice any physical frame may be used for any virtual page. PFN i + offset page #i offset The page tables are themselves stored in memory; a protected register holds a pointer to the current page table. user virtual address physical memory page frames
Assignment 2: Files involved • Key files. • progtest.cc -- test routines to run user code. • addrspace.h addrspace.cc -- create an address space and load the program from disk. • syscall.h -- the system call interface. • exception.cc -- the handler for system calls and other user-level exceptions such as page faults. • filesys.h, openfile.h console.h -- interface to the Nachos file system and console. • Other related files: • bitmap.h bitmap.cc -- manipulate bitmpas (useful for keeping track of physical page frames). • translate.h, translate.cc -- translation tables. • machine.h, machine.cc -- emulates main memory, processor, etc. • mipsim.cc -- emulates MPIS R2/3000 instructions. • console.cc -- emulates a terminal using UNIXfiles.
Questions • Process management • How to let two processes run in parallel? What are fundamental mechanisms to enable this functionality? • Another Nachos Process? Thread? • How to execute Machine->Run? • How to set PC (program counter) correctly in the new process? • How to invoke Machine->Run() from the current execution process?
Assignment 2: Implementation Notes Tao Yang
Part I: Multiprogramming • Fork(func) creates a new user-level (child) process, whose address space starts out as an exact copy of that of the caller (the parent), • Yield(): temporarily relinquish the CPU to another process. • Exit(int) call takes a single argument, which is an integer status value as in Unix. The currently executing process is terminated.. • Exec(filename) spawns a new user-level thread (process), but creates a new address space. It should return to the parent a SpaceId. • Join(ID) call waits and returns only after a process with the specified ID has finished.
Getting Started • Review start.s (under test directory) which includes all system call stubs, following the style of Halt. • Modify ExceptionHandler() in exception.cc to include all system call entries. • After each system call, increment PC registers so that ExceptionHandler() execution flow returns back to next instruction after user’s system call place. • counter = machine->ReadRegister(PCReg); • machine->WriteRegister(PrevPCReg,counter); • counter = counter + 4;machine->WriteRegister(PCReg,counter); • counter = counter + 4; machine->WriteRegister(NextPCReg,counter); • Arguments of a system call are in Registers 4, 5, 6 etc. • how to verify? You may review MPIS assembly code produced for a test C program using gcc -S. • If needed, return result is register 2 • machine->WriteRegister(2,result);
PCB (Process Control Block) • Write the PCB and a process manager. Create a PCB class that will store the necessary information about a process. Don't worry about putting everything in it right now, you can always add more as you go along. To start, it should have a PID, parent PID, and Thread*. • The process manager- it can have getPID and clearPID methods, which return an unused process id and clear a process id respectively.
Memory Manager • Write a Memory Manager that will be used to facilitate memory allocation: • Track memory page usage. • Allocate a page • Free a page • Modify AddrSpace:AddrSpace (addrspace.cc) to use the memory manager. • Modify the page table constructors to use pages allocated by your memory manager • Create a PCB (process control block) also for each process to include key control information.
AddSpace.cc • Write a function (e.g. AddrSpace::Translate), which converts a virtual address to a physical address. It does so by breaking the virtual address into a page table index and an offset. • Write a function( e.g. AddrSpace::ReadFile), which loads the code and data segments into the translated memory, instead of at position 0. • Read data into a system buffer (diskBuffer). • Copy buffered data into proper memory locations (e.g. at machine->mainMemory[physAddr].)
Implement Fork() in ExceptionHandler() • FuncEntry = Value of register 4 ( sys call argu) • Target function to be executed in the new space. • Create a new kernel thread. • Create a new AddrSpace to be a duplicate of the CurrentThread's space and get a new PCB. • The current thread calls Yield() so the new thread can run. • The new thread runs a dummy function that creates a bridge for execution of the user function). • Call NewThread->Fork(ForkBridge, FuncEntry) • Why? It needs to set program counter properly in a new space.
ForkBridge() : Key parts • Set counter = FuncEntry • Initialize and restore the registers. For example, • currentThread->RestoreUserState(); • currentThread->space->RestoreState(); • machine->WriteRegister(PCReg, counter); • machine->WriteRegister(PrevPCReg,counter-4); • machine->WriteRegister(NextPCReg,counter+4); • Call machine->Run() which executes the forked user process in the desired FuncEntry address.
Implement Exec() • Exec is creating a new process with new code and data segments from a file. • Allocate a new address space which fits this file. • Load data/code from an OpenFile object constructed from the filename passed in by the user. • In order to get that file name you will have to write a function that copies over the string from user space. • Allocate a new kernel thread and a PCB to execute with the above space. • Fork the new thread to run a dummy bridge function that sets the machine registers straight and runs the code • Call NewThread->Fork(ExecBridge ,NULL); • The calling thread should yield to give control to the newly spawned thread. • Return the process ID that executes this binary.
ExecBridge(): Key parts • Initialize registers/restore state. • (currentThread->space)->InitRegisters(); • (currentThread->space)->RestoreState(); • Machine->run();
System Calls for File System • For the entire system, maintain a set of objects (SysOpenFile class) representing system-wide opened files. • Each file may be opened by many user processes. • Call it fileOpenTable[]; • For each process, maintain a set of objects in PCB (UserOpenFile class) representing files opened by this process. • Each file has filename, index to the system-wide open table entry, and offset for the current read/write pointer
Implement create() • Nachos already has a simple file system implemented • filesys.cc and openfile.cc under filesys directory. • Use Nachos fileSystem to add a new file to its directory.
Implement open() • Use fileSystem->Open() (nachos’open) to locate the file object. • Add the opened file object to the system-wide fileOpenTable. • If it is already opened by some other user process, just share the entry. • Else insert to the local file open table in PCB.