OS\_Lab2 内存
在 jos中 BIOS程序先写好了,qemu已启动直接跳到了 0x7c00处 开始执行程序,在设模式下内存布局如图
注意: 在 lab1中 作者已经帮我们手动映射了 4 MB的内存
所以0XF0100000开始的内存映射到了0x0100000的位置上,物理地址加上0XF0000000就是虚拟地址
物理地址直接通过MMU模块转换成了虚拟地址!
到目前为止,内核已经写进了了内存,
如图
将一个已经映射好的页表的地址放在指定的地方,开业分页模式后,cpu会自动查找到这个并且通过MMU模块转换层虚拟地址
开始 lab2 !
本章主要内容 是内存管理 虚拟内存 -\> 映射到物理内存
目前的内存布局如下
在写入完内核后,我们知道j接下来的任务建立页表, 我们要从哪里开始建立页表呢?很明显是在内核之后,而我们不知道内核的具体到小,而内核的大小是不固定的,在那个地址结束呢?在 kernel文件生成时,BSS段我们设置一个变量 end,作为内核结束的标志,所以我们可以在其他文件extend 引用这个变量,就可以知道 内核结束的位置在哪里!
Exercise 1. 完善函数 Exercise 1. 完善函数
In the file kern/pmap.c, you must implement code for the following functions (probably in the order given).
```
1
2
3
4
5
```
```hljs isbl
>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()andcheck_page_alloc()test your physical page allocator. You should boot JOS and see whethercheck_page_alloc()reports success. Fix your code so that it passes. You may find it helpful to add your ownassert()s to verify that your assumptions are correct.
第一个函数: boot\_alloc()
最基本的分配内存方式(目前没有malloc,所以我们自己实现一个):
boot_alloc(uint32_t n)
{
static char *nextfree; // virtual address of next byte of free memory
char *result;
if (!nextfree) {
extern char end[];
nextfree = ROUNDUP((char *)end, PGSIZE);
}
cprintf("boot_alloc memory at %x\n", nextfree);
cprintf("Next memory at %x\n", ROUNDUP((char *)(nextfree + n), PGSIZE));
if (n != 0) {
char *next = nextfree;
nextfree = ROUNDUP((char *)(nextfree + n), PGSIZE);
return next;
} else{
return nextfree;
}
}
有了基本的内存分配方式就可以随心分配内存了
先分配 页目录表:
kern_pgdir = (pde_t *)boot_alloc(PGSIZE);
memset(kern_pgdir, 0, PGSIZE);
随后我们继续分配 页表
每个页表项都是个结构体,这个结构体用途是管理内存,这里的每个结构体都管理这个一个4k大小的内存!!
pages = (struct PageInfo*)boot_alloc(sizeof (struct PageInfo)*npages); //调用boot_alloc()函数找到空闲空间起点,并且大小n= PageInfo结构大小*npages
memset(pages,0,sizeof (struct PageInfo)*npages);

第二个函数page\_init()
初始化页表:
pages并没有内容
按要求来
第一个物理页标记为被使用,有几个区间是不可以用的
void
page_init(void)
{
size_t i;
size_t io_hole_start_page = (size_t)IOPHYSMEM / PGSIZE;
size_t kernel_end_page = PADDR(boot_alloc(0)) / PGSIZE; //注意boot_alloc()返回虚拟地址
for (i = 0; i < npages; i++) {
if(i==0) //如上面要求1
{
pages[i].pp_ref = 1;
pages[i].pp_link = NULL;
}
else if(i >= io_hole_start_page && i <= kernel_end_page) //这区域内是已经被使用的了
{
pages[i].pp_ref = 1;
pages[i].pp_link = NULL;
}
else //而这段 才是真正 我么可以操控的内存
{
pages[i].pp_ref = 0;
pages[i].pp_link = page_free_list; // 头插法形成一个链表
page_free_list = &pages[i];
}
}
}
第三个函数 page\_alloc()
page\_init 生成了页表,但是页表项并没有管理空间, page\_alloc 函数可以申请一个空间,并用一个PageInfo来管理
struct PageInfo *
page_alloc(int alloc_flags)
{
// Fill this function in /// page_free_list是指向页表项的结构体指针 (链表)
if (page_free_list == NULL) {
cprintf("out of free memory");
return NULL;
}
struct PageInfo *addr = page_free_list;
page_free_list = page_free_list->pp_link; //需要使用一个页表项 ,防止丢失,指向下一个页表项
addr->pp_link = NULL;
if (alloc_flags & ALLOC_ZERO) { //看 传入的参数, 如果为 1 ,则申请一个 4k的内存
memset(page2kva(addr), 0, PGSIZE); //
}
return addr;
}
这里说下 page2kva(addr) 这个函数;
memset扩充内存,操作的虚拟是虚拟内存 (实际物理地址加 0xf00000)
现在 的addr 只是这个页表项的地址,如果直接按这个地址扩展的话肯定是会覆盖其他页表项的,所以呢,我们的根据这个页表项设计出在那分配内存
page2pa : pages是页表项的初始地址 pp-pages 可以得出 是第几个页表项 ; 而 左移则是 乘以4096 ,那么这个结果是 物理页的偏移量,我们可以算出在哪里开始分配内存
KADDR : 刚才得出的是物理内存,我们还得把这个转换成虚拟地址 ,这个宏就可以将物理地址转换成虚拟地址
Exercise 4 完善函数 Exercise 4 完善函数
Exercise 4. In the file
kern/pmap.c, you must implement code for the following functions.
```
1
2
3
4
5
```
```hljs c
pgdir_walk()
boot_map_region()
page_lookup()
page_remove()
page_insert()
```
check_page(), called frommem_init(), tests your page table management routines. You should make sure it reports success before proceeding.
pgdir\_walk(pde\_t \pgdir, const void \va, int create)
给一个虚拟地址,页目录表入口,返回该虚拟地址所在的页表项
大致看完 lab2 ,先回答几个问题
在 lab1 完成后,此时 OS的布局是怎么的?
解释下 虚拟内存 ,物理内存, 两者是如何映射的?
kernel 存放在哪个位值? (分派内存是以防覆盖) (提示 : bss)
内存权限问题
开启分页模式后,物理地址 默认都会转换为 虚拟地址( cr3 寄存器 可以设置 )
!img
!img