6.097: OPERATING SYSTEM ENGINEERING
Fall 2002
Lab 3 Solutions

These solutions were derived in part from work by Jason Gift.

Exercise 1

  1. start: (locore.S)Sets up the initial GDT and CS and ESP so that C code can run.
    i386_init()Calls all initialization routines and creates an environment (or two)
    cninit()Intializes the console.
    i386_detect_memory()Discovers how much physical RAM the system has
    i386_vm_init()Sets up the initial page directory and page tables for the kernel and allocates space for __envs[] and ppages[]
    ppage_init()Sets up ppages[] to reflect what has actually been used so far, and puts free pages on free list.
    idt_init()Sets up the interrupt descriptor table (tells the processor where it is and adds entries to it for the defined handlers)
    pic_init()Initializes the programmable interrupt controller and masks all interrupts
    clock_init()Sets up the clock to generate 100 interrupts a second and unmasks the clock interrupt in the PIC
    env_init()Marks all envs are free and adds them to the free env list
    env_create()Creates an environment for a binary (e.g. spin), and loads the binary
    yield()Searches for the next runnable env and runs it (does it in a round-robin fashion so that no env gets precedence over another)
    env_run()Saves register of old env, and switches to the new env, calls env_pop_tf() to start it running
    env_pop_tf()Restores the registers in the trapframe into the CPU's register.

Exercise 2

  1. There are 9999892 user instructions executed between interrupts.
  2. The kernel interrupt handler executes 108 instructions.

Exercise 3

  1. If the test case is passed, it should generate a trap, and env_destroy should clean up after it, causing the assert in yield() to fire. The exact trap generated depends on which test case is being used.
  2. The break point exception will be generated if the DPL field of the setgate was 3. This value allows the user process, running at CPL = 3, to invoke the break point with "int $0x3". Otherwise (CPL != 3), the user does not have permission, so GPF is generated.

    Note: setgate() should not set up any handlers as trap gates. In a trap gate, the CPU will not clear the FL_IF of the %eflags register when the exception occurs, leaving interrupts enabled. We do not want interrupts to be enabled while we are executing in the kernel. This would seriously complicate kernel programming. Thus, all gates should be set as interrupt gates.

  3. The point of this mechanism is to make sure the user can not generate arbitrary interrupts, causing all sorts of problems (i.e. an "int 0x20" would appear as clock interrupt to the kernel, and so the user could influence scheduling).
  4. Individual interrupt/exception handlers can push unique trap numbers. Otherwise, the kernel couldn't distinguish one interrupt/exception from another.

Exercise 4

  1. "alice" and "bob" load most of their registers with set values, and continually check to see if those values are what they expect. This way, if the values are erroneously changed by a kernel context switch, they can cause a divide by zero exception. As long as "alice" and "bob" do not cause this trap, one can be reasonably sure that the restoring/saving code is correct.
  2. The registers from one "alice" process could be restored into the other "alice" process without generating an error.

    As some people pointed out, if env_run() never saves registers, the original register set will be continually restored. The process will be started over at the initially point in the code; never making progress, and never dividing by zero. However, this error exists even if there is just one process.

  3. The virtual to physical translation for every env in the kernel part of the address space (address above ULIM) is identical. And since we are executing in the kernel the VA address of 'e' is above ULIM. Thus, after the page directory is switched, 'e' still (and all of the kernel VA space) translates to the same physical address as before the switch.