MIT6.828——Lab2(麻省理工操作系统实验)

Lab2

Lab2 是关于操作系统存储管理的细节。主要是建立内存模型,页表,物理地址映射等。
在Lab2之前,请复习好前序知识:

Lab2内存管理准备知识

MIT6.828——Lab1 PartA

MIT6.828——Lab1 PartB

Part1 物理内存管理

在开始做题之前,需要了解一下一些常用的函数,宏以及内存布局,建议复习一下LAB1中的简单内存模型,LAB2预备知识中的相关。这里有几个很有用的地址变换工具,具体实现可以查看 mmu.hpmap.h,提前掌握这些小工具对于理解地址变换和后续的程序编写有很大帮助。

名称 参数 作用 PADDR 内核
虚拟地址

kva 将内核虚拟地址kva转成对应的物理地址 KADDR
物理地址

pa 将物理地址pa转化为内核虚拟地址 page2pa 页信息结构
struct PageInfo

通过空闲页结构得到这一页起始位置的物理地址 pa2page
物理地址

pa 通过物理地址pa获取这一页对应的页结构体struct PageInfo page2kva 页信息结构
struct PageInfo

通过空闲页结构得到这一页起始位置的虚拟地址 PDX
线性地址

la 获得该线性地址la对应的页目录项索引 PTX
线性地址

la 获得该线性地址la在二级页表中对应的页表项索引 PTE_ADDR(pte) 页表项或页目录项的

获取对应的页表基地址或物理地址基地址(低12位为0)

[En]

Get the corresponding page table base address or physical address base address (lower 12 bits is 0)

MIT6.828——Lab2(麻省理工操作系统实验)
  • 首先关于第一个函数 boot_alloc()这是在内存管理机制还没建立起来时,系统内存的分配函数。在page等建立以后当使用 page_alloc()而不该再使用该函数。根据函数的注释,先记录当前的free指针,然后将free指针偏移n单元即可,注意内存对齐(使用ROUNDUP函数)。
static void *
boot_alloc(uint32_t n)
{
    static char *nextfree;  // virtual address of next byte of free memory
    char *result;
    if (!nextfree) {
        extern char end[];
        nextfree = ROUNDUP((char *) end, PGSIZE);
    }
    // Allocate a chunk large enough to hold 'n' bytes, then update
    // nextfree.  Make sure nextfree is kept aligned
    // to a multiple of PGSIZE.

    // LAB 2: Your code here.

    result = nextfree;
    nextfree = ROUNDUP(result + n, PGSIZE);
    return result;
}
  • 第二个函数是初始化内存管理了,只需要做到check_page_free_list(1)之前即可。 首先使用 i386_detect_memory获取物理内存大小;之后创建内核的页目录,使用的是 boot_alloc(),大小是1页(4KB);然后将内核页目录安装到一个页目录项中;之后创建空闲物理页数组pages。
void
mem_init(void)
{
    uint32_t cr0;
    size_t n;

    i386_detect_memory();

    //////////////////////////////////////////////////////////////////////
    // create initial page directory.
    kern_pgdir = (pde_t *) boot_alloc(PGSIZE);
    memset(kern_pgdir, 0, PGSIZE);

    //////////////////////////////////////////////////////////////////////
    // Permissions: kernel R, user R
    // UVPT是 User read-only virtual page table的虚拟地址
    // PDX获得页目录项索引
    // 将内核页目录安装到内核页目录中(参考前一篇文章中类似的搞法)
    /*
    ULIM, MMIOBASE-->+------------------------------+ 0xef800000
                     |  Cur. Page Table (User R-)   | R-/R-  PTSIZE
     UVPT       ---->+------------------------------+ 0xef400000
    此处PTSIZE=4096*1024 =4MB 为一个页目录项能映射的内存大小
    */
    kern_pgdir[PDX(UVPT)] = PADDR(kern_pgdir) | PTE_U | PTE_P;

    // 分配pages数组,一共有npages个物理页,每个页使用struct PageInfo结构记录,并填充0
    // Your code goes here:
    size_t sizes = sizeof(struct PageInfo) * npages;
    pages = (struct PageInfo*)boot_alloc(sizes);
    memset(pages, 0, sizes);

    page_init();

    check_page_free_list(1);
  • 第三个函数,建立page相关的数据结构。首先哪些物理内存是free的?根据注释,首先物理内存的第0页需要被标记为已使用;IO-hole需要被标记为已使用,不能被分配出去;扩展地址包含内核地方不能被分配出去,剩下的空间就可标记为free并后续可以分配出去。
void
page_init(void)
{
    // npages_basemem :Amount of base memory (in pages)

    //第0页不能被后续分配出去
    pages[0].pp_ref = 1;
    pages[0].pp_link = NULL;

    size_t i;
    //内核的尾端所在的页索引号(那物理地址进行计算)
    size_t kernel_end_page = PADDR(boot_alloc(0)) / PGSIZE;
    for (i = 1; i < npages; i++) {
        //IO-hole和内核部分不能被分配出去
        if (i >= npages_basemem && i < kernel_end_page) {
            pages[i].pp_ref = 1;
            pages[i].pp_link = NULL;
        } else {
            //建立free物理页链表
            pages[i].pp_ref = 0;
            pages[i].pp_link = page_free_list;
            page_free_list = &pages[i];
        }
    }
}
  • 第四个函数,是后续应该使用的内存分配函数 page_alloc,根据前面我们知道, page_free_list指着第一个空闲页,因此只需要从这个链表上摘取一个下来即可。这里通过前面的几个函数或者宏,可以将 struct PageInfo轻松地对应到物理地址或者虚拟地址。
// 分配一个物理页
// If (alloc_flags & ALLOC_ZERO) 用0填充该页
// 不要增加页引用数
// 链接域要设为NULL
// 如果内存不够了,返回NULL
// Hint: use page2kva and memset
struct PageInfo *
page_alloc(int alloc_flags)
{
    //page_free_list=NULL 说明没有内存可供分配
    if (page_free_list == NULL) {
        cprintf("page_alloc: out of free memory\n");
        return NULL;
    }

    //摘下那一页
    struct PageInfo *addr = page_free_list;
    page_free_list = page_free_list->pp_link;
    addr->pp_link = NULL;

    if (alloc_flags & ALLOC_ZERO) {
        //得到这个info结构描述的那个物理页的虚拟地址,才能使用memset
        memset(page2kva(addr), 0, PGSIZE);
    }

    //返回这个空闲页的info结构
    return addr;
}

  • 第五个函数,作用是释放一个页。也就是将一个 struct PageInfo结构,重新挂回 page_free_list。注意不能释放一个引用值不为0的页,或者链接值不为空的页。
void
page_free(struct PageInfo *pp)
{
    // Fill this function in
    // Hint: You may want to panic if pp->pp_ref is nonzero or
    // pp->pp_link is not NULL.
    if (pp->pp_ref != 0 || pp->pp_link != NULL) {
        panic("page_free: can not free the memory");
        return;
    }
    //挂入链表
    pp->pp_link = page_free_list;
    page_free_list = pp;
}

Part2 虚拟内存

这一部分的前序知识,可以看上一篇文章Lab2内存管理准备知识。于是开始建立页表管理。

MIT6.828——Lab2(麻省理工操作系统实验)
  • 第一个函数在给定页目录和虚拟地址的情况下,返回指向页表项的指针。这是一个访问辅助页表以查找值的过程,这在上一篇文章中已详细介绍过。
    [En]

    the first function, given a page directory and virtual address, returns a pointer to the page table item. It is a process of accessing the secondary page table to find values, which was written in detail in the previous article.*

/*    给定一个指向页目录的指针,这个函数返回 指向线性地址va的页表项的指针 这需要访问二级页表    对应的页表不一定存在,如果create参数为false则直接返回NULL否则,该函数申请新的一页来做页表,并增     加页的引用计数值。*/pte_t *pgdir_walk(pde_t *pgdir, const void *va, int create){    // Fill this function in    // 得到页目录索引对应的页目录项    pde_t *dir = pgdir + PDX(va);    //检查这一页表是否存在    if (!(*dir & PTE_P)) {        if (!create) return NULL;        //申请新的一页        struct PageInfo* pp = page_alloc(1);        if (pp == NULL) return NULL;        pp->pp_ref++;        //得到这一页的起始物理地址,并安装到页目录中        *dir = page2pa(pp) | PTE_P | PTE_U | PTE_W;    }    // 页表的起始物理地址转为虚拟地址+在页表项中的索引---->一个指向页表项的指针    return (pte_t *) KADDR(PTE_ADDR(*dir)) + PTX(va);}
  • 第二个函数建立虚拟地址空间和物理地址空间之间的映射关系,即填写页表的值。
    [En]

    the second function establishes a mapping relationship between the virtual address space and the physical address space, that is, filling in the values of the page table.*

/*    将虚拟地址空间[va, va+size),映射到物理地址空间[pa, pa+size)    物理地址和虚拟地址都是页对齐的。    映射的过程就是填页表的过程。*/static voidboot_map_region(pde_t *pgdir, uintptr_t va, size_t size, physaddr_t pa, int perm){    // Fill this function in    // 空间大小为多少页(对齐)    size_t pieces = ROUNDUP(size, PGSIZE) / PGSIZE;    for (size_t i = 0; i < pieces; i++) {        //得到这个虚地址对于的页表项        pte_t *pte = pgdir_walk(pgdir, (void *) va, 1);        if (pte == NULL) {            panic("boot_map_region: out of memory!\n");        }        //页表项放上物理地址(页的起始地址)        *pte = pa | PTE_P | perm;        //下一页        va += PGSIZE;        pa += PGSIZE;    }}
  • 第三个功能是查找虚拟地址对应的页面。
    [En]

    the third function is to find the page corresponding to a virtual address.*

/*    得到虚拟地址va对应的页结构,如果pte_store不为空,就存入这一页的地址    va还没有对应到某个页,就返回NULL*/struct PageInfo *page_lookup(pde_t *pgdir, void *va, pte_t **pte_store){    // Fill this function in    // 查找页表项    pte_t *pte = pgdir_walk(pgdir, va, 0);    // 没有这个项    if (!pte || !(*pte & PTE_P)) {        cprintf("page_lookup: can not find out the page.\n");        return NULL;    }    // 存储记录    if (pte_store) {        *pte_store = pte;    }    // 得到页的物理地址对应的PageInfo结构    return pa2page(PTE_ADDR(*pte));}
  • 第四个功能,取消映射关系
    [En]

    the fourth function, canceling a mapping relationship*

/*    取消虚拟地址va映射到的物理页    物理页的引用计数应该减少(为0是释放)    这个地址对应的页表项(如果有)应该清空    TLB失效*/voidpage_remove(pde_t *pgdir, void *va){    // Fill this function in    // pte_store会存入页表项    pte_t *pte_store;    struct PageInfo *pp = page_lookup(pgdir, va, &pte_store);    // pp不为空说明有这一项    if (pp) {        page_decref(pp);        // 页表项清空        *pte_store = 0;        tlb_invalidate(pgdir, va);    }}
  • 第五个函数
/*    将物理地址pp映射到虚拟地址va 权限设置为 perm|PTE_P    如果va以及和一个物理地址关联了,那么应该使用page_remove()并刷TLB    pp所在的物理页的引用计数增加*/intpage_insert(pde_t *pgdir, struct PageInfo *pp, void *va, int perm){    // Fill this function in    // 对应的页表项,申请新的页如果需要    pte_t *pte = pgdir_walk(pgdir, va, 1);    if (!pte) {        return -E_NO_MEM;    }    pp->pp_ref++;    //已经存在映射关系    if (*pte & PTE_P) {        page_remove(pgdir, va);        tlb_invalidate(pgdir, va);    }    //得到该页的物理地址,并安装进页表    *pte = page2pa(pp) | PTE_P | perm;    return 0;}

MIT6.828——Lab2(麻省理工操作系统实验)

继续完善`mem_init()

void
mem_init(void)
{
    /* ... ... */

    check_page_free_list(1);
    check_page_alloc();
    check_page();

    // pa:PADDR(pages)---->va:UPAGES  size=PTSIZE
    boot_map_region(kern_pgdir, UPAGES, PTSIZE, PADDR(pages), PTE_U);

    // pa:PADDR(bootstack)---->va:KSTACKTOP - KSTKSIZE  size=KSTKSIZE
    boot_map_region(kern_pgdir, KSTACKTOP - KSTKSIZE, KSTKSIZE, PADDR(bootstack), PTE_W);

    // pa:0---->va:KERNBASE  size=0xffffffff - KERNBASE
    boot_map_region(kern_pgdir, KERNBASE, 0xffffffff - KERNBASE, 0, PTE_W);

    // Check that the initial page directory has been set up correctly.
    check_kern_pgdir();

    /* ... ... */
}

现在可以来一段总结了

MIT6.828——Lab2(麻省理工操作系统实验)

这便是JOS目前建立起来的内存映射了,左侧是物理地址空间,右边是虚拟地址空间。比如说UVPT,在代码中有这样一段

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

PDX(UVPT)=1110 1111 01因此地址区间 0xef400000~0xef7fffff共计4MB被映射到 PADDR(kern_pgdir)处。而正如JOS一开始所说,只会使用256MB的内存,映射关系也满足。

总结

  1. 内存映射这块,需要好好地阅读代码,文章中没有详细地列出JOS内存布局,虚拟内存的布局在 memlayout.h
  2. 为了更好地理解这部分,需要熟悉保护模式分页模式下地寻址Lab2内存管理准备知识
  3. 要区分好物理地址和虚拟地址,页表,页目录这些里面装地内容是什么
  4. 要有一个内存模型总体上的概念

Original: https://www.cnblogs.com/oasisyang/p/15495908.html
Author: OasisYang
Title: MIT6.828——Lab2(麻省理工操作系统实验)

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/524405/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • 【操作系统真象还原】04 编写MBR分区(二)和显卡对话

    前言 通过BIOS提供的中断,我们的MBR程序在屏幕上输出了绿油油的 Hi from MBR!。但只有在 &#x5B9E;&#x6A21;&#x5F0F; …

    Linux 2023年5月27日
    0140
  • linux学习之搭建Apache 服务器

    本实验的主要任务是在CentOS操作系统中搭建Apache 服务器,练习文档根目录、首页文件、相关访间控制规则的配置。 ‎【实验目的】 ‎(1) 理解Apache 服务器主配置文件…

    Linux 2023年6月13日
    0105
  • 【MQTT】iniparser库的安装和使用

    iniparser库 * – iniparser库介绍 – 下载库 – iniparser中的API – dictionary中的一…

    Linux 2023年6月13日
    0105
  • Redis功能拓展-消息队列

    1.什么是消息队列,消息队列解决什么问题?从宏观上看,消息队列就是围绕队列这个数据结构而拓展开的一段特殊程序,将这类程序单独部署就可以称之为消息中间件(也称:消息队列)。在分布式系…

    Linux 2023年6月7日
    0109
  • Linux系统解压zip包出现中文乱码问题

    404. 抱歉,您访问的资源不存在。 可能是网址有误,或者对应的内容被删除,或者处于私有状态。 代码改变世界,联系邮箱 contact@cnblogs.com 园子的商业化努力-困…

    Linux 2023年6月7日
    0104
  • zabbix自定义监控(当会话登录超过三个就报警)

    安装过程在此省略。 1.agent端去修改配置文件 2.调用自定义内容 vim /etc/zabbix/zabbix_agentd.d/login.conf UserParamet…

    Linux 2023年6月6日
    091
  • bash shell相关知识

    shell与bash 什么是shell ——以上图片摘自《鸟哥的Linux私房菜》 系统核心不能随意地被操作,所以就设计出了壳程序shell,一方面保护了系统核心,另一方面提供了人…

    Linux 2023年6月7日
    0121
  • Docker基础知识

    Docker 是什么 Docker 经常被提起的特点: Docker 技术的基础: Docker 组件: Docker 安装 Docker 常见命令 容器相关操作 获取容器相关信息…

    Linux 2023年6月7日
    0110
  • Apache Solr Velocity 注入远程命令执行漏洞 (CVE-2019-17558)

    一、Apache Solr介绍 Solr是一个独立的企业级搜索应用服务器,它对外提供类似于web-service的API接口,用户可以通过http请求,向搜索引擎服务器提交一定格式…

    Linux 2023年6月13日
    098
  • Shell脚本完成IOS平台下的多目录和多架构编译(调用Makefile一起完成)

    博客园 :当前访问的博文已被密码保护 请输入阅读密码: Original: https://www.cnblogs.com/cy568searchx/p/5735429.htmlA…

    Linux 2023年5月28日
    0122
  • Python subprocess的使用

    前言 部门内部存在大量代码使用Python去调用Shell或者JS脚本,因此重度依赖subprocess(使用Google的subprocess32),在使用subprocess的…

    Linux 2023年6月7日
    085
  • [转帖]shell中if语句的使用

    404. 抱歉,您访问的资源不存在。 可能是网址有误,或者对应的内容被删除,或者处于私有状态。 代码改变世界,联系邮箱 contact@cnblogs.com 园子的商业化努力-困…

    Linux 2023年5月28日
    0111
  • 进程

    理论知识 操作系统背景知识 顾名思义,进程即正在执行的一个过程。进程是对正在运行程序的一个抽象。 进程的概念起源于操作系统,是操作系统最核心的概念,也是操作系统提供的最古老也是最重…

    Linux 2023年6月14日
    097
  • [ Calibre ] 利用 Calibre LVS 检查网表正确性的最小 rule

    利用 Calibre LVS 检查网表正确性的最小 rule https://www.cnblogs.com/yeungchie/ 顶层验证前先检查网表,再跑完整流程。 check…

    Linux 2023年6月7日
    096
  • Django中自定义管理器Manager用法

    Django中Manager用法 第一种 class create_user(models.Manager): def create(self, name, sex, age): …

    Linux 2023年6月14日
    096
  • AWS修改RDS时区

    查看 RDS 当前时区 默认情况下,AWS 的 RDS 采用的是 UTC 时间。而我们地区一般位于东八区,因此我们本地的时间是 UTC+8。 连接到 RDS 上,查询当前实例的时区…

    Linux 2023年6月7日
    0103
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球