MIT 6.828 JOS Lab 4 Report

MIT 6.828: JOS Lab

Lab 4: Preemptive Multitasking

Part A: Multiprocessor Support and Cooperative Multitasking


Answer the following questions:

  1. Compare kern/mpentry.S side by side with boot/boot.S. Bearing in mind that kern/mpentry.S is compiled and linked to run above KERNBASE just like everything else in the kernel, what is the purpose of macro MPBOOTPHYS? Why is it necessary in kern/mpentry.S but not in boot/boot.S? In other words, what could go wrong if it were omitted in kern/mpentry.S?
    Hint: recall the differences between the link address and the load address that we have discussed in Lab 1.
  1. The MPBOOTPHYS macro is needed because mpentry.S is linked at high addresses but gets loaded by boot_aps() at the low address MPENTRY_ADDR. The bootloader doesn't need a macro like this because it is linked and loaded at the same low address (0x00007c00).


  1. It seems that using the big kernel lock guarantees that only one CPU can run the kernel code at a time. Why do we still need separate kernel stacks for each CPU? Describe a scenario in which using a shared kernel stack will go wrong, even with the protection of the big kernel lock.
  1. During a trap/interrupt, the trapframe is pushed onto the stack without holding the kernel lock. Say CPU 1 enters the kernel on a system call and while it is in the kernel, CPU 2 attempts to enter the kernel on a timer interrupt. CPU 2 can't enter the kernel, it will be spinning at the lock in trap(). However, it will have pushed its trap frame on top of the trap frame already pushed by CPU 1. This of course means that when CPU 1 returns to user mode, it will pop off CPU 2's frame and return in that environment instead of its own.


  1. In your implementation of env_run() you should have called lcr3(). Before and after the call to lcr3(), your code makes references (at least it should) to the variable e, the argument to env_run. Upon loading the %cr3 register, the addressing context used by the MMU is instantly changed. But a virtual address (namely e) has meaning relative to a given address context--the address context specifies the physical address to which the virtual address maps. Why can the pointer e be dereferenced both before and after the addressing switch?
  2. Whenever the kernel switches from one environment to another, it must ensure the old environment's registers are saved so they can be restored properly later. Why? Where does this happen?
  1. Because all environment page directories share certain mappings. The envs array is allocated and mapped to UENVS in mem_init() and those mappings are then copied to all new page directories in env_setup_vm().
  2. It's a like the caller/callee save calling convention except here it is for interrupting the whole process. Processes keep temporary data and variables in registers and the process assumes that when it returns, all those values will still be there. The registers are saved on the user environment's stack as part of the trapframe constructed by the int instruction and the code in alltraps. To restore the state of a new process, JOS uses the env_pop_tf() function, which switches first to the new process' stack and the pops all the registers in place.