Merge pull request #16 from leefige/master

Visual improvement by specifying language for code blocks; typo fix for lab3
This commit is contained in:
Object 2018-04-15 23:47:32 -07:00 committed by GitHub
commit 23ef7645d6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 50 additions and 50 deletions

View File

@ -116,7 +116,7 @@
* [Page Fault异常处理](lab3/lab3_4_page_fault_handler.md)
* [页面置换机制的实现](lab3/lab3_5_swapping.md)
* [页替换算法](lab3/lab3_5_1_page_swapping.md)
* [页面置换机制实验报告要求](lab3/lab3_5_2_page_swapping_principles.md)
* [页面置换机制](lab3/lab3_5_2_page_swapping_principles.md)
* [实验报告要求](lab3/lab3_6_labs_requirement.md)
## Lab 4

View File

@ -71,7 +71,7 @@ void main()
得到的主要汇编代码为:
```asm
```x86asm
movl count,%ecx
movl value,%eax
movl buf,%edi
@ -106,7 +106,7 @@ asm("leal (%1,%1,4),%0"
这段代码到的主要汇编代码为:
```asm
```x86asm
movl x,%eax
#APP
leal (%eax,%eax,4),%eax

View File

@ -4,7 +4,7 @@
Page数据结构来表示。由于一个物理页需要占用一个Page结构的空间Page结构在设计时须尽可能小以减少对内存的占用。Page的定义在kern/mm/memlayout.h中。以页为单位的物理内存分配管理的实现在kern/default\_pmm.[ch]。
为了与以后的分页机制配合我们首先需要建立对整个计算机的每一个物理页的属性用结构Page来表示它包含了映射此物理页的虚拟页个数描述物理页属性的flags和双向链接各个Page结构的page\_link双向链表。
```
```c
struct Page {
int ref; // page frame's reference counter
uint32_t flags; // array of flags that describe the status of the page frame
@ -12,8 +12,8 @@ struct Page {
list_entry_t page_link;// free list link
};
```
这里看看Page数据结构的各个成员变量有何具体含义。ref表示这页被页表的引用记数在“实现分页机制”一节会讲到。如果这个页被页表引用了即在某页表中有一个页表项设置了一个虚拟页到这个Page管理的物理页的映射关系就会把Page的ref加一反之若页表项取消即映射关系解除就会把Page的ref减一。flags表示此物理页的状态标记进一步查看kern/mm/memlayout.h中的定义可以看到
```
这里看看Page数据结构的各个成员变量有何具体含义。ref表示这页被页表的引用记数在“实现分页机制”一节会讲到。如果这个页被页表引用了即在某页表中有一个页表项设置了一个虚拟页到这个Page管理的物理页的映射关系就会把Page的ref加一反之若页表项取消即映射关系解除就会把Page的ref减一。flags表示此物理页的状态标记进一步查看kern/mm/memlayout.h中的定义可以看到
```c
/* Flags describing the status of a page frame */
#define PG_reserved 0 // the page descriptor is reserved for kernel or unusable
#define PG_property 1 // the member 'property' is valid
@ -34,7 +34,7 @@ Head
Page。连续内存空闲块利用这个页的成员变量page\_link来链接比它地址小和大的其他连续内存空闲块。
在初始情况下也许这个物理内存的空闲物理页都是连续的这样就形成了一个大的连续内存空闲块。但随着物理页的分配与释放这个大的连续内存空闲块会分裂为一系列地址不连续的多个小连续内存空闲块且每个连续内存空闲块内部的物理页是连续的。那么为了有效地管理这些小连续内存空闲块。所有的连续内存空闲块可用一个双向链表管理起来便于分配和释放为此定义了一个free\_area\_t数据结构包含了一个list\_entry结构的双向链表指针和记录当前空闲页的个数的无符号整型变量nr\_free。其中的链表指针指向了空闲的物理页。
```
```c
/* free_area_t - maintains a doubly linked list to record free (unused) pages */
typedef struct {
list_entry_t free_list; // the list header
@ -43,38 +43,38 @@ typedef struct {
```
有了这两个数据结构ucore就可以管理起来整个以页为单位的物理内存空间。接下来需要解决两个问题
管理页级物理内存空间所需的Page结构的内存空间从哪里开始占多大空间
空闲内存空间的起始地址在哪里?
- 管理页级物理内存空间所需的Page结构的内存空间从哪里开始占多大空间
- 空闲内存空间的起始地址在哪里?
对于这两个问题我们首先根据bootloader给出的内存布局信息找出最大的物理内存地址maxpa定义在page\_init函数中的局部变量由于x86的起始物理内存地址为0所以可以得知需要管理的物理页个数为
```
```c
npage = maxpa / PGSIZE
```
这样我们就可以预估出管理页级物理内存空间所需的Page结构的内存空间所需的内存大小为
```
```c
sizeof(struct Page) * npage
```
由于bootloader加载ucore的结束地址用全局指针变量end记录以上的空间没有被使用所以我们可以把end按页大小为边界整后作为管理页级物理内存空间所需的Page结构的内存空间记为
```
由于bootloader加载ucore的结束地址用全局指针变量end记录以上的空间没有被使用所以我们可以把end按页大小为边界整后作为管理页级物理内存空间所需的Page结构的内存空间记为
```c
pages = (struct Page *)ROUNDUP((void *)end, PGSIZE);
```
为了简化起见从地址0到地址pages+ sizeof(struct Page) \*
npage)结束的物理内存空间设定为已占用物理内存空间起始0\~640KB的空间是空闲的地址pages+
sizeof(struct Page) \*
npage)以上的空间为空闲物理内存空间,这时的空闲空间起始地址为
```
```c
uintptr_t freemem = PADDR((uintptr_t)pages + sizeof(struct Page) * npage);
```
为此我们需要把这两部分空间给标识出来。首先,对于所有物理空间,通过如下语句即可实现占用标记:
```
```c
for (i = 0; i < npage; i ++) {
SetPageReserved(pages + i);
}
````
然后,根据探测到的空闲物理空间,通过如下语句即可实现空闲标记:
```
然后,根据探测到的空闲物理空间,通过如下语句即可实现空闲标记:
```c
//获得空闲空间的起始地址begin和结束地址end
……
...
init_memmap(pa2page(begin), (end - begin) / PGSIZE);
```
其实SetPageReserved只需把物理地址对应的Page结构中的flags标志设置为PG\_reserved
@ -83,7 +83,7 @@ init_memmap(pa2page(begin), (end - begin) / PGSIZE);
关于内存分配的操作系统原理方面的知识有很多但在本实验中只实现了最简单的内存页分配算法。相应的实现在default\_pmm.c中的default\_alloc\_pages函数和default\_free\_pages函数相关实现很简单这里就不具体分析了直接看源码应该很好理解。
其实实验二在内存分配和释放方面最主要的作用是建立了一个物理内存页管理器框架,这实际上是一个函数指针列表,定义如下:
```
```c
struct pmm_manager {
const char *name; //物理内存页管理器的名字
void (*init)(void); //初始化内存管理器

View File

@ -11,7 +11,7 @@ first\_fit分配算法需要维护一个查找有序地址按从小到大排
libs/list.h定义了可挂接任意元素的通用双向链表结构和对应的操作所以需要了解如何使用这个文件提供的各种函数从而可以完成对双向链表的初始化/插入/删除等。
kern/mm/memlayout.h中定义了一个 free\_area\_t 数据结构,包含成员结构
```
```c
list_entry_t free_list; // the list header 空闲块双向链表的头
unsigned int nr_free; // # of free pages in this free list 空闲块的总数(以页为单位)
```
@ -29,8 +29,8 @@ kern_init --> pmm_init-->page_init-->init_memmap--> pmm_manager->init_memmap
**设计实现**
default\_init\_memmap函数根据每个物理页帧的情况来建立空闲页链表且空闲页块应该是根据地址高低形成一个有序链表。根据上述变量的定义default\_init\_memmap可大致实现如下
```
default\_init\_memmap函数根据每个物理页帧的情况来建立空闲页链表且空闲页块应该是根据地址高低形成一个有序链表。根据上述变量的定义default\_init\_memmap可大致实现如下
```c
default_init_memmap(struct Page *base, size_t n) {
struct Page *p = base;
for (; p != base + n; p ++) {
@ -44,19 +44,19 @@ default_init_memmap(struct Page *base, size_t n) {
}
```
如果要分配一个页那要考虑哪些呢这里就需要考虑实现default\_alloc\_pages函数注意参数n表示要分配n个页。另外需要注意实现时尽量多考虑一些边界情况这样确保软件的鲁棒性。比如
```
```c
if (n > nr_free) {
return NULL;
}
```
这样可以确保分配不会超出范围。也可加一些
assert函数在有错误出现时能够迅速发现。比如 n应该大于0我们就可以加上
```
```c
assert(n \> 0);
```
这样在n<=0的情况下ucore会迅速报错。firstfit需要从空闲链表头开始查找最小的地址通过list\_next找到下一个空闲块元素通过le2page宏可以更加链表元素获得对应的Page指针p。通过p-\>property可以了解此空闲块的大小。如果\>=n这就找到了如果<n则list\_next继续查找直到list\_next==
这样在n<=0的情况下ucore会迅速报错。firstfit需要从空闲链表头开始查找最小的地址通过list\_next找到下一个空闲块元素通过le2page宏可以链表元素获得对应的Page指针p。通过p-\>property可以了解此空闲块的大小。如果\>=n这就找到了如果<n则list\_next继续查找直到list\_next==
&free\_list这表示找完了一遍了。找到后就要从新组织空闲块然后把找到的page返回。所以default\_alloc\_pages可大致实现如下
```
```c
static struct Page *
default_alloc_pages(size_t n) {
if (n > nr_free) {

View File

@ -29,10 +29,10 @@ virt addr = phy addr + 0xC0000000
ucore
的内存管理经常需要查找页表给定一个虚拟地址找出这个虚拟地址在二级页表中对应的项。通过更改此项的值可以方便地将虚拟地址映射到另外的页上。可完成此功能的这个函数是get\_pte函数。它的原型为
```
```c
pte_t *get_pte (pde_t *pgdir, uintptr_t la, bool create)
```
下面的调用关系图可以比较好地看出get\_pte在实现上流程中的位置:
下面的调用关系图可以比较好地看出get\_pte在实现上流程中的位置:
![](../lab2_figs/image007.png)
@ -90,7 +90,7 @@ virt addr = linear addr + 0xC0000000 = phy addr + 2 * 0xC0000000
```
如何保证此时内核依然能够正常工作呢其实只需让index为0的页目录项的内容等于以索引值为(KERNBASE>>22)的目录表项的内容即可。目前内核大小不超过
4M 实际上是3M因为内核从 0x100000开始编址这样就只需要让页表在0\~4MB的线性地址与KERNBASE \~ KERNBASE+4MB的线性地址获得相同的映射即可都映射到 0\~4MB的物理地址空间具体实现在pmm.c中pmm\_init函数的语句
```
```c
boot_pgdir[0] = boot_pgdir[PDX(KERNBASE)];
```
实际上这种映射也限制了内核的大小。当内核大小超过预期的3MB
@ -98,7 +98,7 @@ boot_pgdir[0] = boot_pgdir[PDX(KERNBASE)];
当执行完毕gdt\_init函数后新的段页式映射已经建立好了上面的0\~4MB的线性地址与0\~4MB的物理地址一一映射关系已经没有用了。
所以可以通过如下语句解除这个老的映射关系。
```
```c
boot_pgdir[0] = 0;
```
在page\_init函数建立完实现物理内存一一映射和页目录表自映射的页目录表和页表后一旦使能分页机制则ucore看到的内核虚拟地址空间如下图所示

View File

@ -52,7 +52,7 @@ SECTIONS {
virt addr - 0xC0000000 = linear addr = phy addr # 线性地址在0~4MB之内的三者映射关系
```
请注意`pmm_init`函数中的一条语句:
```
```c
boot_pgdir[0] = boot_pgdir[PDX(KERNBASE)];
```
就是用来建立物理地址在0~4MB之内的三个地址间的临时映射关系`virt addr - 0xC0000000 = linear addr = phy addr`。

View File

@ -32,14 +32,14 @@ boot\_pgdir[PDX(VPT)] = PADDR(boot\_pgdir) | PTE\_P | PTE\_W;
这些变量和语句有何特殊含义呢其实vpd变量的值就是页目录表的起始虚地址0xFAFEB000且它的高10位和中10位是相等的都是10进制的1003。当执行了上述语句就确保了vpd变量的值就是页目录表的起始虚地址且vpt是页目录表中第一个目录表项指向的页表的起始虚地址。此时描述内核虚拟空间的页目录表的虚地址为0xFAFEB000大小为4KB。页表的理论连续虚拟地址空间0xFAC00000\~0xFB000000大小为4MB。因为这个连续地址空间的大小为4MB可有1M个PTE即可映射4GB的地址空间。
但ucore实际上不会用完这么多项在memlayout.h中定义了常量
```
```c
#define KERNBASE 0xC0000000
#define KMEMSIZE 0x38000000 // the maximum amount of physical memory
#define KERNTOP (KERNBASE + KMEMSIZE)
```
表示ucore只支持896MB的物理内存空间这个896MB只是一个设定可以根据情况改变。则最大的内核虚地址为常量
```
```c
#define KERNTOP (KERNBASE + KMEMSIZE)=0xF8000000
```

View File

@ -1,6 +1,6 @@
**探测物理内存分布和大小的方法**
操作系统需要知道了解整个计算机系统中的物理内存如何分布的,哪些可用哪些不可用。其基本方法是通过BIOS中断调用来帮助完成的。其中BIOS中断调用必须在实模式下进行所以在bootloader进入保护模式前完成这部分工作相对比较合适。这些部分由boot/bootasm.S中从probe\_memory处到finish\_probe处的代码部分完成完成。通过BIOS中断获取内存可调用参数为e820h的INT
操作系统需要知道了解整个计算机系统中的物理内存如何分布的哪些可用哪些不可用。其基本方法是通过BIOS中断调用来帮助完成的。其中BIOS中断调用必须在实模式下进行所以在bootloader进入保护模式前完成这部分工作相对比较合适。这些部分由boot/bootasm.S中从probe\_memory处到finish\_probe处的代码部分完成。通过BIOS中断获取内存可调用参数为e820h的INT
15h BIOS中断。BIOS通过系统内存映射地址描述符Address Range
Descriptor格式来表示系统物理内存布局其具体表示如下
```
@ -28,7 +28,7 @@ es:di指向保存地址范围描述符结构的缓冲区BIOS把信息写
```
此中断的返回值为:
```
cflags的CF位若INT 15中断执行成功则不置位否则置位
eflags的CF位若INT 15中断执行成功则不置位否则置位
eax534D4150h ('SMAP')
@ -42,7 +42,7 @@ ah失败时保存出错代码
```
这样我们通过调用INT 15h
BIOS中断递增di的值20的倍数让BIOS帮我们查找出一个一个的内存布局entry并放入到一个保存地址范围描述符结构的缓冲区中供后续的ucore进一步进行物理内存管理。这个缓冲区结构定义在memlayout.h中
```
```c
struct e820map {
int nr_map;
struct {

View File

@ -1,11 +1,11 @@
**实现物理内存探测**
物理内存探测是在bootasm.S中实现的相关代码很短如下所示
```
```x86asm
probe_memory:
//对0x8000处的32位单元清零,即给位于0x8000处的
//struct e820map的成员变量nr_map清零
movl $0, 0x8000
movl $0, 0x8000
xorl %ebx, %ebx
//表示设置调用INT 15h BIOS中断后BIOS返回的映射地址描述符的起始地址
movw $0x8004, %di

View File

@ -118,7 +118,7 @@ segment指用来存放程序执行代码的一块内存区域。这部分
某些架构也允许代码段为可写,即允许修改程序。在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等。
在lab2/kern/init/init.c的kern\_init函数中声明了外部全局变量
```
```c
extern char edata[], end[];
```
但搜寻所有源码文件\*.[ch]没有发现有这两个变量的定义。那这两个变量从哪里来的呢其实在lab2/tools/kernel.ld中可以看到如下内容

View File

@ -51,7 +51,7 @@
fault处理相关的do\_pgfault函数是本次实验需要涉及完成的。
* kern/mm/swap.[ch]定义了实现页替换算法类框架struct swap\_manager。swap.c包含了对此页替换算法类框架的初始化、页换入/换出等各种函数实现。重点是要理解何时调用swap\_out和swap\_in函数。和如何在此框架下连接具体的页替换算法实现。check\_swap函数以及被此函数调用的\_fifo\_check\_swap函数完成了对本次实验中的练习2FIFO页替换算法基本正确性的检查可了解便于知道为何产生错误。
* kern/mm/swap\_fifo.[ch]FIFO页替换算法的基于页替换算法类框架struct swap\_manager的简化实现主要被swap.c的相关函数调用。重点是\_fifo\_map\_swappable函数可用于建立页访问属性和关系比如访问时间的先后顺序和\_fifo\_swap\_out\_victim函数可用于实现挑选出要换出的页当然换出哪个页需要借助于fifo\_map\_swappable函数建立的某种属性关系已选出合适的页。
* kern/mm/mmu.h其中定义额也页表项的各种属性位比如PTE\_P\\PET\_D\\PET\_A等对于实现扩展实验的clock算法会有帮助。
* kern/mm/mmu.h其中定义页表项的各种属性位比如PTE\_P\\PET\_D\\PET\_A等对于实现扩展实验的clock算法会有帮助。
本次实验的主要练习集中在vmm.c中的do\_pgfault函数和swap\_fifo.c中的\_fifo\_map\_swappable函数、\_fifo\_swap\_out\_victim函数。

View File

@ -4,7 +4,7 @@
首先是初始化过程。参考ucore总控函数init的代码可以看到在调用完成虚拟内存初始化的vmm\_init函数之前需要首先调用pmm\_init函数完成物理内存的管理这也是我们lab2已经完成的内容。接着是执行中断和异常相关的初始化工作即调用pic\_init函数和idt\_init函数等这些工作与lab1的中断异常初始化工作的内容是相同的。
在调用完idt\_init函数之后将进一步调用三个lab3中才有的新函数vmm\_init、ide\_init和swap\_init。这三个函数设计了本次实验中的两个练习。第一个函数vmm\_init是检查我们的练习1是否正确实现了。为了表述不在物理内存中的“合法”虚拟页需要有数据结构来描述这样的页为此ucore建立了mm\_struct和vma\_struct数据结构接下来的小节中有进一步详细描述假定我们已经描述好了这样的“合法”虚拟页当ucore访问这些“合法”虚拟页时会由于没有虚实地址映射而产生页访问异常。如果我们正确实现了练习1则do\_pgfault函数会申请一个空闲物理页并建立好虚实映射关系从而使得这样的“合法”虚拟页有实际的物理页帧对应。这样练习1就算完成了。
在调用完idt\_init函数之后将进一步调用三个lab3中才有的新函数vmm\_init、ide\_init和swap\_init。这三个函数涉及了本次实验中的两个练习。第一个函数vmm\_init是检查我们的练习1是否正确实现了。为了表述不在物理内存中的“合法”虚拟页需要有数据结构来描述这样的页为此ucore建立了mm\_struct和vma\_struct数据结构接下来的小节中有进一步详细描述假定我们已经描述好了这样的“合法”虚拟页当ucore访问这些“合法”虚拟页时会由于没有虚实地址映射而产生页访问异常。如果我们正确实现了练习1则do\_pgfault函数会申请一个空闲物理页并建立好虚实映射关系从而使得这样的“合法”虚拟页有实际的物理页帧对应。这样练习1就算完成了。
ide\_init和swap\_init是为练习2准备的。由于页面置换算法的实现存在对硬盘数据块的读写所以ide\_init就是完成对用于页换入换出的硬盘简称swap硬盘的初始化工作。完成ide\_init函数后ucore就可以对这个swap硬盘进行读写操作了。swap\_init函数首先建立swap\_managerswap\_manager是完成页面替换过程的主要功能模块其中包含了页面置换算法的实现具体内容可参考5小节。然后会进一步调用执行check\_swap函数在内核中分配一些页模拟对这些页的访问这会产生页访问异常。如果我们正确实现了练习2就可通过do\_pgfault来调用swap\_map\_swappable函数来查询这些页的访问情况并间接调用实现页面置换算法的相关函数把“不常用”的页换出到磁盘上。

View File

@ -12,7 +12,7 @@ fault异常时可获得访问的内存的方式读或写以及具体的
在ucore中描述应用程序对虚拟内存“需求”的数据结构是vma\_struct定义在vmm.h中以及针对vma\_struct的函数操作。这里把一个vma\_struct结构的变量简称为vma变量。vma\_struct的定义如下
```
```c
struct vma_struct {
// the set of vma using the same PDT
struct mm_struct *vm_mm;
@ -26,7 +26,7 @@ struct vma_struct {
vm\_start和vm\_end描述了一个连续地址的虚拟内存空间的起始位置和结束位置这两个值都应该是PGSIZE 对齐的,而且描述的是一个合理的地址空间范围(即严格确保 vm\_start < vm\_end的关系list\_link是一个双向链表按照从小到大的顺序把一系列用vma\_struct表示的虚拟内存空间链接起来并且还要求这些链起来的vma\_struct应该是不相交的即vma之间的地址空间无交集vm\_flags表示了这个虚拟内存空间的属性目前的属性包括
```
```c
#define VM_READ 0x00000001 //只读
#define VM_WRITE 0x00000002 //可读写
#define VM_EXEC 0x00000004 //可执行
@ -34,7 +34,7 @@ vm\_start和vm\_end描述了一个连续地址的虚拟内存空间的起始位
vm\_mm是一个指针指向一个比vma\_struct更高的抽象层次的数据结构mm\_struct这里把一个mm\_struct结构的变量简称为mm变量。这个数据结构表示了包含所有虚拟内存空间的共同属性具体定义如下
```
``` c
struct mm_struct {
// linear list link which sorted by start addr of vma
list_entry_t mmap_list;

View File

@ -24,6 +24,6 @@
![image](../lab3_figs/image002.png)
产生页访问异常后CPU把引起页访问异常的线性地址装到寄存器CR2中并给出了出错码errorCode说明了页访问异常的类型。ucore OS会把这个值保存在struct trapframe 中tf\_err成员变量中。而中断服务例程会调用页访问异常处理函数do\_pgfault进行具体处理。这里的页访问异常处理是实现按需分页、页换入换出机制的关键之处。
产生页访问异常后CPU把引起页访问异常的线性地址装到寄存器CR2中并给出了出错码errorCode说明了页访问异常的类型。ucore OS会把这个值保存在struct trapframe 中tf\_err成员变量中。而中断服务例程会调用页访问异常处理函数do\_pgfault进行具体处理。这里的页访问异常处理是实现按需分页、页换入换出机制的关键之处。
ucore中do\_pgfault函数是完成页访问异常处理的主要函数它根据从CPU的控制寄存器CR2中获取的页访问异常的物理地址以及根据errorCode的错误类型来查找此地址是否在某个VMA的地址范围内以及是否满足正确的读写权限如果在此范围内并且权限也正确这认为这是一次合法访问但没有建立虚实对应关系。所以需要分配一个空闲的内存页并修改页表完成虚地址到物理地址的映射刷新TLB然后调用iret中断返回到产生页访问异常的指令处重新执行此指令。如果该虚地址不在某VMA范围内则认为是一次非法访问。

View File

@ -27,7 +27,7 @@ swap_entry_t
-------------------------
| offset | reserved | 0 |
-------------------------
24 bits 7 bits 1 bit
24 bits 7 bits 1 bit
```
考虑到硬盘的最小访问单位是一个扇区而一个扇区的大小为5122\^8字节所以需要8个连续扇区才能放置一个4KB的页。在ucore中用了第二个IDE硬盘来保存被换出的扇区根据实验三的输出信息
@ -53,11 +53,11 @@ ucore认为合法的所有虚拟内存空间集合而mm中的每个vma表示
到实验二为止我们知道目前表示内存中物理页使用情况的变量是基于数据结构Page的全局变量pages数组pages的每一项表示了计算机系统中一个物理页的使用情况。为了表示物理页可被换出或已被换出的情况可对Page数据结构进行扩展
```
```c
struct Page {
……
list\_entry\_t pra\_page\_link;
uintptr\_t pra\_vaddr;
list_entry_t pra_page_link;
uintptr_t pra_vaddr;
};
```
@ -67,7 +67,7 @@ pra\_page\_link可用来构造按页的第一次访问时间进行排序的一
为了实现各种页替换算法我们设计了一个页替换算法的类框架swap\_manager:
```
```c
struct swap_manager
{
const char *name;