lab1和2概述:

本次操作系统实验,我们对计算机的操作系统进行了初步的探究,通过完成作业和问题,我们对操作系统的启动、内核载入、一些系统函数、堆栈的使用、内存管理有了更加深刻的了解,并且在完成作业的同时,深刻了解了计算机内存的结构以及每一块儿对应的作用。从实践的角度出发,很好的理解了一个操作系统的底层功能的实现。具体来说:

在启动计算机的部分中,通过gdb 的单补调试和断点控制,我们看到计算机执行一条条机器指令的过程,并且初步的理解了一些指令的作用以及总体了解了计算机启动和内核载入的过程。之后,我们对于Kernal 中的格式化输出函数进行了探究,其中着重观察了其对于进制的控制以及输出时页面的控制。此外,我们还自主探究了一些较为底层的与I/O 有密切关系的函数,从而更加深刻的对I/O 功能有了一定的了解。之后,我们还探究了计算机堆栈的使用,理解了指针在堆栈中的作用,并且在作业中通过对于指针的操作改造了原有的堆栈显示函数。在内存管理的部分,我们的一些对于计算机内存的了解的不足使我们曾一度停滞不前。之后,我们通过学习《深入了解Linux 内核》以及menlayout.h 文件中的一些提示信息逐渐了解了计算机对于内存管理的方法。在物理页管理的部分,实现了boot 的分配、page 的初始化、分配以及释放等。在虚拟内存部分,我们深入了解了计算机物理地址、线性地址、逻辑地址的相互转化,实现了虚拟内存中页表的管理,实现了页目录的初始化、boot 页的映射、也得查询删除及插入等。之后,我们将men_init()中的代码补充完全,实现了对于内核部分线性空间的初始化,并最终通过了所有的check。

此外,在完成这两大块内容的同时,对于问题的完成也使我们对于一些基础概念的理解,以及代码部分知识的掌握有了进一步的提升。

介绍工具和环境的配置

进入我们的具体问题

问题1:

问题2.1:说明两个函数之间的联系

问题2.2解释题目的代码

作业1:改成8进制输出

作业2

(一) 作业描述:

The above exercise should give you theinformation you need to implement a stack backtrace

function, which you should callmon_backtrace(). A prototype for this function is already

waiting for you in kern/monitor.c. You cando it entirely in C, but you may find the read_ebp()

function in inc/x86.h useful. You'll alsohave to hook this new function into the kernel

monitor's command list so that it can beinvoked interactively by the user.

The backtrace function should display alisting of function call frames in the following format:

Stack backtrace:

ebp f0109e58 eip f0100a62 args 00000001f0109e80 f0109e98 f0100ed2 00000031

ebp f0109ed8 eip f01000d6 args 0000000000000000 f0100058 f0109f28 00000061

...

The first line printed reflects thecurrently executing function, namely mon_backtrace itself,

the second line reflects the function thatcalled mon_backtrace, the third line reflects the

function that called that one, and so on.You should print all the outstanding stack frames. By

studying kern/entry.S you'll find thatthere is an easy way to tell when to stop.

(二) 解答:

1.堆栈是一种简单的数据结构,是一种只允许在其一端进行插入或删除的线性表。允许插入或删除操作的一端称为栈顶,另一端称为栈底,对堆栈的插入和删除操作被称入栈和出栈。

2.有一组CPU 指令可以实现对进程的内存实现堆栈访问。其中,POP 指令实现出栈操作,PUSH 指令实现入栈操作。CPU 的ESP 寄存器存放当前线程的栈顶指针,EBP 寄存器中保存当前线程的栈底指针。CPU 的EIP 寄存器存放下一个CPU 指令存放的内存地址,当CPU 执行完当前的指令后,从EIP寄存器中读取下一条指令的内存地址,然后继续执行。

3.实现原理:C 函数调用时,首先将参数push入栈,然后push 返回地址,接着将原来的EBP push 入栈,然后将ESP 的值赋给EBP,令ESP 指向新的栈顶。而函数返回时,会将EBP 的值赋予ESP,然后pop 出原来的EBP 的值赋予EBP指针。

intmon_backtrace(int argc, char **argv, structTrapframe *tf){// Your code here.unsigned int ebp;unsigned int eip;unsigned int args[5];unsigned int i;ebp = read_ebp();cprintf("Stack backtrace:\n");do {eip = *((unsigned int*)(ebp + 4));for(i=0; i<5; i++)args[i] = *((unsigned int*) (ebp + 8 +4*i));cprintf(" ebp %08x eip %08x args %08x%08x %08x %08x %08x\n",ebp, eip, args[0], args[1], args[2],args[3], args[4]);ebp = *((unsigned int *)ebp);} while(ebp != 0);Return 0;}

作业 3

作业描述

在文件 kern/pmap.c 中,你需要实现以下函数的代码(如下,按序给出):

boot_alloc()mem_init() //(在调用check_page_free_list(1)之前的部分)page_init()page_alloc()page_free()

解答

首先,我们要先来理解一下内存管理的机制。

1. 三个重要地址:

1) 逻辑地址为用户程序所使用的地址;

2) 线性地址是操作系统根据x86 段式地址转换将逻辑地址转换后的地址,具体来说:线性地址 = 逻辑地址 -KERNBASE

3) 物理地址是操作系统地址转换系统将线性地址通过页表地址转换后得到的数据真实存储地址逻辑地址;

2. 物理内存分页:

一个物理页的大小为4K 字节,第0 个物理页从物理地址 0x00000000 处开始。由于页的大小为4KB,就是0x1000 字节,所以第1 页从物理地址0x00001000处开始。第2 页从物理地址 0x00002000 处开始。可以看到由于页的大小是4KB,所以只需要32bit 的地址中高20bit 来寻址物理页。页表:一个页表的大小为4K 字节(32bit),放在一个物理页中。由1024 个4字节的页表项组成。页表中的每一项的内容(每项4 个字节,32bit)高20bit 用来放一个物理页的物理地址,低12bit 放着一些标志。

页目录:一个页目录大小为4K 字节(32bit),放在一个物理页中。由1024 个4 字节的页目录项组成。页目录中的每一项的内容(每项4 个字节)高20bit 用来放一个页表的物理地址,低12bit 放着一些标志。

3. 虚拟地址:

如果CPU 寄存器中的分页标志位被设置,那么执行内存操作的机器指令时,CPU 会自动根据页目录和页表中的信息,把虚拟地址转换成物理地址,完成该指令。比如 mov eax,004227b8h ,这是把地址004227b8h 处的值赋给寄存器的汇编代码,004227b8 这个地址就是虚拟址。CPU 在执行这行代码时,发现寄存器中的分页标志位已经被设定,就自动完成虚拟地址到物理地址的转换,使用物理地址取出值,完成指令。对于Intel CPU 来说,分页标志位是寄存器CR0 的第31位,为1 表示使用分页,为0 表示不使用分页。对于初始化之后的 Win2k 我们观察 CR0 ,发现第31 位为1。表明Win2k 是使用分页的。使用了分页机制之后,4G 的地址空间被分成了固定大小的页,每一页或者被映射到物理内存,或者被映射到硬盘上的交换文件中,或者没有映射任何东西。对于一般程序来说,4G 的地址空间,只有一小部分映射了物理内存,大片大片的部分是没有映射任何东西。物理内存也被分页,来映射地址空间。对于32bit的Win2k,页的大小是4K 字节。CPU 用来把虚拟地址转换成物理地址的信息存放在叫做页目录和页表的结构里。

4. 然后我们可以再inc/mmu.h 看到对于线性地址定义的阐述以及对于页表、页

目录的值的宏定义。

5.在inc/memlayout.h 中看到虚拟内存的层次结构。

6.然后打开kern/pmap.c,首先来看boot——allocated()函数。bootmain的最后一句跳转到0x10000c 处,开始执行 entry.S 的代码.kernel 结束之后就是freememory 了,而在free memory 的最开始存放的是pgdir,这块内存就是由boot_alloc 申请。

于是,boot_alloc(unit32_t n)的功能就是申请n 个字节的地址空间,返回申请空间的首地址。如果n 是0,则返回nextfree(未分配空间的首地址)。其中,分配的地址是页对齐的,即4K 对齐。值得注意的是,未初始化的全局变量和静态变量会被自动初始化为0。函数以首先定义静态局部变量nextfree,它指向空闲内存空间的首地址。由于未初始化,所以变量自动初始化为0,所以首次调用boot_alloc()函数的时候,nextfree 的值是0,会执行下面的语句:

if (!nextfree) {extern char end[];nextfree = ROUNDUP((char *) end, PGSIZE);}

还有一个细节就是需要4k 页对齐,使用了ROUNDUP 宏定义。接下来,在你n>0时,我们将nextfree+n 内存加入,并返回当前分配完的这块内存的头指针。

if(n>0){result = nextfree;nextfree = (char *)((uint32_t)nextfree+n);nextfree = ROUNDUP(nextfree,PGSIZE);return result;}else{return nextfree;}

7.接下来是mem_init()函数

这个函数只需要补充一部分就可以了。主要是要为struct Page 的结构体的指针pages 申请一定的地址空间。首先来看structPage 的定义:

struct Page{ //Next page on the free list.struct Page *pp_link;uint16_t pp_ref;}

结构体里主要有两个变量:

1) pp_link 表示下一个空闲页,如果pp_link=0,则表示这个页面被分配了,否则,这个页面未被分配,是空闲页面。

2) pp_ref 表示页面被引用数,如果为0,表示是空闲页。(这个变量类似于智能指针中指针的引用计数)。补充的代码比较简单,就是位pages 申请足够的空间(npages 的页面),来存放这些结构体,并且用memset 来初始化:

pages = (struct Page*)boot_alloc(sizeof(structPage)*npages);memset(pages, 0, sizeof(struct Page)*npages);

8. page_init(void)函数,按照提示的内存划分分别初始化page 就可以了。尤其注意提示4 要参考虚拟内存的层次图,注意划分Empty Memory。对于保留不能让其他程序使用的page 均设置pp_ref=1 即可,对于可以使用的page 做成一个链表,在初始化可用page 时实际上就是做了一个链表的头插入操作。

page_init(void){// The example code here marks all physicalpages as free.// However this is not truly the case. Whatmemory is free?// Change the code to reflect this.// NB: DO NOT actually touch the physicalmemory corresponding to// free pages!//以下按照提示的内存划分分别初始化pagesize_t i;for (i = 0; i < npages; i++) {if(i == 0){// 1) Mark physical page 0 as in use.// This way we preserve the real-mode IDTand BIOS structures// in case we ever need them. (Currently wedon't, but...)pages[i].pp_ref = 1;pages[i].pp_link = NULL;}else if(i>=1 &&i<npages_basemem){// 2) The rest of base memory, [PGSIZE,npages_basemem * PGSIZE)// is free.pages[i].pp_ref = 0;pages[i].pp_link = page_free_list;page_free_list = &pages[i];}else if(i>=IOPHYSMEM/PGSIZE &&i< EXTPHYSMEM/PGSIZE ){// 3) Then comes the IO hole [IOPHYSMEM,EXTPHYSMEM), which must// never be allocated.pages[i].pp_ref = 1;pages[i].pp_link = NULL;}else if( i >= EXTPHYSMEM / PGSIZE&&i < ( (int)(boot_alloc(0)) -KERNBASE)/PGSIZE){// 4) Then extended memory [EXTPHYSMEM,...).// Some of it is in use, some is free.Where is the kernel// in physical memory? Which pages arealready in use for// page tables and other data structures?//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];}}}

9. page_alloc() 和 page_free()的函数思路比较简单,一个是page申请,一个是page 释放,主要也就是链表操作,和pp_ref 的赋值。

struct Page *page_alloc(int alloc_flags){// Fill this function in//如page_free_list 为空,则不能正确分配空闲内存,所以返回空指针if(page_free_list==NULL)return NULL;struct Page *result = page_free_list;//将物理空闲列表的头指针赋值给result,相当于分配了一个空闲内存page_free_list = result->pp_link;result->pp_link = NULL;if(alloc_flags & ALLOC_ZERO)memset(page2kva(result),0,PGSIZE);return result;}voidpage_free(struct Page *pp){// Fill this function in 通过修改列表的表头,将pp加入到队列中//验证pp_ref!=0 的情况,检验要释放的page 没有被任何程序使用if(pp->pp_ref!=0){panic("pp_ref!=0");}pp->pp_link = page_free_list;page_free_list = pp;}

问题三:

先给出答案:虚拟地址。对于这个问题,首先pdf 的上面一页教学内容已经基本把知识讲到了,下面的截图就是响应的关键内容:

然后搜索资料还发现一下很重要的地方:

From code executing on the CPU, once we'rein protected mode (which we

entered first thing inboot/boot.S),there'sno way to directly use a linear or physical

address. All memory references areinterpreted as virtual addresses and translated

by the MMU, which means all pointers in Care virtual addresses.

这句话就告诉我们,在程序里面的所有地址,都是虚拟地址,系统会通过MMU来翻译得到他的物理地址。也就是说,程序里面的一切的地址都是虚拟地址。即使是物理地址,程序在调用的时候也会把他当成是虚拟地址,会转化为物理地址。

综上所述,这道题就是要分清楚在JOS 系统里面,什么是物理地址,什么是虚拟地址。由于系统会经常要进行地址的相关运算,所以经常要进行强制转化,把一个unsigned int 型变量转化为一个地址,或者相反,所以在程序里面,需要对两者进行区分。x 应该是uintptr_t,在程序里面,任何指针都是虚拟地址(段偏移)。

作业四:

Now we set up virtual memory

看到这句话,进入我们的第四问。往下看一下mem_init的几个注释,接下来我们大致要做的事是将虚拟内存空间[UPAGES,UPAGES+ROUNDUP((sizeof(structPage) * npages)映射到物理空间

[PADDR(pages),PADDR(pages)+ROUNDUP((sizeof(struct Page) *npages)中这个映射关系加入到页目录pagedir中去;然后又是对bootstack和KERNBASE的相关地址做相同相似的映射并加入页目录,然后就是一大堆check。我们需要写下的相关程序就是上边操作映射的page_insert和boot_map_region,以及在实现这两个函数要用到的pgdir_walk、page_lookup和page_remove。

我们从最基本的也是最重要的操作pgdir_walk 说起。这个函数做的事情就是: 给定一个页目录表指针pgdir ,该函数应该返回线性地址va 所对应的页表项指针。两个参数pde_t *pgdir, const void *va, int create,根据注释可知具体要实现的东西是:pgdir 是一个指向page dictionary 的指针,然后va 是虚拟地址,这个函数要返回这个虚拟地址指向的页表项(PTE)。如果这个PTE 存在,那么返回这个PTE 即可,如果不存在,参数create 是指这个页表项是否需要被创建,若需要就创建一个页表项,不需要就返回NULL。创建失败返回NULL,成功就为这个页表项的引用计数+1。

所以我们的代码如下:

1.首先得到这个虚拟地址所在的页目录偏移:

int pdeIndex = (unsigned int)va >>22;

2.通过页目录表pgdir+页目录偏移求得这页在pagedirectory 的地址,按照注释的几种情况分别操作:

3.最后计算这个页目录项对应的页表页的基地址,返回对应的页表项指针

然后再来看看mem_init主要用到的boot_map_region函数。提示告诉我们要将虚拟内存空间[va, va+size)映射到物理空间[pa, pa+size)这个映射关系加入到页目录pagedir中。这个函数主要的目的是为了设置虚拟地址UTOP之上的地址范围,这一部分的地址映射是静态的,在操作系统的运行过程中不会改变,所以,这个页的Page结构体中的pp_ref域的值不会发生改变。pde_t *pgdir,uintptr_t va, size_t size, physaddr_t pa,int perm,几个参数意义也很明显,一个页目录表指针pgdir,虚拟地址va和要映射的size,和va映射的物理地址pa,最后权限标志位perm。所以我们的代码也很简单:

接下来的insert函数也是在mem_init进行调用,其目的和boot_map_region很像,把一个物理页pp与虚拟地址va建立映射,主要是操作的空间对象不同。pde_t *pgdir, struct Page *pp, void *va,int perm,几个参数和前面也差不多。根据注释需要注意的是:

如果虚拟内存va 处已经有一个物理页与它映射,那么应该调用page_removed()。如果必要的话,应该分配一个页表并把它插入页目录中。pp->ref 应该在插入成功后+1。如果一页原来就在va 处,那么TLB 一定是无效的。若成功则返回0,没有成功分配页表的话就返回E_NO_MEM。

根据这些情况写出我们的代码:

来看看insert 要用到的page_remove 函数。取消虚拟地址va 处的物理页映射,如果这个地址上本来就没有物理页,那么就不用取消。注意细节就是:这处物理页的引用计数要减1,然后要将它free,如果这个地址的页表项存在,那么页表项要置0。页表中remove 一个表项时要将TLB 置为无效。根据注释信息代码也很明了:

最后,在remove 中我们又用了一个函数,也是本题的最后一个函数page_lookup。结合注释顾名思义,这个函数将会返回虚拟地址va 映射的页page 结构体的指针。然后如果参数pte_store!=0,那么我们将页表项pte 的地址存储到pte_store 中。如果va 处没有物理页,那么返回NULL。所以代码:

作业五:

part3 主要是用户空间和内核空间的一些东西。主要区分就是ULIM,在ULIM上面的就是内核空间,在下面的部分就是用户空间

根据提示需要映射用户仅可读的页,所以我们定义权限为PTE_U|PTE_P。之后,我们通过ROUNDUP 计算出pages 结构体的大小,并使用已经定义好的page_insert()进行映射。

此处根据要求我们把虚拟地址[KSTACKTOP-KSTKSIZE, KSTACKTOP)映射到以bootstack 为起点的物理地址(bootstack 实际存储的是其虚拟地址,需要转换位物理地址),并且在权限上为内核可读写,用户不可见,所以在设置权限后,使用boot_map_region()完成映射。

这段代码要求把地址从[KERNBASE, 2^32)映射到[0, 2^32 - KERNBASE)。但是我们没有2^32-KERNNASE byte 的内存,所以我们需要通过ROUNDUP 来得到size。

问题4:

根据menlayout.h 中的内存表我们可以补充表格如下

我们已经将内核和用户环境放在了相同的地址空间,为什么用户程序不能读或者写内核内存?什么样的具体机制保护内核内存?

主要看低3 位,即U,W,P 三个标志位。

p:代表页面是否有效,若为1,表示页面有效。否则,表示页面无效,不能映射页面,否则会发生错误。

W:表示页面是否可写。若为1,则页面可以进行写操作,否则,页面是只读页面,不能进行修改。

U:表示用户程序是否可以使用该页面。若位1,表示此页面是用户页面,用户程序可以使用并且访问该页面。若为0,则表示用户程序不能访问该页面,只有内核才能访问页面。

上面的页面标志位,可以有效的保护系统的安全。由于操作系统运行在内核空间(微内核除外,其部分系统功能在用户态下进行)中运行,而一般的用户程序都是在用户空间上运行的。所以用户程序的奔溃,不会影响到操作系统,因为用户程序无权对内核地址中的内容进行修改。这就有效的对操作系统和用户程序进行了隔离,加强了系统的稳定性。

3. 这个操作系统最大能支持多大的物理内存?为什么?

2GB

原因:操作系统使用4MB 的UPAGES 存放页的结构体Page,每个页占据8B 的空间,而每个页映射4KB 的内存,所以总共映射的内存为:4MB/8B*4KB = 2GB.

4. 管理内存有多大的空间开销,如果我们拥有最强大的物理内存?这个空间开

销如何减小?

(1)存放所有的Page,需要1024*4B=4MB

(2)存放页目录表 kern_pgdir 4KB

(3)存放当前的页表4MB。

总的开销:8196 KB。

我们可以将每一页的空间定为4MB 这样空间利用率就会有很大的提升

MIT JOS LAB12学习笔记相关推荐

  1. MIT JOS LAB3学习笔记

    LAB3概述: 本次操作系统实验,我们对计算机的操作系统进行了初步的探究,通过完成作业和问题,对lab3部分有了较好的理解.Lab3主要实现能运行被保护的用户模式环境(protected user-m ...

  2. MIT JOS LAB4学习笔记

    Lab4 Part A: 多处理器支持和协作式多任务 练习 1 :实现在 kern/pmap.c 中的 mmio_map_region 方法.你可以看看 kern/lapic.c 中的 lapic_i ...

  3. gram矩阵的性质_第十七课:正交矩阵和GramSchmidt正交化——MIT线性代数课程学习笔记...

    公众号关注  "DL_NLP" 设为 "星标",重磅干货,第一时间送达! ◎ 原创 | 深度学习算法与自然语言处理 ◎ 作者 | 丁坤博 一. 知识概要 这一节 ...

  4. MIT 6.s081学习笔记

    MIT 6.s081学习笔记 introduction 计算机组织结构: 最底部是一些硬件资源,包括了CPU,内存,磁盘,网卡 最上层会运行各种应用程序,比如vim,shell等,这些就是正在运行的所 ...

  5. MIT 6.824 学习笔记(一)--- RPC 详解

    从本文开始,将记录作者学习 MIT 6.824 分布式系统的学习笔记,如果有志同道合者,欢迎一起交流. RPC 的定义和结构 RPC 全称为 Remote Procedure Call,他表示一种远程 ...

  6. [MIT]微积分重点学习笔记 目录

    先介绍下自己的情况,大学的时候学习不认真,很多概念都忘记了,工作中有时要用到微积分,碰到不会的在网上查询,感觉这样学习的比较零散,也不能建立系统的认识.多次想要从头看一遍同济版<高等数学> ...

  7. MIT 6.828 学习笔记4 Lab2实验报告

    Lab2实验报告 Execrise 1 static void *boot_alloc(uint32_t n) {static char *nextfree;char *result;if (!nex ...

  8. MIT 6.828 学习笔记2 阅读main.c

    #include <inc/x86.h> #include <inc/elf.h>/********************************************** ...

  9. MIT 6.824学习笔记

    introduction 为什么需要分布式计算? 并行,容错,物理因素(地理位置),安全 分布式计算面临的挑战? 并发性,部分错误,性能问题 基础设施 存储,通信,计算----目标是抽象成从外部看上去 ...

最新文章

  1. ASP.NET 3.5揭秘-读书笔记1
  2. 《Photoshop Lab修色圣典(修订版)》—第1课1.4节逐步校正峡谷图像
  3. oracle的catalog,oracle学习笔记 RMAN catalog的创建和使用
  4. Apache访问日志切割
  5. java中的操作符(位操作符>>与>>>的区别)
  6. 解决The current branch is not configured for pull No value for key branch.master.merge found in config
  7. 【Java】类加载过程
  8. 深度学习之自编码器(2)Fashion MNIST图片重建实战
  9. android mina分析,Android与Mina整合
  10. 利用JSONP解决AJAX跨域问题的原理与jQuery解决方案
  11. 嵌入式OS入门笔记-以RTX为案例:四.简单的时间管理
  12. ktor框架用到了netty吗_Ktor-构建异步服务器和客户端的 Kotlin 框架
  13. 如何检测python是否安装_布同自制Python函数帮助查询小工具
  14. 用python写网络爬虫-英文翻译
  15. python爬取链家租房信息_爬取链家租房信息数据分析
  16. matlab如何用二分法求函数零点,用二分法求函数的零点及二分法定义
  17. pandas 常见写法
  18. IT视频课程集(包含各类Oracle、DB2、Linux、Mysql、Nosql、Hadoop、BI、云计算、编程开发、网络、大数据、虚拟化
  19. 【数学】拉格朗日乘子法(Lagrange Multiplier) 和KKT条件理解
  20. 研磨设计模式之简单工厂模式-3

热门文章

  1. 最新-安装Windows与Ubuntu双系统
  2. 使用PS批量处理图片大小
  3. CF1647F Madoka and Laziness
  4. 病毒公告:入侵后留下后门的黑客病毒(转)
  5. opencv微信二维码引擎的使用(for java)
  6. HTMLCSS基础篇之十一:字体与颜色样式
  7. 2021阿里实习生前端面试题
  8. 阿里实习生电话面试果断被鄙视
  9. 基于多任务学习和GCN的交通路网出租车需求预测
  10. 讯飞智能录音笔SR502:七夕值得入手的资料备忘好物