实验完善代码 LAB2-4下载链接 提取码:79t8
0、下载lab2 的源代码
[root@xxx lab]# git add .
[root@xxx lab]# git pull
Already up-to-date.
[root@xxx lab]# git checkout -b lab2 origin/lab2
Branch lab2 set up to track remote branch lab2 from origin.
Switched to a new branch 'lab2'
[root@xxx lab]# git merge lab1
Auto-merging kern/pmap.c
Merge made by recursive.
kern/kdebug.c | 11 +++++++++--
kern/monitor.c | 19 +++++++++++++++++++
lib/printfmt.c | 7 +++----
3 files changed, 31 insertions(+), 6 deletions(-)
1、背景知识
通过实验1,我们知道,JOS 的启动过程实际上是BIOS先把bootsector(boot loader,位于可引导磁盘的第一扇区) 的内容读取到0x7c00处,并将控制权交给boot loader,boot loader 的任务有两个:(1) 将模式从实模式转换为保护模式;(2)将内核可执行文件(ELF,位于第二个扇区开始的8个扇区)加载到内存,内核可执行程序被加载到内存中从0x100000开始的地方,然后设置好GDB,并将控制权交给操作系统。
目前内存的结构如图所示。
2、内存分页机制的实现
- 页面管理
本实验的页面大小固定为4KB,在页面管理上,系统必须管理所有的物理内存空间,同时,需要对不同的内存区域进行区别对待。对于已经存在在内核的一些实模式数据和系统区域(BIOS数据,显存区域等),系统不能将他们视为空闲内存进行分配; 同时还需要建立合适的映射关系,将逻辑地址转换为物理地址。内核程序的逻辑地址是从KERNBASE = 0XF0000000开始的。
(1)页面管理链表结构
页面的管理使用 链表结构 ,该结构在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;
};
空闲页面的头指针为:*page_free_list
当前可用空闲空间的开始地址:static char *nextfree,内核载入后,系统管理所需要的内容从nextfree开始分配。
系统页目录所在空间的开始地址:pde_t *kern_pgdir;
注意: 上面两个地址都是虚拟地址!上面的地址在pmap.c 文件中定义的。
- 页面管理链表在内存中的存储和放置
由于启动页式地址管理最开始需要理解的问题就是物理页面的管理问题。所以页面管理链表要在这个启动页式地址管理以前完成。源代码中,pmap.c文件中boot_alloc 函数为页目录分配4KB空间。
这个函数可以用来管理链表中的众多管理节点分配空间。
kern_pgdir = (pde_t *) boot_alloc(PGSIZE);
PGSIZE为一个物理页面的大小,4KB。
static void *boot_alloc(uint32_t n)
{
static char *nextfree; // virtual address of next byte of free memory
char *result;
//end 表示链接器生成的符号,指向内核的bss段的末尾(链接地址)
if (!nextfree) {
extern char end[];
nextfree = ROUNDUP((char *) end, PGSIZE);
}
result = nextfree;
nextfree = ROUNDUP( nextfree+n, PGSIZE);
if((uint32_t)nextfree - KERNBASE > (npages * PGSIZE))
panic("out of memory!");
return result;
}
boot_alloc() 仅仅在JOS 设置其虚拟内存系统的时候使用,end 开始分配页面;真正的物理内存分页用page_alloc()函数。
注意,从boot_alloc 得到的页面没有进行初始化,因此需要手动清空。这个函数只是暂时用来作为页分配器,真实使用的也分配器是page_alloc()。
修改之后的代码为:
- 首先是boot_alloc函数
static void *boot_alloc(uint32_t n)
{
static char *nextfree;
char *result;
if (!nextfree) {//如果是第一次分配页面,留了一页作为索引
extern char end[];
nextfree = ROUNDUP((char *) end, PGSIZE);
}
result = nextfree;
nextfree = ROUNDUP(nextfree+n, PGSIZE);
if((uint32_t)nextfree - KERNBASE > (npages * PGSIZE) )
panic("out of memory!");
return result;
}
- 接下来继续看mem_init()函数,为pages 结构分配内存
系统定义了一个全局变量,struct PageInfo *pages; boot_alloc()函数分配的页面的地址就赋值在这个变量里面。
这里分配一块内存,用来存放pages 结构的信息。
pages 指向的是 有很多Page 结构的连续内存区域,这些结构与实际的物理页面是一一对应的,所以可以用下标的方法,唯一地表示一个物理页面。
pages = (struct PageInfo*)boot_alloc(npages* sizeof(struct PageInfo));
memset(pages, 0, npages* sizeof(struct PageInfo));
- 页面管理操作
页面的操作主要由page_alloc() 以及释放页面的操作 page_free(),另外,页面系统初始化page_init()函数。page_init()函数函数会将所有管理的结点加入页面管理链表。页面管理链表会将所有的内存用一个链表来表示,但是由于部分物理内存存放了内核数据以及代码,需要从页面管理链表中剔除这部分页面。得到页面管理节点后,一定要将其所对应的物理页面(所对应的物理页面的首地址可以用page2kva() 宏得到)清零。 - 接下来完善page_init()函数。
void page_init(void)
{
size_t i;
page_free_list = NULL;
int num_alloc = ((uint32_t)boot_alloc(0) - KERNBASE) / PGSIZE;
int num_iohole = 96;
for (i = 0; i < npages; i++) {
if( i==0 )
pages[i].pp_ref = 1;
else if(i >= npages_basemem && i < npages_basemem + num_iohole + num_alloc)
pages[i].pp_ref = 1;
else {
pages[i].pp_ref = 0;
pages[i].pp_link = page_free_list;
page_free_list = &pages[i];
}
}
}
[0 , PGSIZE) : 存放中断向量表以及BIOS的相关载入程序;
[ PGSIZE , IOPHYSMEM) : 可用内存,可加入空闲链表
[IOPHYSMEM , EXTPHYSMEM) : 存放输入输出所需的空间,比如 VGA、外设、BIOS ROM等信息;
[PADDR(boot_pgdir) , PADDR(boot_pgdir) + PGSIZE) : 存放页目录;
[PADDR(pages) , PADDR(pages) + PGSIZE) : 存放pages 数组;
- page_alloc() 函数 分配一个页面
struct PageInfo *page_alloc(int alloc_flags)
{
struct PageInfo* result;
if(page_free_list == NULL)
return NULL;
result = page_free_list;
page_free_list = result->pp_link;
result->pp_link = NULL;
if(alloc_flags & ALLOC_ZERO)
memset(page2kva(result),0,PGSIZE);
return result;
}
- page_free() 函数 释放一个页面
void page_free(struct PageInfo *pp)
{
// Fill this function in
// Hint: You may want to panic if pp->pp_ref is nonzero or
// pp->pp_link is not NULL.
if(pp->pp_ref != 0 || pp->pp_link != NULL)
panic("page_free: the page is not free \n");
pp->pp_link = page_free_list;
page_free_list = pp;
}
至此,Exercise 1 的全部内容完成了,编译运行结果如图所示。
本文参考文章 http://grid.hust.edu.cn/zyshao/OSEngineering.htm
推荐这位博主系列的文章