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是内存管理单元,负责内存翻译(即把逻辑地址翻译为物理地址),也是单独存在。
- 页表位于主存中,是主存的一部分。
访存过程:
- CPU提供的是虚拟地址,需要转换为物理地址后才能得到数据实际存放位置。
- 虚页号和实页号的对应关系在页表中(页表存在于主存中),因此需要先查到页表。
- CPU给出虚拟地址,TLB接受地址后查询是否有对应的页表项。
- 若TLB不命中,再查页表。
- 如果页表还不命中,说明页面还未掉入主存,需发出缺页中断,从辅存中调入并更新页表和TLB。
- 得到物理地址后,访问Cache-主存体系来操作。
- 先访问Cache,如果命中,直接访问
- 反之,调入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)
-
先对elm进行初始化,令
LIST_NEXT(elm) = LIST_NEXT(listelm)。 -
判断要插入的元素后是否为空,如果不是,则建立联系
-
补充elm和listelm的前后结点。
示意图如下:
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rZoxucwe-1618236338952)(H:\2rd-2rdsemi\OS\lab2\LIST_INSERT_AFTER.jpg)]]()
#define LIST_INSERT_TAIL(head,elm,field)
- 同样需要判断头结点是否为空;
- 进行遍历操作,找到尾结点;
- 插入。
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上的图)

(或者自己乱七八糟画的图)

进一步看,怎么计算页目录的地址?
$$
给定页表基址PT_{base},
设页目录基地址PD_{base}、
自映射页目录项地址为PDE,可知:\
\
PD_{base} = PT_{base} | PT_{base}>>10\
\
PDE = PT_{base} | PT_{base} >> 10 | PT_{base} >> 20
$$
解释:
- 因为1M个页表项和4G的地址空间是线性映射(相差2^12倍),因此页目录地址(简称PD)对应的应该是第
PT>>12个页表项,也就是第一个页目录项。而一个页目录项32位,4字节,因此该项相对于起始地址的偏移为(PT>>12)<<2 = PT>>10,因此PD = PT | PT>>10 - 同理,
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)
解释:
-
PDX(va):由上述对32位虚拟地址介绍可知,va右移22位后低10位是页目录索引,剩余高位都是0,再与0x03FF并是为了确保高位为0。
-
PTX(va)va右移12位后,低10位是页表索引,11-20位是页目录索引,与0x03FF并可以使得11-32位都为0,从而得到页目录索引。
PADDR & KADDR
-
PADDA虚拟地址->物理地址,所做操作是将最高位清零
-
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即可。

![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rZoxucwe-1618236338952)(H:\2rd-2rdsemi\OS\lab2\LIST_INSERT_AFTER.jpg)]](https://img-blog.csdnimg.cn/20210412220708138.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NjM5OTU2Nw==,size_16,color_FFFFFF,t_70#pic_center)
浙公网安备 33010602011771号