Last modified: Mon Feb 26 11:42:17 CST 2001
In a typical machine with address translation hardware, programs executing in user mode get all of their addresses translated, while programs executing in privileged kernel mode may choose to use translated addresses or absolute physical addresses. Only one user address space is known to the CPU at any given time (the one represented in the Translation Lookaside Buffer, or the one whose tables are pointed to by a reserved translation register), and even privileged kernel code can only get hardware translation for the currently active address space. In principle, the kernel can work out address translation for any address space in software, but this is usually much slower than taking advantage of the translation hardware, and it provides one more possibility for a programming error. Local memory for the OS is referenced by absolute physical address.
In Nachos, the combination of native execution with MIPS simulation makes the system address space a bit confusing. In effect, the OS has access to two private address spaces: the native C++/UNIX address space in which Nachos runs, and the physical address space of the simulated MIPS machine, accessed through the array mainMemory in machine.h. The native C++ address space is much more convenient to use, since all of the allocation and type-interpretation features of C++ apply to it, while to Nachos code, mainMemory is just an array of bytes. So far, I have not seen anything in the projects that requires the use of physical addresses to mainMemory.
To access the currently active user address space in Nachos kernel code, with virtual addresses that must be translated, use the functions ReadMem and WriteMem in machine.h. These are the functions used internally by the MIPS simulation to emulate the hardware address translation, so they correspond exactly to the effect of executing MIPS machine code with virtual addresses. The internal documentation in machine.h lists ReadMem and WriteMem among "routines internal to the machine simulation" that should not be called from Nachos kernel code, but that appears to be an error. The other functions in that category---OneInstruction, DelayedLoad, Translate, RaiseException---are truly internal to the simulation, and calls to them from the kernel are highly unrealistic and therefore prohibited. Keep in mind that ReadMem and WriteMem go through exactly the actions of the hardware, including raising any exceptions that occur. Exceptions occur even when interrupts are disabled, so calls to ReadMem and WriteMem run the risk of breaking atomicity.
All the systems that I know of have at most one user address space active for hardware address translation at any time. This makes operations involving more than one user address space, such as message passing relatively expensive. The normal approach to such operations is to let each user address space in turn get control of the CPU, and pass its relevant data to the kernel, which must save data in the private kernel address space, and perform the appropriate operation when all the data have been copied. Then, each user address space expecting a return of data must get the CPU again to transfer results out of the kernel address space. In principle, the kernel could perform software address translation on its own to manipulate several user address spaces at once, but this is hard to program efficiently and reliably, especially when virtual memory is moving user pages around all the time.
Nachos provides a choice between two forms of address translation: linear page tables, or a Translation Lookaside Buffer. Project #3 (#2 in 1995) on user address spaces uses only the page table translation, but Project #4 (#3 in 1995) on virtual memory will use the TLB, so we might as well understand both of them now. Nachos does not use segmentation.
To understand address translation, start with the definition of the class TranslationEntry in translate.h. This is really just a record/structure type. TLBs and page tables are both implemented as arrays of TranslationEntrys. In page tables, the virtualPage field is always the same as the array index, so it is redundant, but saving one field is not worth the additional code complexity of two slightly different entry types. In TLBs, entries may occur in any order, so the virtualPage field is essential. Although real page tables are stored in the MIPS memory (simulated by the array mainMemory), and real TLBs are part of the CPU register set, Nachos keeps them both as independent data structures in its C++ address space. Since page tables are not in any user address space, and the registers in the TLB are not accessible to user programs, this way of representing them does not distort the structure of the OS code.
The crucial fields in a TranslationEntry are the virtualPage (in TLBs) and physicalPage (in both TLBs and page tables) entries, which define the mapping from virtual pages to physical page frames. The valid flag indicates an uninitialized TLB entry. I can find no place in the page table code where it is set FALSE, so I'm pretty sure that it is not used for page table translation. The valid flag is important for TLB operation, since it allows the whole table to be set empty very quickly during a context switch. readOnly prevents user programs from writing the page. It is never set TRUE in the initial Nachos code, and it is not required for our projects. A better OS would use it, e.g., to prevent accidental overwriting of executable code, or to provide a safe kind of broadcast communication where one thread writes a shared page and a number of others can only read it. Finally, the use flag is intended to be set when a page is referenced, and the dirty bit when a page is written. These are not needed for Project #3 (#2 in 1995), but are crucial for virtual memory.
Machine::Translate in translate.cc performs address translation. This code is part of the MIPS simulation, so it doesn't represent actual OS code. But, it provides an excellent reference for the precise behavior of address translation. Also, when you start using the TLB in Project #4 (#3 in 1995), you will need to do something very similar to the page table translation in your OS kernel code, and you can probably steal a lot of the code from the Translate function, even though you will not be allowed to call Translate.
The essence of the page table translation is in the assignments
which are scattered through the function body. The first two assignments take the leftmost bits of virtAddr as the virtual page number (vpn) and the remaining bits as the offset. The third and fourth are a slightly ugly C++ way of setting pageFrame to be the physicalPage address given for vpn in the pageTable. The last assignment is the standard formula reconstructing the physical address (physAddr) as the concatenation of pageFrame with offset.vpn = (unsigned) virtAddr / PageSize; offset = (unsigned) virtAddr % PageSize; entry = &pageTable[vpn]; pageFrame = entry->physicalPage; *physAddr = pageFrame * PageSize + offset;
The TLB translation is the same, except that pageFrame is found by a search for vpn in tlb, rather than by direct indexing into the table. In a real hardware TLB this search is done in parallel by the CPU circuitry, but for the simulation linear search is satisfactory.
The rest of the code in Translate deals with errors and other exceptions. Translate returns an ExceptionType, and ReadMem or WriteMem calls machine->RaiseException. Study Translate to understand precisely what conditions lead to the different sorts of exceptions. Notice in particular that PageFaultException is used in the TLB scheme to indicate a TLB cache miss, which is not really a page fault. With TLB translation true page faults are detected by the kernel software when it discovers that a page frame is on disk instead of in memory. That kernel code only runs when the TLB has already raised its version of PageFaultException, so it will be crucial to invalidate TLB entries for pages that are swapped out. Notice also that the page table code raises PageFaultException when it finds valid set FALSE. So, it appears that the valid bit under page-table translation is intended to indicate whether the page is in memory.
With address translation, every context switch requires a new translation table to be activated. Scheduler::Run in scheduler.cc actually calls five different functions to perform different parts of a context switch:
SWITCH preforms the context switch on the actual CPU in which the native C++ code for the Nachos kernel is running. Thread::SaveUserState and Thread::RestoreUserState in thread.cc perform the context switch on the simulated MIPS user-accessible registers. AddrSpace->SaveState and AddrSpace->RestoreState in addrspace.cc are responsible for switching the address translation tables that define the user address space.currentThread->SaveUserState; currentThread->space->SaveState; SWITCH; currentThread->RestoreUserState; currentThread->space->RestoreState;
In the initial Nachos code, SaveState does nothing, because the page table for a thread is stored permanently as thread->space->pageTable. RestoreState merely executes
to make the simulated MIPS CPU use the pageTable associated with the newly scheduled thread (the pageTable and numPages on the right-hand sides of the assignments are members of the AddrSpace class, so they are correctly taken from the particular thread->space). In a real OS on a real machine, a special register, not accessible to user programs, contains a pointer to the currently active page table, and RestoreState would set this register. In a real system SaveState could get by with doing nothing as long as the location of the page table cannot change while a thread is executing. To be safe, a real system might choose to save the actual register contents, since there are conceivable reasons for moving a page table, and since it might be simpler to save and restore all registers uncritically, instead of choosing those that need to be saved.machine->pageTable = pageTable; machine->pageTableSize = numPages;
I think that the initial versions of SaveState and RestoreState will work through Project #3 (#2 in 1995). They will definitely need to be changed when you start using the TLB in Project #4 (#3 in 1995). In particular, RestoreState is probably the appropriate place to invalidate the entries in the TLB. Be alert for any other changes to Nachos code that might create some address-space context that requires switching. In particular, any changes to the data structures in the AddrSpace class are likely to require corresponding changes to SaveState and/or RestoreState.