Exercise 1. Implement mmio_map_region
in kern/pmap.c. To see how this is used, look at the beginning of lapic_init
in kern/lapic.c. You'll have to do the next exercise, too, before the tests for mmio_map_region
will run.
613 static uintptr_t base = MMIOBASE;
633
634 pa = ROUNDDOWN(pa, PGSIZE);
635 size = ROUNDUP(pa + size, PGSIZE) - pa;
636 boot_map_region(kern_pgdir, base, size, pa, PTE_PCD|PTE_PWT|PTE_W);
637 base += size;
638 if(base >= MMIOLIM)
639 panic("mmio_map_region not implemented");
640 return (void *)(base-size);
Exercise 2. Read boot_aps()
and mp_main()
in kern/init.c, and the assembly code in kern/mpentry.S. Make sure you understand the control flow transfer during the bootstrap of APs. Then modify your implementation of page_init()
in kern/pmap.c to avoid adding the page at MPENTRY_PADDR
to the free list, so that we can safely copy and run AP bootstrap code at that physical address. Your code should pass the updated check_page_free_list()
test (but might fail the updated check_kern_pgdir()
test, which we will fix soon).
328 int i;
329 int lower_pgnum = PGNUM(IOPHYSMEM);
330 int upper_pgnum = PGNUM(ROUNDUP((int)boot_alloc(0) - KERNBASE,PGSIZE) );
331 int mpentry_pgnum = PGNUM(MPENTRY_PADDR);
332 page_free_list = NULL;
333 for (i = 0; i < npages; i++) {
334 if ((i == 0) || ((i >= lower_pgnum) && (i < upper_pgnum)) || (i == mpentry_pgnum)) {
335 pages[i].pp_ref = 1;
336 pages[i].pp_link = NULL;
337 continue;
338 }
339 pages[i].pp_ref = 0;
340 pages[i].pp_link = page_free_list;
341 page_free_list = &pages[i];
Question
- 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 macroMPBOOTPHYS
? 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.
Exercise 3. Modify mem_init_mp()
(in kern/pmap.c) to map per-CPU stacks starting at KSTACKTOP
, as shown in inc/memlayout.h. The size of each stack is KSTKSIZE
bytes plus KSTKGAP
bytes of unmapped guard pages. Your code should pass the new check in check_kern_pgdir()
.
302 int kstacktop_i;
303 int i;
304 for (i = 0; i < NCPU; i++) {
305 kstacktop_i = KSTACKTOP - i * (KSTKSIZE + KSTKGAP);
306 boot_map_region(kern_pgdir, (uintptr_t)(kstacktop_i - KSTKSIZE), KSTKSI ZE, PADDR(percpu_kstacks[i]), PTE_W|PTE_P);
307 }
Exercise 4. The code in trap_init_percpu()
(kern/trap.c) initializes the TSS and TSS descriptor for the BSP. It worked in Lab 3, but is incorrect when running on other CPUs. Change the code so that it can work on all CPUs. (Note: your new code should not use the global ts
variable any more.)
145 struct Taskstate ts;
146 // Setup a TSS so that we get the right stack
147 // when we trap to the kernel.
148 thiscpu->cpu_ts.ts_esp0 = KSTACKTOP - cpu_i * (KSTKSIZE + KSTKGAP);
149 thiscpu->cpu_ts.ts_ss0 = GD_KD;
150
151 // Initialize the TSS slot of the gdt.
152 gdt[(GD_TSS0 >> 3) + cpu_i] = SEG16(STS_T32A, (uint32_t) (&thiscpu->cpu_ts),
153 sizeof(struct Taskstate), 0);
154 gdt[(GD_TSS0 >> 3) + cpu_i].sd_s = 0;
155
156 // Load the TSS selector (like other segment selectors, the
157 // bottom three bits are special; we leave them 0)
158 ltr(GD_TSS0 + ((cpu_i << 3) & (~0x7)));
159
160 // Load the IDT
161 lidt(&idt_pd);
Exercise 5. Apply the big kernel lock as described above, by calling
lock_kernel()
and
unlock_kernel()
at the proper locations.
Too easy to put the code here, notice that these are the entrance and exit of kernel.
Question
- 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.