Why disable interrupts? The following is mainly for your enrichment, although you will certainly understand the assignment better.
Confused yet? It gets worse. Because the user level program is being simulated, the interrupt mechanism we used for project 2 no longer applies to it. Instead, we use mt_os_tick in mt_os.c to simulate the clock interrupt to the user program when mipsi is simulating it.
Furthermore, mipsi was not designed to run with the interrupt mechanism we used in project 2. As a result, interrupts are disabled upon returning from any minithread call. You don't have to worry about this because remember, every call to the minithreads package must go through the system call mechanism. This is why when you look at mt_syscall (where you have to add your system calls), you will see that we have enabled interrupts just before any minithreads or VM routine is called, and disabled interrupts before returning to mipsi.
In summary, the net effect is that:
syscall(TRAPNUMBER, arg0, arg1, arg2, ...);
The simulator uses the TRAPNUMBER to translate the call into an appropriate UNIX syscall or to run a routine in mt_os.
Any application that calls routines in mt_os will go through the MT_ENTRY trap number. Calls to the mt_os look like:
syscall(MT_ENTRY, MT_OS_TRAPNUMBER, arg0, arg1, ...);Valid MT_OS_TRAPNUMBER's can be found in mt_os.h.
So how is this all done? mipsi basically simulates each instruction one at a time. Whenever a trap occurs, mipsi goes and translates the trap and forwards it to the host operating system (in our case this is Ultrix) or for traps into mt_os, mipsi simply runs the appropriate mt_os function. For example, when mipsi is simulating a program, the call to minithread_fork looks like:
syscall(MT_ENTRY, MT_MINITHREAD_FORK, proc, arg);mipsi recognizes the syscall and calls
mt_syscall(MT_MINITHREAD_FORK, proc, arg);mt_syscall recognizes that the syscall is to MT_MINITHREAD_FORK, so it packages up the arguments and calls mt_minithread_fork (see mt_os.c to see how this is implemented).
mt_minithread_fork(proc, arg);mt_minithread_fork then does the necessary magic to get things ready and calls minithread_fork, returning the thread id which is propagated up to the user level. Note that all this is happening inside mipsi. As far as the application is concerned, it just did a normal syscall.
syscall is not a very pleasant interface to program with, so a wrapper for using these is provided in USER/user_minithread.c. Here's an example of the use of the syscall interface through the minithread_fork call:
minithread_t minithread_fork(proc_t proc, arg_t arg) { minithread_t mt; mt = (minithread_t)syscall(MT_ENTRY, MT_MINITHREAD_FORK, proc, arg); return(mt); }This will no doubt confuse some people as there are now two files that implement minithread.h: user_minithread.c and minithread.c. Obviously, the former is only seen by user programs that are to be simulated, and the latter is what is actually used in the kernel to implement the system calls. To simulate a user program that uses the functionality provided by mt_os, compile it using the cross-compiler and link in user_minithread.o.
A convenient idiom for declaring handle types is to introduce a C
macro that conditionally compiles one way or another, depending on
whether the minithread and synchronization object references are
pointers (as in project 2) or handles (as in project 3 user programs).
The Makefiles that we've given you do this, by defining the symbol
KERN or USER appropriately. (Note we don't use the symbol KERNEL
because some of the Ultrix include files we use use this for building
the Ultrix kernel). For example, you can use this in your program by
saying:
Notice that we only have one "process" running, and therefore only
have one address space, even though we have multiple threads. This
means that you do not have to worry about having different page tables
for different threads, and that you should not flush the TLB.
C. Help with Handles
To return a handle means that the operating system has
explicit knowledge of what the user level process has access
to, e.g., semaphores, mutexes, threads, condition variables
etc. Handles are passed back and forth between user and kernel so the
kernel knows what resource the system call is dealing with, even
though it does not actually have a pointer to that resource. When a
resource is destroyed, the kernel should be able to recognize when the
user attempts to use this stale handle, and kill the user
program appropriately. For example, a user program should not be able
to P or V on a destroyed semaphore.
#ifdef KERN
typedef struct semaphore* semaphore_t;
#else
typedef os_handle_t semaphore_t;
#endif
where os_handle_t is a type for your exported handles. (A
good type here might be an integer).
D. Implementing Virtual Memory
Your user programs run in protected mode, which means that all of
their memory is virtual (at least in part 3 of the project). Memory
management is performed by your operating system code, which provides
an interface to memory management and implements paging and virtual
memory. Your virtual memory system uses faults to drive page
replacement. The TLB
mt_tlb_translate_fault() should actually return a little more
than just the physical page number for its return value. It should
return a PTE (page table entry) which is simply a data structure that
has the physical page number in the lower 20-bits and flags for this
page, e.g., the write protect flag, in the upper 12-bits.
The macro
TLB_MAKE_PTE(ppage,flags)
is provided in tlb.h to turn the physical page number and the
flags into the appropriate form.
The Disk
You will need to use a disk to page in and page out memory pages.
The disk is accessed through the following two calls:
ReadBlock(disk-block-number, physical address);
WriteBlock(disk-block-number, physical address);
These calls read and write to disk from (simulated) physical memory in
4K sized blocks (convenient, since this is the page size used in the
VM system). A 16 MB disk is provided for swap space. Unfortunately,
since this disk is implemented as an array in memory, your
core files will be rather large.