JOS Lab2 Memory Management Part 3 & challenge

本文详细介绍了JOS操作系统如何划分和管理内核地址空间,包括用户环境与内核空间的隔离、权限位的使用,以及如何初始化内核地址空间。文章通过一系列函数解析了内存映射的过程,如建立页表项、映射物理地址等,并给出了相关问题的答案。
摘要由CSDN通过智能技术生成

Part 3: Kernel Address Space

JOS 把处理器的32位地址空间划分为两部分。User Environment管理低地址的部分,Kernel则对高地址的部分保持控制。这种划分比较的任意,大致是以inc/memlayout,h中的ULIM为划分。
因为kernel和user memory在彼此的地址空间中存在,我们需要使用权限位来让用户代码只能访问用户的地址空间。不然可能会导致写入了内核代码而产生问题。
用户态应当没有权限来访问在ULIM上的内存,kernel则应当拥有读写权限。用户态代码和内核都对[UTOP, ULIM)的内存只拥有读权限,因为这部分是为了向用户态暴露一部分内核只读数据结构。UTOP下面的地址空间则是留给用户态使用的。

初始化内核地址空间

Exercise 5要求设置UTOP以上的地址空间,补充完成mem_init()中剩下内容。

	//
	// Map 'pages' read-only by the user at linear address UPAGES
	// Permissions:
	//    - the new image at UPAGES -- kernel R, user R
	//      (ie. perm = PTE_U | PTE_P)
	//    - pages itself -- kernel RW, user NONE
	// Your code goes here:
	boot_map_region(kern_pgdir, UPAGES, PTSIZE, PADDR(pages), PTE_U);

	//
	// Use the physical memory that 'bootstack' refers to as the kernel
	// stack.  The kernel stack grows down from virtual address KSTACKTOP.
	// We consider the entire range from [KSTACKTOP-PTSIZE, KSTACKTOP)
	// to be the kernel stack, but break this into two pieces:
	//     * [KSTACKTOP-KSTKSIZE, KSTACKTOP) -- backed by physical memory
	//     * [KSTACKTOP-PTSIZE, KSTACKTOP-KSTKSIZE) -- not backed; so if
	//       the kernel overflows its stack, it will fault rather than
	//       overwrite memory.  Known as a "guard page".
	//     Permissions: kernel RW, user NONE
	// Your code goes here:
	boot_map_region(kern_pgdir, KSTACKTOP - KSTKSIZE, KSTKSIZE, PADDR(bootstack), PTE_W);

	//
	// Map all of physical memory at KERNBASE.
	// Ie.  the VA range [KERNBASE, 2^32) should map to
	//      the PA range [0, 2^32 - KERNBASE)
	// We might not have 2^32 - KERNBASE bytes of physical memory, but
	// we just set up the mapping anyway.
	// Permissions: kernel RW, user NONE
	// Your code goes here:
	boot_map_region(kern_pgdir, KERNBASE, 0x10000000, 0, PTE_W);

然后make qemu看看结果:

Physical memory: 131072K available, base = 640K, extended = 130432K
check_page_free_list() succeeded!
Fail to allocate a new page! Out of Free Physical Memory!
Fail to allocate a new page! Out of Free Physical Memory!
check_page_alloc() succeeded!
Fail to allocate a new page! Out of Free Physical Memory!
Fail to allocate a new page! Out of Free Physical Memory!
Fail to allocate a new page! Out of Free Physical Memory!
Fail to allocate a new page! Out of Free Physical Memory!
Fail to allocate a new page! Out of Free Physical Memory!
Fail to allocate a new page! Out of Free Physical Memory!
check_page() succeeded!
check_kern_pgdir() succeeded!
check_page_free_list() succeeded!
check_page_installed_pgdir() succeeded!

通过了所有测试。
在回答后面的问题之前,我们重新回过头梳理一下这个lab究竟做了什么事情,完成了怎样的映射。

Review:

先来回顾一下各个函数究竟在干什么,特别关注操作对象及其地址情况。
boot_alloc:
给定n,在.bss end (0xf011A0000)之上划分一块n大小,按页对齐的虚拟地址空间。
page_init:
对虚拟地址[0xf011B000, 0xf015B000)pages数组根据物理内存的使用情况进行初始化,产生一个PageInfo的空闲物理内存页的链表。
page_alloc:
给定alloc_flags(决定分配的页是否要清零),从PageInfo的空闲物理内存页的链表中找到代表空闲物理页的PageInfo结构体,并将这个结构体对应的虚拟内存页根据alloc_flag清零或不清零,返回指向这个结构体的指针。
page_free:
将给定的PageInfo结构体加入空闲链表中。这里即不对物理内存做操作,也不对虚拟内存做操作,也不调整页表项。
pgdir_walk:
给定指向页目录的指针和虚拟地址,返回对应虚拟地址的页表项的指针。这里特别需要注意虚拟地址和物理地址的区分。拿到的页目录的指针是虚拟地址,得到的页目录项中包括了页表的物理地址。需要先转换成页表的虚拟地址,然后访问拿到对应的页表项,再取出页表项的虚拟地址,返回。
page_lookup:
给定指向页目录的指针,一个虚拟地址,和一个指向页表项指针的指针。首先查找该虚拟地址对应的页表项,如果不存在指向该页表项的指针,或者该页表项不是Present返回NULL,否则传入的双重指针指向该页表项的指针,返回指向对应该页表项对应物理地址的PageInfo结构体的指针,并且使虚拟地址对应的TLB无效。
page_remove:
给定指向页表项的指针,一个虚拟地址。先查找并拿到虚拟地址对应的PageInfo结构体和指向该虚拟地址对应页表项的指针。减少对该物理页的引用并清空该页表项,相当于取消虚拟地址和其对应物理地址的映射。
page_insert:
给定指向页目录的指针,一个代表物理页的指向PageInfo结构体的指针,和一个虚拟地址。先找到该虚拟地址对应的页表项的指针,如果该指针存在且页表项Present,那么我们先增加对给定物理页的引用,使用page_remove移除原有的映射(顺序是针对corner case的考虑),拿到给定结构体代表的物理页的物理地址,写到给定虚拟地址对应的页表项里,并使该虚拟地址对应的TLB无效。
boot_map_region:
给定指向页目录的指针,一个起始虚拟地址,一个起始物理地址,和地址空间大小以及权限。取出虚拟地址对应的页表项,把对应的物理地址和权限写进去。注意在注释中说不需要增加相应物理页的引用。(感谢GZZ同学解惑,我之前这里还不是特别清楚为什么不用引用计数增加,因为这里并不是真正地分配了page,而仅仅是通过写页表完成了映射)
这里看的时候还需要特别注意,KADDRpage2kva的区别,以及PADDRpage2pa的区别。前者是直接返回某个虚拟地址的物理地址或者是某个物理地址的虚拟地址,即加减KERNBASE,而后者则是返回PageInfo结构体对应的物理地址和虚拟地址。
还需要注意的一点是,分配空闲页是分配空闲页,映射虚拟地址和物理地址只是映射,二者函数功能分离。
我们再回去看mem_init(),映射完成之后就是这么个情况:
在这里插入图片描述然后我们来回答一下剩下的问题:
2. 要看页目录的填充情况,只需要看mem_init函数中的映射行为就可以了。
每个entry对应的基虚拟地址也就是entry乘上0x400000, 因此就不重复说了。

kern_pgdir[PDX(UVPT)] = PADDR(kern_pgdir) | PTE_U | PTE_P;

手动把UVPT对应的页目录项映射到页目录对应的物理地址,UVPT对应entry 957,内容是页目录的物理地址。

boot_map_region(kern_pgdir, UPAGES, PTSIZE, PADDR(pages), PTE_U);

UPAGESpages数组占据的物理地址空间建立映射,UPAGES对应entry 956,指向UPAGES对应的页表,页表指向pages数组的物理地址空间。

boot_map_region(kern_pgdir, KSTACKTOP - KSTKSIZE, KSTKSIZE, PADDR(bootstack), PTE_W);

KSTACKTOP-KSTSIZE(即cpu0的kernelstack)和bootstack占据的物理地址空间建立映射,对应entry 959,指向包含多个kernstack到对应bootstack映射的页表。注意在这里我们只映射了cpu0的kernelstackbootstack,而由于剩下的kernelstack页目录项是一样的,所以页目录项非空,且相应的页表非空,但是页表中除了cpu0 kernelstack之外其它的页表项是空的。

boot_map_region(kern_pgdir, KERNBASE, 0x10000000, 0, PTE_W);

kernel映射到物理地址0开始的256MB物理地址空间,kernel地址从0xf00000000xffffffff, 因此对应的页目录项的enrty从960到1023,指向负责映射kernel的页表,页表指向从0开始的256MB物理地址空间。
3. 因为使用了权限位,因为内核代码的权限位标志为supervisor level且Read-only access
4. 最多支持2G,因为RO PAGES 4096*1024 Byte 一个PageInfo结构为8 Byte,一个物理页4096 Byte,因此最多访问4096 * 1024 / 8 * 4096 = 2GB 这个地方之前没有注意到KADDR的映射是加上kernbase,我以为是映射到全部的4G虚拟地址空间,因此为了不溢出,只能支持256MB。
5. 最多可以放4MB的PageInfo数组,加上4KB的页目录,再加上4MB的页表,一共8MB+4KB 如果只能支持256MB的大小,那么PageInfo数组最多512KB,加上4KB的页目录,但是可以把所有的虚拟地址映射到256MB的物理内存上去,因此页表最多使用4MB大小,这样总共是4612KB.
6. 在jmp *%eax之后。 因为我们之前在entrypgdir.c里面把[0x00000000, 0x00400000)的虚拟地址映射到了[0x00000000, 0x00400000)的物理地址。因为后面kern_pgdir会被加载进来,低虚拟地址就不再使用了。

Challenge

(补坑)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值