一、Introduction


Part A 的任务是使 JOS 支持多处理器,并且添加 Round-Robin 调度。并实现几个系统调用,如创建进程和销毁进程,分配和映射内存等。

Part B 要求实现一个类 Unix 的 fork() 函数。

Part C 则要求在 JOS 中支持进程间通信(IPC)。

在远程主机上拉取 lab4 的内容,并合并。下面是本实验中一些重要的源文件:

 

二、Part A: Multiprocessor Support and Cooperative Multitasking


本部分的三个任务:

2.1、Multiprocessor Support

JOS 中支持的是 SMP(Symmetric Multiprocessing)。在 SMP 架构中,处理器分为两类:

BSP 是启动时使用的处理器,它主要负责初始化系统和操作系统,初始化完成后在激活其他的 APs。多处理器架构中,哪个处理器是 BSP,是由硬件和 BIOS 决定的。

在 SMP 系统中,每个 CPU 都有一个 Local APIC(LAPIC)。LAPIC 负责传递中断到处理器:

APIC

The LAPIC also provides its connected CPU with a unique identifier.

在本实验中,我们可以利用 lapic 来获取一些有用的信息:

处理器读写 lapic 使用的是 Memory-mapped I/O(MMIO)。简单来说 MMIO 就是将一段内存单元(4MB)硬连线到外设的寄存器上,这样就能使用 load/store 指令读写外设。

这段内存单元在物理内存上是固定的,我们将这段内存映射到 JOS 指定的虚拟地址空间上:

                                                 Virtual Memory
                                              +-------------------+
            Physical Memory                   |                   |
       4GB +----------------+                 |                   |
           |       .        |                 |                   |
           |       .        |                 |        .          |
           |       .        |                 |        .          |
           +----------------+                 |        .          |
           |      4MB       +-----+           |                   |
0xFE000000 +----------------+     |           |                   |
           |                |     |           |                   |
           |                |     |           |                   |
           |                |     |           |                   |
           |       .        |     |           |                   |
           |       .        |     |  MMIOLIM  +-------------------+ 0xefc00000
           |       .        |     +---------> | Memory|mapped I/O | RW/--  PTSIZE
           |                |        MMIOBASE +-------------------+ 0xef800000
           |                |                 |                   |
           |                |                 |        .          |
           |                |                 |        .          |
           |                |                 |        .          |
           |                |                 |                   |
           |                |                 |                   |
           +----------------+                 +-------------------+

2.1.1、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.

下面来完成这部分的映射。

void *
mmio_map_region(physaddr_t pa, size_t size)
{
	static uintptr_t base = MMIOBASE;
	if (base + size > MMIOLIM)
		panic("MMIO map overflow!");
	physaddr_t pa_begin = (physaddr_t)ROUNDDOWN(pa, PGSIZE);
	physaddr_t pa_end = (physaddr_t)ROUNDUP(pa+size, PGSIZE);
	boot_map_region(kern_pgdir, 
	                base, 
					pa_end - pa_begin, 
					pa_begin, 
					PTE_PCD | PTE_PWT | PTE_W);
	return (void *)base;
}
这段内存区域与其他区域不同,CPU 不能缓存这部分内容,所以我们在 PTE 标志位设为 **PTE_PCD() PTE_PWT PTE_W**:

2.1.2、Application Processors Bootstrap

在启动 APs 之前,系统应该先获取到它们的信息,例如:

首先看一下各种初始化函数在 i386_init() 中的调用顺序(只列出部分相关函数):

void
i386_init(void)
{
    ...
	mp_init();
	lapic_init();

	// Lab 4 multitasking initialization functions
	pic_init();

	// Acquire the big kernel lock before waking up APs
	// Your code here:

	// Starting non-boot CPUs
	boot_aps();
    ...
}

通过 mp_init() 函数读取 BIOS 中的 MP configuration table,获取到系统中所有 CPU 的信息。接着初始化 lapic 和 8259A 这两个中断控制器:

lapic_init();
pic_init();

最后进入 boot_aps() 关键函数,通过它去启动其它处理器:

static void
boot_aps(void)
{
	extern unsigned char mpentry_start[], mpentry_end[];
	void *code;
	struct CpuInfo *c;

	code = KADDR(MPENTRY_PADDR);
	memmove(code, mpentry_start, mpentry_end - mpentry_start);

	for (c = cpus; c < cpus + ncpu; c++) {
		if (c == cpus + cpunum())
			continue;

		mpentry_kstack = percpu_kstacks[c - cpus] + KSTKSIZE;
		lapic_startap(c->cpu_id, PADDR(code));
		while(c->cpu_status != CPU_STARTED)
			;
	}
}

这个函数一次启动一个 CPU。先使用 memmove() 将 mpentry.S 中的启动代码拷贝到指定位置(0x7000)处。然后将 CPU 的栈位置保存到 mpentry_kstack 中,以便 mpentry.S 中设置 CPU 的栈指针使用。再调用 lapic_startap() 去执行 mpentry.S

mpentry_start:
	cli            
    # 设置段寄存器,以 %ax 为媒介
	xorw    %ax, %ax
	movw    %ax, %ds
	movw    %ax, %es
	movw    %ax, %ss

	lgdt    MPBOOTPHYS(gdtdesc)
	movl    %cr0, %eax
	orl     $CR0_PE, %eax
	movl    %eax, %cr0

	ljmpl   $(PROT_MODE_CSEG), $(MPBOOTPHYS(start32))

.code32
start32:
	movw    $(PROT_MODE_DSEG), %ax
	movw    %ax, %ds
	movw    %ax, %es
	movw    %ax, %ss
	movw    $0, %ax
	movw    %ax, %fs
	movw    %ax, %gs

	# Set up initial page table. We cannot use kern_pgdir yet because
	# we are still running at a low EIP.
	movl    $(RELOC(entry_pgdir)), %eax
	movl    %eax, %cr3
	# Turn on paging.
	movl    %cr0, %eax
	orl     $(CR0_PE|CR0_PG|CR0_WP), %eax
	movl    %eax, %cr0

	# Switch to the per-cpu stack allocated in boot_aps()
    # 这里加载我们前面设置的栈指针值
	movl    mpentry_kstack, %esp
    # 设置 &ebp 为 0,剖析栈帧时作为结束标志
	movl    $0x0, %ebp       # nuke frame pointer

	# Call mp_main().  (Exercise for the reader: why the indirect call?)
	movl    $mp_main, %eax
	call    *%eax

这段汇编与 entry.S 的功能基本相同。最后进入 mp_main()

void
mp_main(void)
{
	// We are in high EIP now, safe to switch to kern_pgdir
    /* 加载新的内核页表 */
	lcr3(PADDR(kern_pgdir));
	cprintf("SMP: CPU %d starting\n", cpunum());
    
    /* 初始化内核空间 */
	lapic_init();
	env_init_percpu();
	trap_init_percpu();

    /* 让 boot_aps() 结束 while 循环,接着初始化下一个 CPU */
	xchg(&thiscpu->cpu_status, CPU_STARTED); // tell boot_aps() we're up

	// Now that we have finished some basic setup, call sched_yield()
	// to start running processes on this CPU.  But make sure that
	// only one CPU can enter the scheduler at a time!
	//
	// Your code here:

	// Remove this after you finish Exercise 6
	for (;;);
}

这个函数还不完整,暂时让这个 CPU 进入自旋。

2.1.3、Exercise 2

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.

前面实验中,我们在初始化管理物理内存的时候,还没有考虑到多处理器的情况。现在需要修改 page_init(),将 [mpentry_start, mpentry_end] 这段代码所在的页从 free page list 中移除。

for (i = 1; i < npages_basemem; i++)
{
	if( MPENTRY_PADDR / PGSIZE == i )
	{
		pages[i].pp_ref = 1;
		continue;
	}
	pages[i].pp_ref = 0;
	pages[i].pp_link = page_free_list;
	page_free_list = &pages[i];
}

2.2、Round-Robin Scheduling

2.3、System Calls for Environment Creation

 

三、Part B: Copy-on-Write Fork


 

四、Part C: Preemptive Multitasking and Inter-Process communication(IPC)