MIT 6.828 操作系统工程 lab4A:多处理器支持和协作多任务
这篇是我自己探索实现 MIT 6.828 lab 的笔记记录,会包含一部分代码注释和要求的翻译记录,以及踩过的坑/个人的解决方案
这里是我实现的完整代码仓库,也包含其他笔记等等:https://github.com/yunwei37/6.828-2018-labs
实验 4 包含许多新的源文件:
kern/cpu.h
多处理器支持的内核私有定义kern/mpconfig.c
读取多处理器配置的代码kern/lapic.c
驱动每个处理器中的本地 APIC 单元的内核代码kern/mpentry.S
非引导 CPU 的汇编语言入口代码kern/spinlock.h
自旋锁的内核私有定义,包括大内核锁kern/spinlock.c
实现自旋锁的内核代码kern/sched.c
您将要实现的调度程序的代码框架
多处理器支持
我们将让 JOS 支持“对称多处理”(SMP),这是一种多处理器模型,在该模型中,所有 CPU 都具有对系统资源(例如内存和 I/O 总线)的同等访问权限。尽管 SMP 中所有 CPU 的功能都相同,但在引导过程中它们可以分为两种类型:引导处理器 (BSP) 负责初始化系统和引导操作系统;只有在操作系统启动并运行后,BSP 才会激活应用处理器(AP)。哪个处理器是 BSP 是由硬件和 BIOS 决定的。到目前为止,您现有的所有 JOS 代码都已在 BSP 上运行。
在 SMP 系统中,每个 CPU 都有一个伴随的本地 APIC (LAPIC) 单元。LAPIC 单元负责在整个系统中传送中断。LAPIC 还为其连接的 CPU 提供唯一标识符。在本实验中,我们使用 LAPIC 单元(在kern/lapic.c 中)的以下基本功能:
- 读取 LAPIC 标识符 (APIC ID) 以判断我们的代码当前正在哪个 CPU 上运行
- 发送STARTUP从BSP到AP间中断(IPI)去启动其他CPU
- 在 C 部分,我们对 LAPIC 的内置计时器进行编程以触发时钟中断以支持抢占式多任务处理
处理器使用内存映射 I/O (MMIO) 访问其 LAPIC。
练习1:mmio_map_region
void *
mmio_map_region(physaddr_t pa, size_t size)
{
// Where to start the next region. Initially, this is the
// beginning of the MMIO region. Because this is static, its
// value will be preserved between calls to mmio_map_region
// (just like nextfree in boot_alloc).
static uintptr_t base = MMIOBASE;
// Reserve size bytes of virtual memory starting at base and
// map physical pages [pa,pa+size) to virtual addresses
// [base,base+size). Since this is device memory and not
// regular DRAM, you'll have to tell the CPU that it isn't
// safe to cache access to this memory. Luckily, the page
// tables provide bits for this purpose; simply create the
// mapping with PTE_PCD|PTE_PWT (cache-disable and
// write-through) in addition to PTE_W. (If you're interested
// in more details on this, see section 10.5 of IA32 volume
// 3A.)
//
// Be sure to round size up to a multiple of PGSIZE and to
// handle if this reservation would overflow MMIOLIM (it's
// okay to simply panic if this happens).
//
// Hint: The staff solution uses boot_map_region.
//
// Your code here:
uintptr_t start = base;
base += ROUNDUP(size, PGSIZE);
boot_map_region(kern_pgdir, start, ROUNDUP(size, PGSIZE), pa, PTE_PCD|PTE_PWT|PTE_W);
if (base > MMIOLIM) {
panic("mmio_map_region overflows MMIOLIM");
}
return (void*)start;
}
应用处理器引导程序
在启动 AP 之前,BSP 应首先收集有关多处理器系统的信息,例如 CPU 总数、它们的 APIC ID 和 LAPIC 单元的 MMIO 地址。
练习2:page_init
...
cprintf("Init pages alloc...\n");
size_t i;
for (i = 1; i < npages_basemem; i++) {
if (i * PGSIZE != MPENTRY_PADDR){
pages[i].pp_ref = 0;
pages[i].pp_link = page_free_list;
page_free_list = &pages[i];
}
}
....
每个 CPU 的状态和初始化
在编写多处理器操作系统时,区分每个处理器私有的每个 CPU 状态和整个系统共享的全局状态很重要。
以下是您应该注意的每个 CPU 状态:
- 每 CPU 内核堆栈
- 每 CPU TSS 和 TSS 描述符
- 每个 CPU 当前环境指针
- 每个 CPU 系统寄存器
练习3:mem_init_mp
static void
mem_init_mp(void)
{
// Map per-CPU stacks starting at KSTACKTOP, for up to 'NCPU' CPUs.
//
// For CPU i, use the physical memory that 'percpu_kstacks[i]' refers