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)

大家都在看

  • 002 Linux 文件与目录命令的必会姿势!

    文件及目录的路径切换、显示、创建、复制、移动和删除操作的常用姿势,必会!因为这些命令是使用 Linux 系统进行工作的基础,是摆脱小白的第一步,是构建大厦的基石!发现锅锅真是个话痨…

    Linux 2023年5月27日
    079
  • Redis 基础

    Redis 基础 Redis 定位 – 特性 关系型数据库 特性 非关系型数据库 特性 Redis 特性 Redis 安装 – 启动 – 使用 …

    Linux 2023年6月13日
    0134
  • 一篇文章剖析设计模式中的简单工厂、工厂方法和抽象工厂

    前言 大部分的面试者在IT行业面试中,提及设计模式,可以列举一大堆,但是面试官要求细说的时候,往往部分基础不够牢固的同学只能提及简单工厂。今天我们来对面试过程中最常见的简单工厂、工…

    Linux 2023年6月13日
    093
  • PHP PDF转图片

    Windows环境下 一、开启 Imagick 扩展 1、安装PHP扩展:Imagick,下载地址 https://pecl.php.net/package/imagick 注意和…

    Linux 2023年6月7日
    093
  • 闭包、装饰器

    闭包: 闭包的演变过程: 闭包的概念: “闭包”的本质就是函数的嵌套定义,即在函数内部再定义函数 “闭包”有两种不同的方式,第一种是…

    Linux 2023年6月8日
    073
  • C++的vector的使用方法

    vector c++的vector的使用方法,创建,初始化,插入,删除等。 #include "ex_vector.h" #include #include #…

    Linux 2023年6月14日
    086
  • 4-初识Django Admin

    初识Django Admin Django Admin是Django为我们提供的网站后台管理应用,通常网站,个人博客,CMS等都会有个后台管理界面,这个界面只有管理员权限的用户才能…

    Linux 2023年6月7日
    085
  • Snap Build Your Own Blocks输入中文解决办法

    Snap Build Your Own Blocks 输入中文解决办法 Snap! (formerly BYOB) is a visual, drag-and-drop progr…

    Linux 2023年6月6日
    085
  • idea 运行 tyarn 命令提示系统禁止运行脚本

    无法加载文件D:……….(报错信息。。。),因为在此系统上禁止运行脚本,有关详细信息,请参阅 https:/go.microsoft.com/f…

    Linux 2023年6月13日
    081
  • 正则表达式

    1.正则表达式分类 正则表达式:REGEXP,REGular EXPression。正则表达式分为两类: Basic REGEXP(基本正则表达式) Extended REGEXP…

    Linux 2023年6月6日
    087
  • POJ1861(Network)-Kruskal

    题目在这 Sample Input 4 6 1 2 1 1 3 1 1 4 2 2 3 1 3 4 1 2 4 1 Sample Output 1 4 1 2 1 3 2 3 3 …

    Linux 2023年6月7日
    075
  • centos7中防火墙转为iptables

    1、关闭firewall systemctl stop firewalld.service #停止firewall systemctl disable firewalld.serv…

    Linux 2023年6月6日
    078
  • python异常处理

    关于异常 在程序运行中,总会遇到各种各样的错误,如打开一个不存在的文件,程序期待用户输入数字,但用户输入了字符串,网络传输终止等,如果不对这些可能引发异常的情况进行处理,就会导致抛…

    Linux 2023年6月7日
    0156
  • 《Redis开发与运维》——(六)Redis复制(脑图)

    posted @2021-01-09 15:05 雪山上的蒲公英 阅读(91 ) 评论() 编辑 / 返回顶部代码 / Original: https://www.cnblogs….

    Linux 2023年5月28日
    067
  • [ Perl ] 多线程 并发编程

    记录一些常用的 模块 / 方法 。 多线程 use 5.010; use threads; 定义一个需要并发的子函数 sub func { my $id = shift; slee…

    Linux 2023年6月7日
    081
  • typesafe_cb

    callback 回调函数 什么是callback function 如图(来自维基百科),回调函数提供了一种服务,可以由用户决定使用怎么样的服务(登记回调函数)。回调函数机制,提…

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