主要姓名:冯成 学号:19021221183 学院:电子工程学院
lab3 延续1和2。实现了配套的虚拟机制。主要是包括缺页异常处理。
相较于lab2已有代码的改变:
struct Page *
alloc_pages(size_t n) {
struct Page *page=NULL;
bool intr_flag;
while (1)
{
local_intr_save(intr_flag);
{
page = pmm_manager->alloc_pages(n);
}
local_intr_restore(intr_flag);
// 为什么N不能大于1
if (page != NULL || n > 1 || swap_init_ok == 0) break;
extern struct mm_struct *check_mm_struct;
//cprintf("page %x, call swap_out in alloc_pages %d\n",page, n);
// 在分配页时,如果没有可用的物理内存(在空闲链表上查找),就将一部分内存换出到磁盘。
swap_out(check_mm_struct, n, 0);
}
//cprintf("n %d,get page %x, No %d in alloc_pages\n",n,page,(page-pages));
return page;
}
struct Page {
int ref; // page frame's reference counter
uint32_t flags; // array of flags that describe the status of the page frame
unsigned int property; // the num of free block, used in first fit pm manager
list_entry_t page_link; // free list link
list_entry_t pra_page_link; // used for pra (page replace algorithm) 虚拟地址的链表,每次缺页时,会加载一个页,并将该页放到这个中。根据这个链表前后顺序可以达到需要换出的物理页面。
uintptr_t pra_vaddr; // used for pra (page replace algorithm) 虚拟地址
};
//
同时在trap.c中加入了缺页异常的处理程序。init.c 中也加入了对磁盘的初始化。本实验中主要是为了理解操作系统的实现。所以我们将对磁盘的操作看成一个api即可。以
swapfs_write为例。
swapfs_write(swap_entry_t entry, struct Page *page) 表示将page空间的内存写入到entry位置的磁盘中。为了实现这个lab,在理解完整个流程后就要明确check_swap函数。
首先了解有关虚拟内存的数据结构
// mm_struct和vma_struct都是现代linux内核中重要的东西。
// 首先每个程序只有一个mm_struct结构体,表示这个进程的内存管理器。
// 这个进程又有多个内存区域,对应于只读区,静态区,代码区等等(这些区域分别对应一个vma_struct)。
struct mm_struct {
list_entry_t mmap_list; // linear list link which sorted by start addr of vma
struct vma_struct *mmap_cache; // current accessed vma, used for speed purpose
pde_t *pgdir; // the PDT of these vma
int map_count; // the count of these vma
void *sm_priv; // the private data for swap manager
};
struct vma_struct {
struct mm_struct *vm_mm; // the set of vma using the same PDT
uintptr_t vm_start; // start addr of vma
uintptr_t vm_end; // end addr of vma, not include the vm_end itself
uint32_t vm_flags; // flags of vma
list_entry_t list_link; // linear list link which sorted by start addr of vma
};
检测程序.
// 引用了部分代码
static void check_swap(void)
{
pde_t *pgdir = mm->pgdir = boot_pgdir;
assert(pgdir[0] == 0);
// 0x1000-0x6000, 这里实际上就是0x1000-0x5999可用。一共5页。(虚拟页)
struct vma_struct *vma = vma_create(BEING_CHECK_VALID_VADDR, CHECK_VALID_VADDR, VM_WRITE | VM_READ);
assert(vma != NULL);
insert_vma_struct(mm, vma);
for (i = 0; i < CHECK_VALID_PHY_PAGE_NUM; i++)
{
check_rp[i] = alloc_page();
}
list_entry_t free_list_store = free_list;
list_init(&free_list);
assert(list_empty(&free_list));
// 此时空闲链表中没有数据,再通过下面释放四页。总共有四页可用
unsigned int nr_free_store = nr_free;
nr_free = 0;
for (i = 0; i < CHECK_VALID_PHY_PAGE_NUM; i++)
{
free_pages(check_rp[i], 1);
}
// 中间将空闲链表清空后,然后释放4页。
assert(nr_free == CHECK_VALID_PHY_PAGE_NUM);
// 总结:现在就是一共4个物理空闲页,虚拟空间一共5页。
// 那么一定的,对其中一个页的访问需要借助磁盘了。
pgfault_num = 0;
check_content_set();
// 此时0x1000-0x4999一共四个页都分配了物理地址
ret = check_content_access();
}
static inline void
check_content_set(void)
{
// 这里的缺页函数都会进入到do_pgfault的pgdir_alloc_page中,而且此时是不需要换出的。
// 在执行这前,这些内存值都没有被访问,所以每第一次访问会出现异常。
// 第二次可以正常访问,所以缺页总数第一次访问时都是加1的。第二次是相等的
*(unsigned char *)0x1000 = 0x0a;
assert(pgfault_num == 1);
*(unsigned char *)0x1010 = 0x0a;
assert(pgfault_num == 1);
*(unsigned char *)0x2000 = 0x0b;
assert(pgfault_num == 2);
*(unsigned char *)0x2010 = 0x0b;
assert(pgfault_num == 2);
*(unsigned char *)0x3000 = 0x0c;
assert(pgfault_num == 3);
*(unsigned char *)0x3010 = 0x0c;
assert(pgfault_num == 3);
*(unsigned char *)0x4000 = 0x0d;
assert(pgfault_num == 4);
*(unsigned char *)0x4010 = 0x0d;
assert(pgfault_num == 4);
}
// 但是当执行
static int
_fifo_check_swap(void) {
// 0x1000-0x4999一共四个页都分配了物理地址
cprintf("write Virt Page c in fifo_check_swap\n");
*(unsigned char *)0x3000 = 0x0c;
assert(pgfault_num==4);
cprintf("write Virt Page a in fifo_check_swap\n");
*(unsigned char *)0x1000 = 0x0a;
assert(pgfault_num==4);
cprintf("write Virt Page d in fifo_check_swap\n");
*(unsigned char *)0x4000 = 0x0d;
assert(pgfault_num==4);
cprintf("write Virt Page b in fifo_check_swap\n");
*(unsigned char *)0x2000 = 0x0b;
assert(pgfault_num==4);
// 这里一定会出现缺页。在check_swap中将空闲物理页设置为4个了,当访问0x5000时,就需要加载新的页,但是此时的空闲物理页已经用完,所以需要换出。
cprintf("write Virt Page e in fifo_check_swap\n");
*(unsigned char *)0x5000 = 0x0e;
assert(pgfault_num==5);
cprintf("write Virt Page b in fifo_check_swap\n");
*(unsigned char *)0x2000 = 0x0b;
assert(pgfault_num==5);
cprintf("write Virt Page a in fifo_check_swap\n");
*(unsigned char *)0x1000 = 0x0a;
assert(pgfault_num==6);
cprintf("write Virt Page b in fifo_check_swap\n");
*(unsigned char *)0x2000 = 0x0b;
assert(pgfault_num==7);
cprintf("write Virt Page c in fifo_check_swap\n");
*(unsigned char *)0x3000 = 0x0c;
assert(pgfault_num==8);
cprintf("write Virt Page d in fifo_check_swap\n");
*(unsigned char *)0x4000 = 0x0d;
assert(pgfault_num==9);
cprintf("write Virt Page e in fifo_check_swap\n");
*(unsigned char *)0x5000 = 0x0e;
assert(pgfault_num==10);
cprintf("write Virt Page a in fifo_check_swap\n");
assert(*(unsigned char *)0x1000 == 0x0a);
*(unsigned char *)0x1000 = 0x0a;
assert(pgfault_num==11);
return 0;
}
int do_pgfault(struct mm_struct *mm, uint32_t error_code, uintptr_t addr)
{
int ret = -E_INVAL;
// try to find a vma which include addr
struct vma_struct *vma = find_vma(mm, addr);
pgfault_num++;
// If the addr is in the range of a mm's vma?
if (vma == NULL || vma->vm_start > addr)
{
cprintf("not valid addr %x, and can not find it in vma\n", addr);
goto failed;
}
// check the error_code
// 当发生缺页异常时,硬件将会对error_code压栈,并且将出错的地址写入cr2寄存器中。
switch (error_code & 3)
{
default:
/* error code flag : default is 3 ( W/R=1, P=1): write, present */
case 2: /* error code flag : (W/R=1, P=0): write, not present */
if (!(vma->vm_flags & VM_WRITE))
{
cprintf("do_pgfault failed: error code flag = write AND not present, but the addr's vma cannot write\n");
goto failed;
}
break;
case 1: /* error code flag : (W/R=0, P=1): read, present */
cprintf("do_pgfault failed: error code flag = read AND present\n");
goto failed;
case 0: /* error code flag : (W/R=0, P=0): read, not present */
if (!(vma->vm_flags & (VM_READ | VM_EXEC)))
{
cprintf("do_pgfault failed: error code flag = read AND not present, but the addr's vma cannot read or exec\n");
goto failed;
}
}
uint32_t perm = PTE_U;
if (vma->vm_flags & VM_WRITE)
{
perm |= PTE_W;
}
addr = ROUNDDOWN(addr, PGSIZE);
ret = -E_NO_MEM;
pte_t *ptep = NULL;
#if 1
ptep = get_pte(mm->pgdir, addr, 1);
struct Page *page;
/*LAB3 EXERCISE 1: YOUR CODE*/
//(1) try to find a pte, if pte's PT(Page Table) isn't existed, then create a PT.
if (*ptep == 0)
{
//(2) if the phy addr isn't exist, then alloc a page & map the phy addr with logical addr
page = pgdir_alloc_page(mm->pgdir, addr, perm);
}
else
{ // 执行到这里只有一种情况就是,pte表示的是磁盘中的位置。不可能是正常的物理地址,因为正常的物理地址不会走异常中断的
cprintf("****** need swap\n");
if (swap_init_ok)
{
struct Page *page = NULL;
swap_in(mm, addr, &page);
page->pra_vaddr =addr;
page_insert(mm->pgdir, page, addr, perm);
swap_map_swappable(mm, addr, page, 1);
//(1)According to the mm AND addr, try to load the content of right disk page into the memory which page managed.
//(2) According to the mm, addr AND page, setup the map of phy addr <---> logical addr
//(3) make the page swappable.
}
else
{
cprintf("no swap_init_ok but ptep is %x, failed\n", *ptep);
goto failed;
}
}
#endif
ret = 0;
failed:
return ret;
}
访问一个内存时,如果异常,却地址范围和权限都是正确的,就会进入异常处理程序。如果这个地址对应的页没有映射,就给他分配一个页。在分配一个页的过程中,如果没有可用物理地址就将最早缺页异常的空间换出到磁盘空间。此时这个地址对应的pte是磁盘中的位置。当下一次访问这个地址的时候一定出现缺页异常。只是这个地址对应的pte是有值的,这个值是可磁盘中的位置是有关联的,所以可以将磁盘中的数据加载到这个虚拟内存(缺页异常地址)对应的页中。