CSE 451 - Fall 1997: Appendix to Project 3

CSE 451

Appendix to
Project 3: System Calls
and Virtual Memory

Fall, 1997


This is an appendix to the project 3 description. It contains a bunch of tidbits that you may find useful. Check the section web for more hints for this project.

A. General Information

If you would like to rebuild everything from scratch, invoke "make clean" from the top level (project3) directory and then invoke "make mipsi" or "make mipsi-orig. Expect warnings when compiling either version of mipsi -- I've never met a simulator that didn't produce compile time warnings. Browse through the Makefile in the project3 directory and try to figure out what happens when you do this.

Importing your minithread implementation to mipsi

We do not want interrupts to be enabled when mipsi is running. As a result, you must ensure than any newly forked thread runs initially with interrupts disabled. For some of you this may mean something as simple as removing the initial_proc from the stack initialization call.

Why disable interrupts? The following is mainly for your enrichment, although you will certainly understand the assignment better.

Your minithreads system now no longer deals with the user level program. The user level program is being simulated by mipsi. If you look carefully at mt_thread.c and examine the mt_minithread_fork system call, you will see that it does something strange with a run_struct. This is because instead of creating a thread that runs the user-specified procedure, we create a thread that runs a mipsi procedure that simulates the user-specified procedure.

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:

  • user level programs are preempted by mt_os_tick
  • mipsi is not preempted by anything (except the real OS)
  • minithreads runs preemptively because we enable interrupts before running any of its routines.

B. More on mipsi

Though mipsi is a fairly complicated system, the Makefiles provided will build it without much effort on your part. To get mipsi to work with the minithreads package we had to modify sys.s and add a file called switch.c to the minithreads package. These are pre-compiled and are in the lib directory.

System Calls

User programs can interact with the operating system by calling into the the simulator via traps which are called through the syscall interface.

	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.

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.

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:

	#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.

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.

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.