The high order bits:
code/userprog/.You'll want to take a look at the Project 2 Description as well.
At the start of the quarter, we saw that operating systems rely on the processor to support two modes of operation: user and kernel (or privileged and unprivileged). In kernel mode, you can do just about whatever you want. Code running in user mode has restrictions placed on it so that it cannot directly access hardware, interfere with other running programs, and so on—assuming the operating system is implemented correctly. The hardware provides the basic mechanisms, and the operating system has to make it all work.
Nachos is no different. In Project 1, all the code you wrote ran in kernel mode. In Project 2, you'll finally start to run code in Nachos's "user mode", though most of the code you write will still run in "kernel mode". How do the two modes actually work in Nachos?
code/test/ subdirectory are meant to run in user
mode.test subdirectory run without emulation, and hence
can do anything Nachos will let you—thus, the equivalent of
kernel mode.To provide proper protection, there is quite a bit of separation between code running in the two modes:
machine/machine.h) and
mustn't ever try to directly dereference a user-supplied pointer.
The kernel and user address spaces are entirely different; to access
data in a user program, you must convert the user address to an
address in the kernel, and check that the pointer is actually
valid. The ReadMem and WriteMem methods
in Machine might be useful.ExceptionHandler function in
userprog/exception.cc. You'll need to extend this
function to check all the different causes for user code to switch
to kernel mode, and handle them all safely.A user program in Nachos performs a system call (such as
exec or read) by loading arguments into
registers and executing the system call instruction. This causes a trap
to ExceptionHandler in the kernel.
ExceptionHandler looks to see what happens, notices that
the cause is SyscallException, then examines the processor
registers to determine exactly which system call was requested.
Question: Why do we need to keep the userspace registers separate from the kernel registers here? Why can't we simply treat this like a function call into the kernel, with the usual rules that the called function may overwrite some of the registers?
In Nachos, all the "physical memory" in the system (the memory used
for userspace code) is stored in Machine::mainMemory. But
user code cannot acces this memory directly. Instead, Nachos uses page
tables that map virtual addresses (used by user code) into physical
addresses (addresses in mainMemory).
The AddrSpace class has a pageTable array
(and numPages variable giving its size). Element
i of this array gives the physical page (location in
mainMemory) corresponding to virtual page i.
By setting up the pageTable array properly, you can make a
user's pages actually be stored anywhere in memory you want, without the
user program knowing. You can even (and will, in Project 3) provide
virtual memory, reading in pages from disk when a process needs
them.
With support for user mode programs, each user process is associated with a kernel thread. So there's no need to change the way that scheduling is done—scheduling the kernel threads also schedules the user threads for us.
The Machine class can only store one pagetable and set
of user registers at a time. Since each user process should have its
own pagetables and registers, we store the registers and a pointer to
the pagetables in the Thread object. When we context
switch, both the user and kernel state are swapped. So, everything just
works.
(For details, look at Thread::RestoreUserState and
AddrSpace::RestoreState.)