Lab4
Part A: 多处理器支持和协作式多任务
练习 1 :实现在 kern/pmap.c 中的 mmio_map_region 方法。你可以看看 kern/lapic.c 中的 lapic_init 开头部分,了解一下它是如何被调用的。你还需要完成接下来的练习,你的 mmio_map_region 才能够正常运行。
lapic_init()函数的一开始就调用了该函数,将从lapicaddr 开始的4kB物理地址映射到虚拟地址,并返回其起始地址。注意到,它是以页为单位对齐的,每次都映射一个页的大小。
练习 2:阅读 kern/init.c 中的 boot_aps() 和 mp_main() 方法,和 kern/mpentry.S 中的汇编代码。确保你已经明白了引导 AP 启动的控制流执行过程。接着,修改你在 kern/pmap.c 中实现过的 page_init() 以避免将 MPENTRY_PADDR 加入到 free list 中,以使得我们可以安全地将 AP 的引导代码拷贝于这个物理地址并运行。你的代码应当通过我们更新过的 check_page_free_list() 测试,不过可能仍会在我们更新过的 check_kern_pgdir() 测试中失败,我们接下来将解决这个问题。
修改 kern/pmap.c中的page_init()将MPENTRY_PADDR(0x7000)这一页不要加入到page_free_list。
问题 1:逐行比较 kern/mpentry.S 和 boot/boot.S。牢记 kern/mpentry.S 和其他内核代码一样也是被编译和链接在 KERNBASE 之上运行的。那么,MPBOOTPHYS 这个宏定义的目的是什么呢?为什么它在 kern/mpentry.S 中是必要的,但在 boot/boot.S 却不用?换句话说,如果我们忽略掉 kern/mpentry.S 哪里会出现问题呢?
提示:回忆一下我们在 Lab 1 讨论的链接地址和装载地址的不同之处。
1.kern/mpentry.S 与 boot/boot.S 有以下差别:
没有 Enable A20 的部分
GDT 相关的地址都用 MPBOOTPHYS 宏包装了一下
栈设置在了 mpentry_kstack
跳转到入口 mp_main
2.MPBOOTPHYS 宏的作用:
MPBOOTPHYS 的作用是将高地址变为地址。
因为 kern/mpentry.S 都链接到了高位的虚拟地址,但是实际上装载在低位的物理地址,所以 MPBOOTPHYS 要把这个高位的地址映射到低位的地址。boot/boot.S 装载在低位并且链接也在低位,所以就不需要这样的宏。
练习 3:修改位于 kern/pmap.c 中的 mem_init_mp(),将每个CPU堆栈映射在 KSTACKTOP 开始的区域,就像 inc/memlayout.h 中描述的那样。每个堆栈的大小都是 KSTKSIZE 字节,加上 KSTKGAP 字节没有被映射的 守护页 。现在,你的代码应当能够通过我们新的 check_kern_pgdir() 测试了。
对于 CPU i, 、物理地址为 'percpu_kstacks[i]',然后映射过去就行。
练习 4 :位于kern/trap.c 中的 trap_init_percpu() 为 BSP 初始化了 TSS 和 TSS描述符,它在 Lab 3 中可以工作,但是在其他 CPU 上运行时,它是不正确的。修改这段代码使得它能够在所有 CPU 上正确执行。(注意:你的代码不应该再使用全局变量 ts。)
每一个 cpu 的 task state segment (TSS)被用来指定每一个 CPU 的内核栈存在的地方,The TSS for CPU i 储存在 cpus[i].cpu_ts,相应的 TSS descriptor 定义在 gdt[(GD_TSS0 >> 3) + i]。先利用cpu_id建立TSS,初始化TSS descriptor,之后加载TSS selector,最后加载IDT(中断描述符表)。
加锁
你应当在以下 4 个位置使用全局内核锁:
- i386_init() 中,在 BSP 唤醒其他 CPU 之前获得内核锁
- mp_main() 中,在初始化完 AP 后获得内核锁,接着调用 sched_yield() 来开始在这个 AP 上运行进程。
- trap() 中,从用户模式陷入(trap into)内核模式之前获得锁。你可以通过检查 tf_cs 的低位判断这一 trap 发生在用户模式还是内核模式(译者注:Lab 3 中曾经使用过这一检查)
- env_run() 中,恰好在回到用户进程之前释放内核锁。不要太早或太晚做这件事,否则可能会出现竞争或死锁。
练习 5:在上述提到的位置使用内核锁,加锁时使用 lock_kernel(), 释放锁时使用 unlock_kernel()。
Lock_kernel()的函数定义如下:
Unlock_kernel()的函数定义如下:
在kern/spinlock.cpp中,
spin_lock()函数的定义如下:
其中,while循环体现了循环等待的思想,