BUAA-OS-lab2 MIPS操作系统之内存管理

OSlab2学习笔记:

笔者注:以下内容为笔者在学习BUAA_OS_lab2中遇到的诸多迷惑,以及对代码的理解,整理如下。为方便注释需要,可能包含部分源码()

学习重点

\include: queue.h pmaph mmu.h

\mm pmap.c

TLB、cache、MMU、页表等存在的位置是怎样的?有什么关系?(结合访存过程谈)

  • TLB是快表,一种寄存器,单独存在,不在CACHE也不在主存中。
  • cache是高速缓冲寄存器,位于CPU和内存之间。
  • MMU是内存管理单元,负责内存翻译(即把逻辑地址翻译为物理地址),也是单独存在。
  • 页表位于主存中,是主存的一部分。

访存过程:

  1. CPU提供的是虚拟地址,需要转换为物理地址后才能得到数据实际存放位置。
  2. 虚页号和实页号的对应关系在页表中(页表存在于主存中),因此需要先查到页表。
    1. CPU给出虚拟地址,TLB接受地址后查询是否有对应的页表项。
    2. 若TLB不命中,再查页表。
    3. 如果页表还不命中,说明页面还未掉入主存,需发出缺页中断,从辅存中调入并更新页表和TLB。
  3. 得到物理地址后,访问Cache-主存体系来操作。
    1. 先访问Cache,如果命中,直接访问
    2. 反之,调入Cache并访问。

什么时候用虚拟地址/物理地址?

在程序中使用的都是虚拟地址,而实际的数据都存储在物理地址中。

内存控制块

在MIPS CPU 中,地址转换以4KB 大小为单位,称为页。整个物理内存按4KB大小分成了许多页,我们大多数时候的内存分配,也是以页为单位来进行。为了记录分配情况,我们需要使用 Page 结构体来记录一页内存的相关信息:

需要重点理解这几个结构体的结构。

typedef LIST_ENTRY(Page) Page_LIST_entry_t;

struct Page {
    Page_LIST_entry_t pp_link;//|pp_link|是当前节点指向链表中下一个节点的指针
    u_short pp_ref;//|pp_ref|用来记录这一物理页面的引用次数
};

注意,Page结构体只是信息的载体,只代表相应物理内存页的信息,本身不是物理内存页。

LIST_ENTRY的结构:

#define LIST_ENTRY(type)
{
	struct{
		struct type *le_next;//下一个元素的指针
		struct type **le_prev;//上一个元素指向下一个元素的指针的地址,实现了**prev指向自己
	}
}

在 include/pmap.h 中,使用 LIST_HEAD 宏来定义了一个结构体类型 Page_list ,在 mm/pmap.c 中,创建了一个该类型的变量 page_free_list 来以链表的形式表示所有的空闲物理内存:

LIST_HEAD(Page_list, Page);

static struct Page_list page_free_list; //所有的空闲物理内存

Page_List的结构:

struct Page_list{
    struct {
         struct {
            struct Page *le_next;
            struct Page **le_prev;
        } pp_link;
        u_short pp_ref;
    }* lh_first;
}

queue.h中存储的是一系列宏函数来简化对链表的操作。

LIST_INSERT_AFTER(listelm, elm, field)LIST_INSERT_TAIL(head, elm, field)为例。

#define LIST_INSERT_AFTER(listelm,elm,field)

  1. 先对elm进行初始化,令LIST_NEXT(elm) = LIST_NEXT(listelm)

  2. 判断要插入的元素后是否为空,如果不是,则建立联系

  3. 补充elm和listelm的前后结点。

    示意图如下:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rZoxucwe-1618236338952)(H:\2rd-2rdsemi\OS\lab2\LIST_INSERT_AFTER.jpg)]

#define LIST_INSERT_TAIL(head,elm,field)

  1. 同样需要判断头结点是否为空;
  2. 进行遍历操作,找到尾结点;
  3. 插入。

alloc和page_alloc函数的区别在于:前者是按照字节分配内存,后者是以页为单位。

page2pa为什么可以得到物理地址

每个物理页面都有一个page类型的结构体表示,整个物理页面和page数组存在一一映射的关系,而每个物理页面的大小为4KB,所以通过page2ppn得到物理页号后,左移12位即可得到物理地址。

两级页表机制

二级页表机制示意:

在这里插入图片描述

  • 第一级表称为页目录,一共1024个页目录项,每个页目录项32位(4 Byte),页目录项存储的值为其对应的二级页表入口的物理地址(高20位存储页表的入口地址,低12位是一些杂七杂八的标志位)。整个页目录存放在一个页面(4KB)中。
  • 第二级表称为页表(page table),每一张页表有1024个页表项,每个页表 项32位(4 Byte),页表项存储的是对应页面的页框号(页面的物理地址,高20位)以及标志位(低12位)。每张页表占用一个页面大小 (4KB)的内存空间。

对于一个32位的虚存地址,其31-22位表示的是页目录项的索引,21-12位表示的是页表项的索引,11-0位表示的是该地址在该页面内的偏移。

在这里插入图片描述

页目录的自映射机制:

定义:页目录中有一条页目录项指向自身的物理地址。

图解:(扒的CSDN上的图

img

或者自己乱七八糟画的图

在这里插入图片描述

进一步看,怎么计算页目录的地址?
$$
给定页表基址PT_{base},
设页目录基地址PD_{base}、
自映射页目录项地址为PDE,可知:\
\
PD_{base} = PT_{base} | PT_{base}>>10\
\
PDE = PT_{base} | PT_{base} >> 10 | PT_{base} >> 20
$$
解释:

  1. 因为1M个页表项和4G的地址空间是线性映射(相差2^12倍),因此页目录地址(简称PD)对应的应该是第PT>>12个页表项,也就是第一个页目录项。而一个页目录项32位,4字节,因此该项相对于起始地址的偏移为(PT>>12)<<2 = PT>>10,因此PD = PT | PT>>10
  2. 同理,PT>>10是页目录在整个页表上的偏移,对应(PT>>10)>>10是自映射页目录项在页目录这张页表上的偏移。

boot_pgdir_walk( )函数

作用

建立页目录和页表的联系,不需要考虑页表里有没有东西。

PDX(va)和PTX(va)

#define PDX(va)  (((u_long)(va))>>22 & 0x03FF)
#define PTX(va)  (((u_long)(va))>>12 & 0x03FF)

解释:

  1. PDX(va)

    由上述对32位虚拟地址介绍可知,va右移22位后低10位是页目录索引,剩余高位都是0,再与0x03FF并是为了确保高位为0。

  2. PTX(va)

    va右移12位后,低10位是页表索引,11-20位是页目录索引,与0x03FF并可以使得11-32位都为0,从而得到页目录索引。

PADDR & KADDR

  1. PADDA

    虚拟地址->物理地址,所做操作是将最高位清零

  2. KADDR

    物理地址->虚拟地址,所做操作是将最高位置1

怎么由页目录项得到页表

页目录、页表、页三者的关系为:

  • 页目录表里面存放页目录表项,每个页目录表项指向页表。其中页目录表项的高20位为对应页表的物理地址的高20位。低12位为属性位。
  • 页表里面存放着页表项,每个页表项指向页。其中页表项的高20位为对应页的物理地址的高20位,低12为属性位。

因此PTE_ADDR函数的作用即将页目录项的低12位清零,得到对应页表的物理地址。

同时由于地址翻译是在内核态下完成的,因此用内核态的地址翻译方式,即用KADDR得到pgtable的虚拟地址。

如果页目录没有对应的页表,则需要新建,对应操作是:

  • 先用alloc函数给页表分配一块内存空间
  • 更改页目录项
    • 先将低12位标志位全部清零
    • 将高20位更改为当前页表的物理地址
    • 更改有效位PTE_V

最后返回页目录地址。

padir_walk( )函数

作用

新建一个Page结构来储存页表,并建立页目录与页表之间的联系。

创建

  • 前面没啥变化,后面分配内存时要先判断内存是否已满

    • 如果是,则返回-E_NO_MEM。
  • 注意,记得将新分配页面ppage的pp_ref++

  • 建立页表项和页的联系:pgtable = (Pte*)page2pa(ppage);//页表的物理地址

  • 建立页目录项和页表的联系

  • 最后把pgtable转化为虚拟地址:pgtable = (Pte *)KADDR((u_long)pgtable);//在程序中pgtable使用的还是虚拟地址,(*pgtable)存储的才是对应页的物理地址。

  • 返回对应页表项的地址

boot_map_segment( )函数

作用

实现将指定的物理内存和虚拟内存建立映射的功能。

page_insert( )函数

tlb_invalidate函数的作用是使得某一虚拟地址对应的tlb表项失效。从而下次访问这个地址的时候诱发tlb重填,以保证数据更新。

注:可能的优化

在函数开头调用pgdir_walk(pgdir, va, 1, &pgtable_entry),如果返回值是-E_NO_MEM,函数也直接返回-E_NO_MEM。

这样只需要调用一次pgdir_walk即可。

posted @ 2021-04-12 22:16  blurrrr  阅读(564)  评论(0)    收藏  举报