Introduction
该 lab 主要需要编写操作系统的内存管理部分。内存管理分为两个部分:
内核的物理内存分配器 (physical memory allocator)
使得内核可以分配、释放内存。该分配器以页为单位,JOS 中一页是 4kB。本次 lab 的任务是维护一个数据结构,该数据结构记录了物理内存分配与释放,以及多少个进程正在共享各个已分配的页。
虚拟内存 (virtual memory)
将内核和用户程序使用的虚拟地址映射到物理内存的地址中。x86 的内存管理单元 (MMU) 会在指令用到内存时完成这个映射,查询一系列页表。
在 lab2 中,新加入了几个源文件:
inc/memlayout.h // 描述虚拟地址空间的布局
kern/pmap.c // 读取物理内存大小,对虚拟地址空间进行布局
kern/pmap.h
kern/kclock.h // 操纵 PC 的时钟以及 CMOS RAM 等设备
kern/kclock.c // 这些设备中记录了物理内存大小
重点需要阅读 memlayout.h 以及 pmap.h,还需参考 inc/mmu.h。
处理冲突
在git merge lab1时,几乎必然出现冲突,以 conf/lab.mk 为例:
~/OS/lab$ more conf/lab.mk
<<<<<<< HEAD
LAB=2
PACKAGEDATE=Wed Sep 21 11:13:24 EDT 2016
=======
LAB=1
PACKAGEDATE=Wed Sep 14 12:18:32 EDT 2016
>>>>>>> lab1
其中,=======是分隔符,容易看出之上属于现在的 HEAD 即 lab2 的内容,之下属于 lab1 的内容,我们显然选择 lab2 的内容。
即手动修改 conf/lab.mk,仅保留:
LAB=2
PACKAGEDATE=Wed Sep 21 11:13:24 EDT 2016
其他冲突文件也如此处理后,直接 git add .,git commit -a提交。
Exercise 1
In the file kern/pmap.c, you must implement code for the following functions (probably in the order given).
boot_alloc()
mem_init() (only up to the call to check_page_free_list(1))
page_init()
page_alloc()
page_free()
**check_page_free_list() and check_page_alloc() test your physical page allocator. **
操作系统必需跟踪哪些物理 RAM 是空闲的,哪些正在使用。这个 exercise 主要编写物理页面分配器。它利用一个 PageInfo 结构体组成的链表记录哪些页面空闲,每个结构体对应一个物理页。因为页表的实现需要分配物理内存来存储页表,在虚拟内存的实现之前,我们需要先编写物理页面分配器。
boot_alloc 函数
static void *
boot_alloc(uint32_t n)
{
static char *nextfree; // virtual address of next byte of free memory
char *result;
// Initialize nextfree if this is the first time.
// 'end' is a magic symbol automatically generated by the linker,
// which points to the end of the kernel's bss segment:
// the first virtual address that the linker did *not* assign
// to any kernel code or global variables.
if (!nextfree) {
extern char end[];
nextfree = ROUNDUP((char *) end, PGSIZE);
}
// Allocate a chunk large enough to hold 'n' bytes, then update
// nextfree. Make sure nextfree is kept aligned
// to a multiple of PGSIZE.
//
// LAB 2: Your code here.
if (n == 0) {
return nextfree;
}
result = nextfree;
nextfree += ROUNDUP(n, PGSIZE);
return result;
}
其中,需要注意的一个是 end 到底是什么,另一个是 ROUNDUP 这个宏。其中,end 指向内核的 bss 段的末尾。利用 objdump -h kernel可以看出,bss 段已经是内核的最后一段。因此,end 指向的是第一个未使用的虚拟内存地址。而 ROUNDUP 定义在 inc/types.h 中。
~/OS/lab/obj/kern$ objdump -h kernel
kernel: file format elf32-i386
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 000019f1 f0100000 00100000 00001000 2**4
CONTENTS, ALLOC, LOAD, READONLY, CODE
1 .rodata 000007f0 f0101a00 00101a00 00002a00 2**5
CONTENTS, ALLOC, LOAD, READONLY, DATA
2 .stab 00004105 f01021f0 001021f0 000031f0 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
3 .stabstr 00001be6 f01062f5 001062f5 000072f5 2**0
CONTENTS, ALLOC, LOAD, READONLY, DATA
4 .data 0000a300 f0108000 00108000 00009000 2**12
CONTENTS, ALLOC, LOAD, DATA
5 .bss 00000650 f0112300 00112300 00013300 2**5
ALLOC
6 .comment 00000034 00000000 00000000 00013300 2**0
CONTENTS, READONLY
mem_init 函数
这里需要用到 PageInfo 这个结构体了,首先在 inc/memlayout.h 中找到其定义:
struct PageInfo {
// Next page on the free list.
struct PageInfo *pp_link;
// pp_ref is the count of pointers (usually in page table entries)
// to this page, for pages allocated using page_alloc.
// Pages allocated at boot time using pmap.c's
// boot_alloc do not have valid reference count fields.
uint16_t pp_ref;
};
这是一个非常典型的链表。其中,pp_ref 表示有多少个指针指向该页,pp_link 表示空闲内存列表中的下一页。注意,非空闲页的 pp_link 总是为 NULL。
mem_init 函数中需要添加以下两行:
//
// Allocate an array of npages 'struct PageInfo's and store it in 'pages'.
// The kernel uses this array to keep track of physical pages: for
// each physical page, there is a corresponding struct PageInfo in this
// array. 'npages' is the number of physical pages in memory. Use memset
// to initialize all fields of each struct PageInfo to 0.
// Your code goes here:
pages = (struct PageInfo *) boot_alloc(npages * sizeof(struct PageInfo));
memset(pages, 0, npages * sizeof(struct PageInfo));
需要注意的是分配内存用的是 boot_alloc。这是一个仅用于 JOS 设置自身虚拟内存系统时使用的物理内存分配器,仅用于 mem_init 函数。当初始化页面以及空闲内存列表后,不再使用 boot_alloc,而使用 page_alloc。
page_init 函数
void
page_init(void)
{
// NB: DO NOT actually touch the physical memory corresponding to
// free pages!
size_t i;
// 1) Mark physical page 0 as in use.
// This way we preserve the real-mode IDT and BIOS structures
// in case we ever need them. (Currently we don't, but...)
pages[0].pp_ref = 1;
// 2) The rest of base memory, [PGSIZE, npages_basemem * PGSIZE)
// is free.
for (i = 1; i < npages_basemem; i++) {
pages[i].pp_ref = 0;
pages[i].pp_link = page_free_list;
page_free_list = &pages[i];
}
// 3) Then comes the IO hole [IOPHYSMEM, EXTPHYSMEM), which must
// never be allocated.
for (i = IOPHYSMEM/PGSIZE; i < EXTPHYSMEM/PGSIZE; i++) {
pages[i].pp_ref = 1;
}
// 4) Then extended memory [EXTPHYSMEM, ...).
// Some of it is in use, some is free. Where is the kernel
// in physical memory? Which pages are already in use for
// page tables and other data structures?
size_t first_free_address = PADDR(boot_alloc(0));
for (i = EXT