【计算机组成与体系结构Ⅱ】虚拟存储器以及虚拟变换(实验)

实验7:虚拟存储器以及虚拟变换

一、实验目的

1:加深对虚拟存储器基本概念、基本组织结构以及基本工作原理的理解。

2:掌握页式、段式,段页式存储的原理以及地址变换的方法。

3:理解LRU与随机替换的基本思想。

二、实验平台

    在Dev-C++软件上,运行或修改VA-Converting.cpp文件。

三、实验内容与步骤

3.1 学习虚拟地址变换的基本操作,了解基本工作原理


1:启动虚拟地址变化模拟器。

【计算机组成与体系结构Ⅱ】虚拟存储器以及虚拟变换(实验)_第1张图片


2:运行程序,设计测试地址,思考模拟器地址变化的原理。

【计算机组成与体系结构Ⅱ】虚拟存储器以及虚拟变换(实验)_第2张图片

在上图中,测试的逻辑地址是300。物理地址的计算公式为:逻辑地址的块号 * 页面大小 + 页内地址。其中,逻辑地址块号的计算公式为:逻辑地址块号 = 逻辑地址 / 页面大小。页内地址的计算公式为:页内地址 = 逻辑地址 % 页面大小。

综上所述,模拟器地址变化主要依靠逻辑地址和物理地址之间的映射。

3:思考物理地址和虚拟地址空间大小。


    由下图打印的信息可知,pa对应虚拟地址的页号,d对应虚拟地址的页内地址,n对应所映射的物理地址的页号,m对应所映射的物理地址。

【计算机组成与体系结构Ⅱ】虚拟存储器以及虚拟变换(实验)_第3张图片


物理地址的空间大小 = 物理页号最大值 * 页面大小 + 最大页面地址。其中,物理页号的最大值为25,页面大小为1KB(1024B),最大页面地址为1023B(页面大小减去1)。

【计算机组成与体系结构Ⅱ】虚拟存储器以及虚拟变换(实验)_第4张图片


虚拟地址的空间大小 = 虚拟页号最大值 * 页面大小 + 最大页面地址。其中,虚拟页号的最大值为1024(即字母l对应的大小),页面大小为1KB(1024B),最大页面地址为1023B(页面大小减去1)。

【计算机组成与体系结构Ⅱ】虚拟存储器以及虚拟变换(实验)_第5张图片

3.2 打开源代码

1:分析示例程序的源码中,页表用什么数据结构实现的?替换算法用的什么?

页面是用结构体搭建的哈希表实现的。Page中包含一个虚拟地址的页号和一个所映射的物理地
址页号,且总共设置了15个虚拟页。

【计算机组成与体系结构Ⅱ】虚拟存储器以及虚拟变换(实验)_第6张图片

替换算法采用了LRU算法,是用栈实现的。

2:分析虚拟变换过程是如何实现的,替换算法是如何实现的?


虚拟变化过程的实现:利用页表进行实现。首先对用户输入的逻辑地址进行分解和转换,分别得到虚拟页号pa、页内地址d。再利用locate函数,通过传入的虚拟页号得到物理页号。

【计算机组成与体系结构Ⅱ】虚拟存储器以及虚拟变换(实验)_第7张图片

 其中,locate函数的实现如下所示。首先遍历所有虚拟页,对比当前位置虚拟页号和函数传入的虚拟页号是否相等。如果相等,则返回结构体中所保存的物理页号,否则继续下一次遍历。如果最终没有对应的物理页号,则返回-1,表示函数传入的虚拟页号和当前系统中的物理页号没有映射关系。

【计算机组成与体系结构Ⅱ】虚拟存储器以及虚拟变换(实验)_第8张图片

替换算法的实现:利用栈进行实现。如果栈内是非满状态的话(即存在空闲的页表),首先判断该页是否在栈内,如果不在栈内则进行入栈操作,如果在栈内则调用其位置于栈顶(即最近最常访问内容的位置往前靠)。如果栈内是满状态的话(即不存在空闲的页表),首先判断该页是否在栈内,如果不在栈内则进行入栈操作,并将栈底的内容弹出(即替换掉最近最不常访问的内容),如果在栈内则调用其位置于栈顶(即最近最常访问内容的位置往前靠)。

【计算机组成与体系结构Ⅱ】虚拟存储器以及虚拟变换(实验)_第9张图片

【计算机组成与体系结构Ⅱ】虚拟存储器以及虚拟变换(实验)_第10张图片

3:分析cache的大小,cache的地址变换用什么数据结构实现?

本程序中没有设置cache。

3.3 自行设计地址变换模拟器

1:用C、C++或者熟悉任意语言编程实现虚拟地址变换过程。

2:虚拟地址32位,物理地址16位,每页大小2K、Cache大小2K、直接映像、块大小32B。

3:输入采用16进制(或10进制)的地址,通过随机方法进行替换。(可自行设计一个初始的页表数据)

4:采用一级页表方式。

5:运行需要输出虚拟地址变换后的物理地址,以及Cache块的索引和块内地址、是否命中。

【分析】

1:页式结构分析

页面大小2K = 2^11

虚拟地址32位,空间大小为2^32,页面数量为2^32 / 2^11 = 2^21个

物理地址16位,空间大小为2^16,分块数量为2^16 / 2^11 = 2^5 = 32个

2:Cache结构分析

Cache大小2K = 2^11,块大小32B = 2^5,Cache块数为2^11 / 2^5 = 2^6 = 64块

Cache是直接映像,每组对应1块,所以Cache的行数和组数都是64

3:地址映射分析

页面号 = 虚拟地址 / 页面大小

页内地址 = 虚拟地址 % 页面大小

主存块号:计算出的页面号在页表内进行一一映射

物理地址 = 主存块号 * 页面大小 + 页内地址

4:Cache命中分析

Cache索引 = 物理地址 / Cache块大小 % Cache块数

Cache块内地址 = 物理地址 % Cache块大小

其中,Cache块大小 = 32B

【测试用例】


1:执行随机页面置换算法

【计算机组成与体系结构Ⅱ】虚拟存储器以及虚拟变换(实验)_第11张图片

 使用【功能1】,输入页面总数为10,页面号从0~9随机输入(此处的序列为0、1、2、3、6、5、4、7、9、8)。当存储页面的栈处于满状态时,栈内执行随机替换算法,即新插入的页面替换掉栈中的某页面。最终的结果如上图所示。


2:地址变换

【计算机组成与体系结构Ⅱ】虚拟存储器以及虚拟变换(实验)_第12张图片


使用【功能2】,输入逻辑地址为300。程序通过计算逻辑页面和页内地址,将逻辑页面在页表中找到其对应的物理块号后,可以计算出逻辑地址所映射的物理地址为2048 * 7 + 300 = 14636。Cache再对物理地址进行检索,本测试中Cache命中,对应的Cache行是14636 / 32 % 64 = 9,块内地址是14636 % 32 = 12。最终的结果如上图所示。

【计算机组成与体系结构Ⅱ】虚拟存储器以及虚拟变换(实验)_第13张图片

当用户输入的逻辑地址过大时,程序会提示【地址越界】,结果如上图所示。


3:退出程序系统

【计算机组成与体系结构Ⅱ】虚拟存储器以及虚拟变换(实验)_第14张图片

 使用【功能3】,打印“结束使用!”字样,程序运行完毕。结果如上图所示。

【源代码】

import random

# 栈类,用于管理页面号的栈

class SqStack:

    def __init__(self):

        self.stack = []  # 栈的存储结构

        self.max_size = 5  # 栈的最大容量!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

       

    def push(self, item):

        # 将一个元素推入栈中

        if len(self.stack) < self.max_size:

            self.stack.append(item)

    def is_full(self):

        # 判断栈是否已满

        return len(self.stack) == self.max_size

    def is_empty(self):

        # 判断栈是否为空

        return len(self.stack) == 0

    def find(self, item):

        # 查找栈中是否含有某个元素

        return item in self.stack

    def print_stack(self):

        # 打印栈的内容

        print(self.stack)

# 页面类,用于管理页面和内存

class Page:

    def __init__(self):

        self.page_table = {}  # 页面表

        self.page_len = 65536  # 页面总数

        self.l = 2048  # 页面大小

        # 初始化页面表

        for i in range(self.page_len):

            j = random.randint(0, 10)

            self.page_table[i] = i + j

    def locate(self, n):

        # 根据页面号查找对应的块号

        return self.page_table.get(n, -1)

# 地址转换函数

def address_translation(page, address):

    # 计算页面号和偏移量

    page_number = address // page.l

    if not (0 <= page_number < page.page_len):

        print("地址越界")

        return -1

    offset = address % page.l

    # 获取块号并计算物理地址

    block_number = page.locate(page_number)

    if block_number != -1:

        physical_address = block_number * page.l + offset

        print(f"页号: {page_number}, 块号: {block_number}, 偏移量: {offset}")

        print(f"物理地址 = {physical_address}")

        return physical_address

    else:

        print("此地址没有对应的条目")

        return -1

# 随机页面置换函数

def random_page_replacement(stack, page_number):

    # 若栈未满且页面号不存在于栈中,则入栈

    if not stack.is_full():

        if not stack.find(page_number):

            stack.push(page_number)

            return True

    # 若栈已满且页面号不存在于栈中,则随机替换一个元素

    else:

        if not stack.find(page_number):

            replace_index = random.randint(0, stack.max_size - 1)

            stack.stack[replace_index] = page_number

            return True

    return False

def main():

    page = Page()

    stack = SqStack()

    cache = [None] * 64  # 初始化缓存

    cache_v = 32

    while True:

        print("**********菜单***********")

        print("1 - 执行随机页面置换算法")

        print("2 - 地址变换")

        print("3 - 退出")

        print("*************************")

        choice = input("请输入菜单号:")

        if choice == "1":

            while True:

                try:

                    n = int(input("请输入页面数:"))

                    if n > 100:

                        print("输入的数值太大!!")

                        continue

                    break

                except ValueError:

                    print("请输入一个有效的整数!")

            page_sequence = []

            for _ in range(n):

                while True:

                    try:

                        page_number = int(input("请输入页面号 (0~~9)"))

                        if 0 <= page_number <= 9:

                            page_sequence.append(page_number)

                            break

                        else:

                            print("请输入一个介于 0 9 之间的整数!")

                    except ValueError:

                        print("请输入一个有效的整数!")

            for page_number in page_sequence:

                random_page_replacement(stack, page_number)

                stack.print_stack()

        elif choice == "2":

            # 执行地址变换

            print("页面大小是 2kb")

            print("物理地址 = 其所对块号 * 页面大小 + 页内地址")

            adr = int(input("请输入逻辑地址:"))

            physical_address = address_translation(page, adr)

            if physical_address != -1:

                cache_index = physical_address // cache_v % len(cache)

                offset = physical_address % cache_v

                cache[cache_index] = physical_address // cache_v

                print(f"物理地址 {physical_address} 对应的 cache 索引为:{cache_index}")

                print(f"物理地址 {physical_address} 对应的 cache 块内地址为:{offset}")

        elif choice == "3":

            # 退出程序

            print("结束使用!")

            break

if __name__ == "__main__":

    main()

3.4 思考问题并简要回答

1:如果替换算法改为FIFO,需要对哪些数据结构以及算法进行改进?

  1. 数据结构:使用队列或列表,用于追踪页面的加载顺序。
  2. 算法改进:修改页面置换函数,使其按照 FIFO 的逻辑运行。即当缓存满的时候,移除最早加载的页面。

2:如果采用TLB快表,则需如何改进方案?

TLB快表用于加速虚拟地址到物理地址的转换过程。

  1. 数据结构:使用数组或列表,用于存储最近的地址转换。
  2. 算法改进:在进行地址转换之前,先在 TLB 快表中查找。如果在快表中找到该地址,则直接使用;如果在快表中没有找到该地址,进行常规地址转换,并将结果存入快表中。

3:如果虚拟地址大小、物理地址大小、虚页大小、cache大小、cache块大小、相联度等参数可以通过用户输入的情况,需要对哪些部分进行改进?

  1. 用户输入改进:设置允许用户输入各种参数的模块,并设置相应的变量进行保存,以便于函数调用时进行传参操作。
  2. 数据结构调整:
    (1)页面和页大小:根据用户输入的虚拟页大小和物理地址大小调整页面的数据结构。
    (2)Cache 结构:基于用户输入的 cache 大小和块大小调整 cache 的数据结构。如果实现了相联 cache(直接映射、全相联、组相联),则需要根据相联度来组织 cache 的存储方式。
  3. 算法调整:
    (1)地址转换:修改地址转换逻辑以适应新的虚拟地址和物理地址大小。计算页号和偏移量的方式需要根据新的页面大小进行调整。
    (2)Cache 管理:根据新的 cache 参数调整 cache 的管理策略,例如数据的存储、检索、替换等。
    (3)页面置换算法:页面置换算法需要根据新的页面大小进行调整,例如使用FIFO、LRU、随机替换算法等。
  4. 参考代码如下所示:

def get_user_input():

    virtual_address_size = int(input("请输入虚拟地址大小:"))

    physical_address_size = int(input("请输入物理地址大小:"))

    page_size = int(input("请输入虚页大小:"))

    cache_size = int(input("请输入cache大小:"))

    cache_block_size = int(input("请输入cache块大小:"))

    associativity = int(input("请输入相联度:"))

    # ... 其他参数

    # 进行参数验证和处理

    return (virtual_address_size, physical_address_size, page_size,

            cache_size, cache_block_size, associativity)

# 在程序的主要部分中调用此函数

def main():

    params = get_user_input()

    # 根据输入的参数设置数据结构和算法

    # ...

if __name__ == "__main__":

    main()

4:段式和页式的区别,如果改成段式存储,则如何实现段式首地址和偏移量?

段式和页式的区别:

页式存储:页机械地划分为大小相同的块。页式管理是以定长页面进行存储管理的方式。

【计算机组成与体系结构Ⅱ】虚拟存储器以及虚拟变换(实验)_第15张图片


段式存储:段会按程序逻辑划分成的相对独立可变长的块。段式管理是把主存按段分配的存储管理方式。

【计算机组成与体系结构Ⅱ】虚拟存储器以及虚拟变换(实验)_第16张图片

整体区别如下图所示。

【计算机组成与体系结构Ⅱ】虚拟存储器以及虚拟变换(实验)_第17张图片

如果改为段式存储,如何实现段式首地址和偏移量:

  1. 段表的构建:
    段表可以用数组或者列表实现,数组的索引或列表的位置可以用作段号。
    每个表项 = 段基址 + 段长度。
    (1)段基址:段在内存中的起始物理地址。
    (2)段长度:段在内存中的长度。
  2. 地址转换:
    虚拟地址由两部分组成:段号和段内偏移量。
    地址转换过程需要三个步骤,分别是:定位段、检查越界、计算物理地址。
    (1)定位段:使用段号从段表中查找到相应的段基址和段长度。
    (2)检查越界:检查偏移量是否超过了段长度。
    (3)计算物理地址:将段基址和偏移量相加,得到物理地址。
  3. 参考代码如下所示:

class Segment:

    def __init__(self, base, length):

        self.base = base

        self.length = length

def create_segment_table():

    # 示例:创建包含几个段的段表

    return [Segment(1000, 300), Segment(2000, 400), Segment(3000, 500)]

def translate_address(segment_table, segment_number, offset):

    if segment_number >= len(segment_table):

        raise ValueError("无效的段号")

   

    segment = segment_table[segment_number]

    if offset >= segment.length:

        raise ValueError("偏移量越界")

    return segment.base + offset

# 示例使用

segment_table = create_segment_table()

physical_address = translate_address(segment_table, 1, 50)  # 段号1,偏移量50

print("物理地址:", physical_address)

四、实验总结

1:虚拟存储器是存储器的逻辑模型,借助于磁盘等辅助存储器来扩大主存容量,为更大或更多的程序所使用。

2:物理地址由CPU地址引脚送出,用于访问主存的地址。

3:虚拟地址由编译程序生成,是程序的逻辑地址,其地址空间的大小受到辅助存储器容量的限制。

4:【主存-外存层次】和【cache-主存层次】用的地址变换映射方法和替换策略是相同的,都基于程序局部性原理(时间局部性 + 空间局部性)。


5:虚拟存储器的实现,基于基本信息传送单位、替换算法、地址映射、一致性问题。

【计算机组成与体系结构Ⅱ】虚拟存储器以及虚拟变换(实验)_第18张图片

6:替换算法主要分为以下几种:
    (1)LRU:近期最少使用算法。当需要替换一个页面时,选择最近最久未被使用的页面进行替换。它维护一个页面访问历史记录,并将最近被访问的页面置于队列的前面,而最久未被访问的页面在队列的末尾。当需要替换页面时,选择队列末尾的页面进行替换。
    (2)LFU:最不经常使用算法。当需要替换一个页面时,选择被访问次数最少的页面进行替换。它维护一个计数器来跟踪每个页面的访问次数,并选择访问次数最少的页面进行替换。
    (3)FIFO:先进先出算法。最早进入内存的页面会被最早替换出去。它使用一个队列来维护内存中的页面顺序,当需要替换页面时,选择队列中的最早进入的页面进行替换。
    (4)随机替换算法。当需要替换一个页面时,随机选择集合中的一个页面进行替换。
    (5)LFU + FIFO。

7:有效的页面置换算法可以显著减少缺页中断,提高系统效率。

五、代码修改

基于VA-Converting.cpp代码进行修改,实现【自行设计地址变换模拟器】。

#include "stdio.h"

#include "math.h"

#include"malloc.h"

#include "stdlib.h"

#include

#include "math.h"

#define OK 1

#define OVERFLOW -1

#define ERROR -1

#define Max 5

typedef int status;

typedef int SElemType;

using namespace std;

int k=0;//记录缺页次数

/*--------------------栈及其操作---------------------*/

typedef struct {

    SElemType  *base; //栈底指针  

    SElemType   *top;  //栈顶指针

    int  count;    //栈的大小

}SqStack;

//构造空栈

status InitStack (SqStack &S){  

    S.base=(SElemType *)malloc(Max * sizeof(SElemType));

    if (!S.base)  return(OVERFLOW);

    S.count = 0;

    S.top = S.base;

    return(OK);

}

//入栈

status Push(SqStack &s,SElemType e)

{

    *s.top++=e;

    s.count++;

//  cout<<"插入"<

    return OK;

}

//销毁栈

status DestroyStack(SqStack &S)

{

   

    S.top=NULL;

    S.base=NULL;

    delete[] S.base;

    S.count=0;

    return OK;

}

//判断栈是否为空

bool EmptyStack(SqStack s)

{

    if(s.count==0) return true;

    else          return false;

}

//是否已满

bool full(SqStack s)

{

    if(s.count==5) return true;

    return false;

}

//判断是否已经存在

int equeal(SqStack s,SElemType e)

{

    int num=s.count;

    if(EmptyStack(s)) return -1;

    for (int i=1;i<=num;i++)

    {

       if(*(s.top-i)==e)

           return i;

    }

    return -1;

}

//输出

void print(SqStack s){

    int a,i,num=s.count;

    //cout<<"有"<

    for (i=0;i

    {

       a=*(s.base+i);

       cout<

    }

    cout<

}

/*----------------页表---------------------*/

int pageLen=65536;//页面数

int l=2048;  //页面大小

struct page{

    int pageNum;

    int memNum;

}p[70000];

int initiate(){

    int i,j;

    for (i=0;i

    {  

       j=rand()%11;

       p[i].pageNum=i;

       p[i].memNum=i+j;

    }

    return 1;

}

int locate(int n){

    for (int i=0;i

    {

       if (p[i].pageNum==n)

       {

           return p[i].memNum;

       }

    }

    return -1;

}

//物理地址

int adress(){

    int pa,d,adr;

    cout<<"请输入逻辑地址:";

    cin>>adr;

    cout<

    pa=(int)adr/l;

    if (pa<0||pa>=l)

    {

       cout<<"越界"<

       return -1;

    }

    d=adr%l;

    int n=locate(pa);

    if (n!=-1)

    {

       int m=n*l+d;

       cout<<"其对应的页号是:"<

       cout<<"其对应的页内地址是:"<

       cout<<"此页号地址对应的块号是:"<

       cout<<"物理地址="<

       cout<<"其物理地址为:"<

       return m;

    }

    else{

       cout<<"此地址无对应项"<

       return -1;

    }

}

//随机替换算法

int randReplace(SqStack& s, SElemType e){

    int i;

    int num=equeal(s, e);

    cout<<"访问页面"<

    if(!full(s)) {

        if (num==-1){

            cout<<",不存在此页号,"<

            k++;

            Push(s,e);

        }

    }

    else{

        if(num == -1){

            k++;

            cout<<",此页号不存在且栈满,随机替换栈中页面,并"<

            int randomIndex=rand()%Max;

            s.base[randomIndex]=e;

        }

    }

    return 1;

}

//执行随即替换算法

int begin(){

    int i,n,m;

    SElemType a[100];

    SqStack s;

    InitStack(s);

    cout<<"请输入页面数:";

    cin>>n;

    if (n>100){

       cout<<"输入的数值太大!!"<

       return 0;

    }

    cout<<"请输入页面号序列(0~~9):"<

    for(i=0;i

       cin>>m;

       if(m<0||m>9){

           cout<<"输入错误,请重新输入!!"<

           i--;

           continue;

       }

       a[i]=m;

    }

    for (i=0;i

    {

       randReplace(s,a[i]);

       cout<<"结果为:";

       print(s);

    }

    DestroyStack(s);

    cout<<"一共缺页"<

    return 1;

}

int Cachenum=64;

int CacheV=32;

int CACHE[65];

void cacheadress(int pad){

    int Index=pad/CacheV%Cachenum;

    int Offset=pad%CacheV;

    CACHE[Index]=pad/CacheV;

    cout<<"物理地址"<

    cout<<"物理地址"<

}

int CacheHit(int pad){

    for(int i=0;i

       if(CACHE[i]==pad/CacheV){

           return 1;

       }

    }

    return -1;

}

int x;

int main(){

    int ok=0;

    initiate();

    while (ok!=3){

       cout<<"                       **********菜单***********"<

       cout<<"                             1--执行随即变换算法"<

       cout<<"                             2--地址变换    "<

       cout<<"                             3--退出        "<

       cout<<"                       *************************"<

       cout<<"请输入菜单号:";

       cin>>ok;

       switch (ok){

           case 1:

              begin();

              break;

           case 2:

              cout<<"页面大小是2kb"<

              cout<<"物理地址=其所对块号*页面大小+页内地址"<

              x=adress();

              if(CacheHit(x)==1)  cout<<"cache成功命中"<

              else  cout<<"cache命中失败"<

              cacheadress(x);

              break;

           default:

              break;

       }

    }

    return 1;

}

你可能感兴趣的:(计算机组成与体系结构,体系结构,虚拟存储器)