Merge pull request #16 from leefige/master
Visual improvement by specifying language for code blocks; typo fix for lab3
This commit is contained in:
commit
23ef7645d6
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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); //初始化内存管理器
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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在实现上述流程中的位置:
|
||||
|
||||

|
||||
|
||||
|
@ -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看到的内核虚拟地址空间如下图所示:
|
||||
|
|
|
@ -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`。
|
||||
|
|
|
@ -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
|
||||
```
|
||||
|
||||
|
|
|
@ -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中断执行成功,则不置位,否则置位;
|
||||
|
||||
eax:534D4150h ('SMAP') ;
|
||||
|
||||
|
@ -42,7 +42,7 @@ ah:失败时保存出错代码
|
|||
```
|
||||
这样,我们通过调用INT 15h
|
||||
BIOS中断,递增di的值(20的倍数),让BIOS帮我们查找出一个一个的内存布局entry,并放入到一个保存地址范围描述符结构的缓冲区中,供后续的ucore进一步进行物理内存管理。这个缓冲区结构定义在memlayout.h中:
|
||||
```
|
||||
```c
|
||||
struct e820map {
|
||||
int nr_map;
|
||||
struct {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -118,7 +118,7 @@ segment):指用来存放程序执行代码的一块内存区域。这部分
|
|||
某些架构也允许代码段为可写,即允许修改程序。在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等。
|
||||
|
||||
在lab2/kern/init/init.c的kern\_init函数中,声明了外部全局变量:
|
||||
```
|
||||
```c
|
||||
extern char edata[], end[];
|
||||
```
|
||||
但搜寻所有源码文件\*.[ch],没有发现有这两个变量的定义。那这两个变量从哪里来的呢?其实在lab2/tools/kernel.ld中,可以看到如下内容:
|
||||
|
|
|
@ -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函数完成了对本次实验中的练习2:FIFO页替换算法基本正确性的检查,可了解,便于知道为何产生错误。
|
||||
* 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函数。
|
||||
|
||||
|
|
|
@ -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\_manager,swap\_manager是完成页面替换过程的主要功能模块,其中包含了页面置换算法的实现(具体内容可参考5小节)。然后会进一步调用执行check\_swap函数在内核中分配一些页,模拟对这些页的访问,这会产生页访问异常。如果我们正确实现了练习2,就可通过do\_pgfault来调用swap\_map\_swappable函数来查询这些页的访问情况并间接调用实现页面置换算法的相关函数,把“不常用”的页换出到磁盘上。
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -24,6 +24,6 @@
|
|||
|
||||

|
||||
|
||||
产生页访问异常后,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范围内,则认为是一次非法访问。
|
||||
|
|
|
@ -27,7 +27,7 @@ swap_entry_t
|
|||
-------------------------
|
||||
| offset | reserved | 0 |
|
||||
-------------------------
|
||||
24 bits 7 bits 1 bit
|
||||
24 bits 7 bits 1 bit
|
||||
```
|
||||
|
||||
考虑到硬盘的最小访问单位是一个扇区,而一个扇区的大小为512(2\^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;
|
||||
|
|
Loading…
Reference in New Issue