操作系统模拟实验——虚拟页式存储管理系统(先进先出FIFO、最少使用LRU、时钟置换CLOCK和随机置换)

一、实验目的

  1. 理解虚拟内存和物理内存之间的关系及转化,在什么情况下会发生缺页中断。

  2. 理解和掌握各种页面置换算法的工作原理,包括先进先出(FIFO)、最近最少使用(LRU)、时钟置换(CLOCK)和随机置换算法,并通过模拟实验观察它们的执行过程。

  3. 通过比较不同页面置换算法下系统的性能,理解不同算法的优缺点以及在何种情况下更适用。

  4. 学习如何评估系统性能。通过实验结果,理解如何通过缺页中断次数、平均访问时间等指标去评估系统性能,并能合理地解释实验结果。

  5. 从实验中体验和理解内存访问的局部性原理,提高自己对操作系统虚拟内存管理的理解。

二、实验要求

  1. 设计并实现一个模拟的简单虚拟页式存储管理系统。在该系统中,应能模拟以下功能:创建进程的页表,为进程页面分配内存块,实现逻辑地址向物理地址的转换。当发生缺页时,通过缺页中断处理调入页面,如果需要,可以进行页面置换。

  2. 制定一套模拟数据,包括要访问的页面、需执行的操作等,并按照虚拟页式存储管理的原则进行处理,展示每一步的执行结果。

  3. 能够模拟出页面置换的情况,例如FIFO、LRU、CLOCK和随机置换等算法,比较并分析每种算法下系统的性能。

三、实验代码

#include<iostream>
#include<cstdio>
#include<vector>
#include<string>
#include<queue>
#include<cmath>
#include<functional>
#include<stdlib.h>
#include<time.h>

using namespace std;

const int N = 15;//PCB上限个数
const int PAGE_SIZE = pow(2,12);//4096,页面大小设置为4KB
const int M = pow(2,2);//4,内存页面个数
//const int M = 3;
const int INF = 0x3f3f3f3f;//极大值

//进程操作类型
enum PcbOptionType{
    VISIT,//访问
    MODIFY,//修改
};

//定义进程操作的数据结构,将操作拆分为代表逻辑地址的指令和操作类型
struct PcbOption{
    string instruction;//指令(逻辑地址),长度为22位
    PcbOptionType option;//要执行的操作
};

//定义页表项的数据结构
struct PageTableItem{
    int frameNum = -1;//页帧(也称页框、内存块)号,初始状态为-1
    int status = 0;//状态位,表示该页面是否调入内存,status为1代表已调入,初始状态为0
    int exmemAddress = 0;//外存地址,保存页面在外存中的存放位置
    //访问字段,方便页面置换算法进行操作
    int selectedTime = INF;//页面被放入内存的时间
    int visitedTime = INF;//页面最近一次被访问的时间
    int visit = 0;//访问位,表示页面近期是否被访问,0代表近期未被访问
    int dirty = 0;//修改位,表示页面调入内存后是否被修改过,dirty为1代表已修改,初始状态为0

    //初始化函数
    PageTableItem()
    {
        frameNum = -1;
        status = 0;
        dirty = 0;
        exmemAddress = 0;
    }
};

//定义PCB的数据结构
struct PCB{
    int id;//进程编号
    vector<PcbOption> options;//进程操作序列
    vector<PageTableItem> pageTable;//储存在进程中的页表
}p[N];

//定义内存的数据结构,表示一个内存块
struct Memory{
    int value = 0;//内存块中是否存放数据,0代表内存块为空,1代表内存块存在数据,初始状态为0
    int pageNum = -1;//页号,代表当前内存块被进程的哪一个页面占用,-1代表内存块未被占用
    vector<int> memData;//模拟每个内存块中每个字节存放的数据,0代表没有数据,1代表有数据
}mem[M];

//FIFO算法优先队列比较函数
struct cmp
{
	bool operator()(const int &a, const int &b)//selectTime小的靠近队首
	{
		return p[0].pageTable[a].selectedTime > p[0].pageTable[b].selectedTime;
	}
}; 

int pcb_num = 1;//该系统默认只运行一个进程
function<int()> algorithmOption;//选择使用的替换算法
int missingCount = 0;//统计缺页中断次数
int replaceCount = 0;//统计置换次数
int t = 0;//保存页面加入内存的时间
int clockPoint = 0;//CLOCK算法用到的指针,保存指向的页帧号,初始为0

//地址转换函数,输入逻辑地址,输出物理地址
int LogicalToPhysical(string instruction, PcbOptionType option)
{
    //将二进制的指令转化为十进制
    int tmp = 0;
    for(int i = 0; i < instruction.size(); i++)
    {
        tmp <<= 1;
        if(instruction[i] == '1')
            tmp += 1;
    }
    //获得页号和页内偏移量
    int pageNum = tmp / PAGE_SIZE;
    int offset = tmp % PAGE_SIZE;
    //cout << "tmp:" << tmp << " " << pageNum << " " << offset << '\n';
    //判断页号是否越界
    if(pageNum >= p[0].pageTable.size())//页号大于等于页表项个数,则发生越界(内中断),输出报错信息
    {
        printf("指令%s发生越界,页号:%d,页表项个数:%d\n", instruction, pageNum, p[0].pageTable.size());
        return -1;
    }
    //查询页表
    if(p[0].pageTable[pageNum].status == 1)//如果该页面已调入内存,无需置换
    {
        printf("%d号页面已调入内存,访问命中\n", pageNum);
        p[0].pageTable[pageNum].visitedTime = t;
        p[0].pageTable[pageNum].visit = 1;
        return p[0].pageTable[pageNum].frameNum * PAGE_SIZE + offset;//返回物理地址
    }
    else//如果该页面未调入内存
    {
        bool flag = 0;//表示是否采用置换算法,0表示未采用
        missingCount++;//增加缺页次数
        //寻找内存中空的内存块
        int mem_id = -1;//内存中空的内存块的编号,-1代表没有空的内存块
        for(int i = 0; i < M; i++)
        {
            if(mem[i].value == 0)
            {
                mem_id = i;
                break;
            }
        }
        //cout << "mem_id_1:" << mem_id << '\n';
        if(mem_id == -1)//如果内存中没有空的内存块,采用置换算法获得内存块
        {
            flag = 1;
            mem_id = algorithmOption();
            replaceCount++;//增加置换次数
        }
        //cout << "mem_id_2:" << mem_id << '\n';
        if(!flag)
            printf("%d号页面未调入内存,发生缺页,将%d号页面放入空内存块%d\n", pageNum, pageNum, mem_id);
        else
            printf("%d号页面未调入内存,发生缺页,采用替换算法将%d号页面放入内存块%d,替换%d号页面\n",
                pageNum, pageNum, mem_id, mem[mem_id].pageNum);

        mem[mem_id].pageNum = pageNum;//更新页框所对应的页号
        mem[mem_id].value = 1;//页框被占用
        p[0].pageTable[pageNum].frameNum = mem_id;//将页表项中的页帧号更新为mem_id
        p[0].pageTable[pageNum].status = 1;//将页表项中的状态位更新为1
        //更新页表项中的访问字段
        p[0].pageTable[pageNum].selectedTime = t;
        p[0].pageTable[pageNum].visitedTime = t;
        p[0].pageTable[pageNum].visit = 1;//
        if(option == MODIFY)
            p[0].pageTable[pageNum].dirty = 1;

        return mem_id * PAGE_SIZE + offset;//返回物理地址
    }
}

//FIFO算法
int FIFO()
{
    priority_queue<int, vector<int>, cmp> q;//;以页面调入内存的先后顺序排成的队列,保存页号
    if(q.empty())//如果队列为空,则按照页面访问顺序查找在内存中的页面
        for(int i = 0; i < p[0].pageTable.size(); i++)
            if(p[0].pageTable[i].status == 1)
                q.push(i);
    int pageNum = q.top();//从队头取出要替换的页帧(最早进入内存的页面)所对应的页号
    q.pop();
    int frameNum = p[0].pageTable[pageNum].frameNum;//获得对应页帧号
    p[0].pageTable[pageNum].status = 0;//原来的页面移除内存
    q.push(pageNum);//再将页号放入队尾
    return frameNum;//返回要替换的页帧号
}

//LRU算法
int LRU()
{
    int minn = INF;
    int frameNum = -1;//要替换的页帧号
    int pageNum = -1;//要替换的页帧所对应的页号
    for(int i = 0; i < M; i++)
    {
        if(p[0].pageTable[mem[i].pageNum].visitedTime < minn)//如果该页面是最近最久未使用的页面,则替换
        {
            minn = p[0].pageTable[mem[i].pageNum].visitedTime;
            pageNum = mem[i].pageNum;//保存要替换的页帧所对应的页号
            frameNum = i;//保存要替换的页帧号
        }
    }
    p[0].pageTable[pageNum].status = 0;//原来的页面移除内存
    return frameNum;//返回要替换的页帧号
}

//CLOCK算法
int CLOCK()
{
    int frameNum = -1;//要替换的页帧号
    int pageNum = -1;//要替换的页帧所对应的页号
    printf("\n当前循环队列:\n");
    printf("%6s %8s %6s %6s\n", "页帧号", "对应页号", "访问位", "修改位");
    for(int k = 0; k < M; k++)//打印链式结构
    {
        if(k != 0)
        {
            printf("                                 |\n");
            printf("               |                 |\n");
            printf("               v                 |\n");
            printf("                                 |\n");
        }
        if(k == 0)
        {
            printf("%-6d %-8d %-6d %-6d<----\n", k, mem[k].pageNum, 
            p[0].pageTable[mem[k].pageNum].visit, p[0].pageTable[mem[k].pageNum].dirty);
        }
        else if(k == M - 1)
        {
            printf("%-6d %-8d %-6d %-6d-----\n", k, mem[k].pageNum,
            p[0].pageTable[mem[k].pageNum].visit, p[0].pageTable[mem[k].pageNum].dirty);
        }
        else
        {
            printf("%-6d %-8d %-6d %-6d    |\n", k, mem[k].pageNum,
            p[0].pageTable[mem[k].pageNum].visit, p[0].pageTable[mem[k].pageNum].dirty);
        }
    }
    printf("当前替换指针指向的页帧号:%d\n\n", clockPoint);
    for(int i = 1; i <= 4; i++)//从第一轮到第四轮
    {
        if(i == 1)//第一轮,查找第一个访问位和修改位均为0的页面,不修改任何标志位
        {
            for(int k = 0; k < M; k++)
            {
                int j = (clockPoint + k) % M;//扫描到的页帧号
                //如果该页面的访问位和修改位均为0,则替换
                if(p[0].pageTable[mem[j].pageNum].visit == 0 && p[0].pageTable[mem[j].pageNum].dirty == 0)
                {
                    pageNum = mem[j].pageNum;//保存要替换的页帧所对应的页号
                    frameNum = j;//保存要替换的页帧号
                    break;
                }
            }
        }
        else if(i == 2)//第二轮,查找第一个访问位为0,修改位为1的页面,将所有扫描过的帧访问位置为0
        {
            for(int k = 0; k < M; k++)
            {
                int j = (clockPoint + k) % M;//扫描到的页帧号
                //如果该页面的访问位为0,修改位为1,则替换
                if(p[0].pageTable[mem[j].pageNum].visit == 0 && p[0].pageTable[mem[j].pageNum].dirty == 1)
                {
                    pageNum = mem[j].pageNum;//保存要替换的页帧所对应的页号
                    frameNum = j;//保存要替换的页帧号
                    break;
                }
                p[0].pageTable[mem[j].pageNum].visit = 0;//将所有扫描过的帧访问位置为0
            }
        }
        else if(i == 3)//第三轮,查找第一个访问位和修改位均为0的页面,不修改任何标志位
        {
            for(int k = 0; k < M; k++)
            {
                int j = (clockPoint + k) % M;//扫描到的页帧号
                //如果该页面的访问位和修改位均为0,则替换
                if(p[0].pageTable[mem[j].pageNum].visit == 0 && p[0].pageTable[mem[j].pageNum].dirty == 0)
                {
                    pageNum = mem[j].pageNum;//保存要替换的页帧所对应的页号
                    frameNum = j;//保存要替换的页帧号
                    break;
                }
            }
        }
        else//第四轮,查找第一个访问位为0,修改位为1的页面,必然可以找到
        {
            for(int k = 0; k < M; k++)
            {
                int j = (clockPoint + k) % M;//扫描到的页帧号
                //如果该页面的访问位为0,修改位为1,则替换
                if(p[0].pageTable[mem[j].pageNum].visit == 0 && p[0].pageTable[mem[j].pageNum].dirty == 1)
                {
                    pageNum = mem[j].pageNum;//保存要替换的页帧所对应的页号
                    frameNum = j;//保存要替换的页帧号
                    break;
                }
            }
        }
        if(frameNum != -1)//如果找到了要替换的页帧,直接返回
        {
            p[0].pageTable[pageNum].status = 0;//原来的页面移除内存
            clockPoint = (frameNum + 1) % M;//指针指向下一个页帧
            return frameNum;//返回要替换的页帧号
        }
    }
}

//随机置换算法(RRA)
int RRA()
{
    int frameNum = -1;//要替换的页帧号
    int pageNum = -1;//要替换的页帧所对应的页号
    srand(time(NULL));
    frameNum = rand() % M;//页帧号取值区间为0到M-1
    pageNum = mem[frameNum].pageNum;//保存要替换的页帧所对应的页号
    p[0].pageTable[pageNum].status = 0;//原来的页面移除内存
    return frameNum;//返回要替换的页帧号
}

//输入模块
void Input()
{
    //初始化内存,每个内存块有PAGE_SIZE个字节
    for(int i = 0; i < M; i++)
        mem[i].memData.resize(PAGE_SIZE, 0);

    for(int i = 0; i < pcb_num; i++)
    {
        //初始化进程页表
        int pageTableLength;//页表长度
        cout << "请输入页表长度:" << '\n';
        cin >> pageTableLength;
        for(int j = 0; j < pageTableLength; j++)
        {
            PageTableItem pti;//每个页表项初始为默认值
            p[i].pageTable.push_back(pti);//将页表项放入页表
        }
        //初始化进程操作序列
        int optionNum;//操作数量
        cout << "请输入进程操作数量optionNum:" << '\n';
        cin >> optionNum;
        cout << "接下来optionNum行,请分别输入指令和操作类型:" << '\n';
        for(int j = 0; j < optionNum; j++)
        {
            string instruction;//指令
            int opType;//操作类型,0代表访问,1代表修改
            cin >> instruction >> opType;//输入指令和操作类型
            PcbOption po;
            po.instruction = instruction;
            if(opType == 0)
                po.option = VISIT;
            else
                po.option = MODIFY;
            p[i].options.push_back(po);//将该操作放入操作序列
        }
    }
}

//算法选择模块
void ChooseAlgorithm()
{
    cout << "请选择将要使用的页面置换算法:" << '\n';
    cout << "1.先进先出算法(FIFO)" << '\n';
    cout << "2.最近最久未使用算法(LRU)" << '\n';
    cout << "3.时钟置换算法(CLOCK)" << '\n';
    cout << "4.随机置换算法(RRA)" << '\n';
    int op;
    cin >> op;
    switch(op)
    {
        case 1:
            algorithmOption = FIFO;
            break;
        case 2:
            algorithmOption = LRU;
            break;
        case 3:
            algorithmOption = CLOCK;
            break;
        case 4:
            algorithmOption = RRA;
            break;
        default:
            break;
    }
}

//运行测试模块
void RunTest()
{
    cout << "运行过程:" << '\n';
    //逐条执行进程操作序列
    for(int i = 0; i < p[0].options.size(); i++)
    {
        int physicalAddress = LogicalToPhysical(p[0].options[i].instruction, p[0].options[i].option);//逻辑地址转化为物理地址
        cout << "逻辑地址: " << p[0].options[i].instruction << " -> 物理地址: " << physicalAddress << '\n';
        t++;//时间自增
    }
}

//统计数据模块
void ShowData()
{
    cout << "内存数据:" << '\n';
    printf("%6s %8s\n", "页帧号", "进程页号");
    for(int i = 0; i < M; i++)
        printf("%-6d %-8d\n", i, mem[i].pageNum);

    cout << "页表数据:" << '\n';
    printf("%4s %6s %6s %6s %6s\n", "页号", "页帧号", "状态位", "访问位", "修改位");
    for(int i = 0; i < p[0].pageTable.size(); i++)
        printf("%-4d %-6d %-6d %-6d %-6d\n", i, 
            p[0].pageTable[i].frameNum, p[0].pageTable[i].status,
            p[0].pageTable[i].visit, p[0].pageTable[i].dirty);
    cout << "访问次数:" << p[0].options.size() << '\n';
    cout << "缺页中断次数:" << missingCount <<'\n';
    cout << "置换次数:" << replaceCount << '\n';
    cout << "缺页中断率:" << (double)missingCount / p[0].options.size() * 100 << "%\n";
}

int main()
{
    Input();//输入进程要执行的操作序列
    ChooseAlgorithm();//选择将要执行的页面置换算法
    RunTest();//运行测试
    ShowData();//显示统计数据
    return 0;
}

四、实验代码解读

4.1前言

  模拟实验代码语言为c++。

4.2数据结构

4.2.1PCB的数据结构

//定义PCB的数据结构
struct PCB{
    int id;//进程编号
    vector<PcbOption> options;//进程操作序列
    vector<PageTableItem> pageTable;//储存在进程中的页表
}p[N];

  PCB主要保存进程编号、进程操作序列和进程中的页表三个属性。该系统中只有一个p[0]进程运行,该进程模拟所有的页面访问。

4.2.2进程操作的数据结构

//定义进程操作的数据结构,将操作拆分为代表逻辑地址的指令和操作类型
struct PcbOption{
    string instruction;//指令(逻辑地址),长度为22位
    PcbOptionType option;//要执行的操作
};

  PcbOption是进程操作的数据结构,将操作拆分为代表逻辑地址的指令和操作类型。逻辑地址表示p[0]进程将要访问的页面。

4.2.3页表项的数据结构

//定义页表项的数据结构
struct PageTableItem{
    int frameNum = -1;//页帧(也称页框、内存块)号,初始状态为-1
    int status = 0;//状态位,表示该页面是否调入内存,status为1代表已调入,初始状态为0
    int exmemAddress = 0;//外存地址,保存页面在外存中的存放位置
    //访问字段,方便页面置换算法进行操作
    int selectedTime = INF;//页面被放入内存的时间
    int visitedTime = INF;//页面最近一次被访问的时间
    int visit = 0;//访问位,表示页面近期是否被访问,0代表近期未被访问
    int dirty = 0;//修改位,表示页面调入内存后是否被修改过,dirty为1代表已修改,初始状态为0

    //初始化函数
    PageTableItem()
    {
        frameNum = -1;
        status = 0;
        dirty = 0;
        exmemAddress = 0;
    }
};

  PageTableItem是页表项的数据结构。PageTableItem保存页帧号frameNum、状态位status和外存地址exmemAddress等基本属性,同时保持selectedTime、visitedTime、visit和dirty等访问字段,方便页面置换算法进行操作。

4.2.4内存的数据结构

//定义内存的数据结构,表示一个内存块
struct Memory{
    int value = 0;//内存块中是否存放数据,0代表内存块为空,1代表内存块存在数据,初始状态为0
    int pageNum = -1;//页号,代表当前内存块被进程的哪一个页面占用,-1代表内存块未被占用
    vector<int> memData;//模拟每个内存块中每个字节存放的数据,0代表没有数据,1代表有数据
}mem[M];

  Memory是内存块的数据结构,内存块大小M=3。一个内存块中保存数据value、页号pageNum等属性。

4.3主要函数

4.3.1地址转换函数

//地址转换函数,输入逻辑地址,输出物理地址
int LogicalToPhysical(string instruction, PcbOptionType option)
{
    //将二进制的指令转化为十进制
    int tmp = 0;
    for(int i = 0; i < instruction.size(); i++)
    {
        tmp <<= 1;
        if(instruction[i] == '1')
            tmp += 1;
    }
    //获得页号和页内偏移量
    int pageNum = tmp / PAGE_SIZE;
    int offset = tmp % PAGE_SIZE;
    //cout << "tmp:" << tmp << " " << pageNum << " " << offset << '\n';
    //判断页号是否越界
    if(pageNum >= p[0].pageTable.size())//页号大于等于页表项个数,则发生越界(内中断),输出报错信息
    {
        printf("指令%s发生越界,页号:%d,页表项个数:%d\n", instruction, pageNum, p[0].pageTable.size());
        return -1;
    }
    //查询页表
    if(p[0].pageTable[pageNum].status == 1)//如果该页面已调入内存,无需置换
    {
        printf("%d号页面已调入内存,访问命中\n", pageNum);
        p[0].pageTable[pageNum].visitedTime = t;
        p[0].pageTable[pageNum].visit = 1;
        return p[0].pageTable[pageNum].frameNum * PAGE_SIZE + offset;//返回物理地址
    }
    else//如果该页面未调入内存
    {
        bool flag = 0;//表示是否采用置换算法,0表示未采用
        missingCount++;//增加缺页次数
        //寻找内存中空的内存块
        int mem_id = -1;//内存中空的内存块的编号,-1代表没有空的内存块
        for(int i = 0; i < M; i++)
        {
            if(mem[i].value == 0)
            {
                mem_id = i;
                break;
            }
        }
        //cout << "mem_id_1:" << mem_id << '\n';
        if(mem_id == -1)//如果内存中没有空的内存块,采用置换算法获得内存块
        {
            flag = 1;
            mem_id = algorithmOption();
            replaceCount++;//增加置换次数
        }
        //cout << "mem_id_2:" << mem_id << '\n';
        if(!flag)
            printf("%d号页面未调入内存,发生缺页,将%d号页面放入空内存块%d\n", pageNum, pageNum, mem_id);
        else
            printf("%d号页面未调入内存,发生缺页,采用替换算法将%d号页面放入内存块%d,替换%d号页面\n",
                pageNum, pageNum, mem_id, mem[mem_id].pageNum);

        mem[mem_id].pageNum = pageNum;//更新页框所对应的页号
        mem[mem_id].value = 1;//页框被占用
        p[0].pageTable[pageNum].frameNum = mem_id;//将页表项中的页帧号更新为mem_id
        p[0].pageTable[pageNum].status = 1;//将页表项中的状态位更新为1
        //更新页表项中的访问字段
        p[0].pageTable[pageNum].selectedTime = t;
        p[0].pageTable[pageNum].visitedTime = t;
        p[0].pageTable[pageNum].visit = 1;//
        if(option == MODIFY)
            p[0].pageTable[pageNum].dirty = 1;

        return mem_id * PAGE_SIZE + offset;//返回物理地址
    }
}

  地址转换函数将逻辑地址转换为物理地址。该函数先根据逻辑地址计算出页号和页内偏移量,再判断页号是否越界。如果页号未越界就查询页表,未命中的情况下会在内存中寻找空的内存块或者采用页面置换算法。

4.3.2FIFO算法

//FIFO算法优先队列比较函数
struct cmp
{
	bool operator()(const int &a, const int &b)//selectTime小的靠近队首
	{
		return p[0].pageTable[a].selectedTime > p[0].pageTable[b].selectedTime;
	}
};
//FIFO算法
int FIFO()
{
    priority_queue<int, vector<int>, cmp> q;//;以页面调入内存的先后顺序排成的队列,保存页号
    if(q.empty())//如果队列为空,则按照页面访问顺序查找在内存中的页面
        for(int i = 0; i < p[0].pageTable.size(); i++)
            if(p[0].pageTable[i].status == 1)
                q.push(i);
    int pageNum = q.top();//从队头取出要替换的页帧(最早进入内存的页面)所对应的页号
    q.pop();
    int frameNum = p[0].pageTable[pageNum].frameNum;//获得对应页框号
    p[0].pageTable[pageNum].status = 0;//原来的页面移除内存
    q.push(pageNum);//再将页号放入队尾
    return frameNum;//返回要替换的页帧号
}

  设置一个优先队列,保存调入内存的页面的页号。每次从队头取出最早进入内存的页面的页号。

4.3.3LRU算法

//LRU算法
int LRU()
{
    int minn = INF;
    int frameNum = -1;//要替换的页帧号
    int pageNum = -1;//要替换的页帧所对应的页号
    for(int i = 0; i < M; i++)
    {
        if(p[0].pageTable[mem[i].pageNum].visitedTime < minn)//如果该页面是最近最久未使用的页面,则替换
        {
            minn = p[0].pageTable[mem[i].pageNum].visitedTime;
            pageNum = mem[i].pageNum;//保存要替换的页帧所对应的页号
            frameNum = i;//保存要替换的页帧号
        }
    }
    p[0].pageTable[pageNum].status = 0;//原来的页面移除内存
    return frameNum;//返回要替换的页帧号
}

  LRU算法遍历内存中的页面,通过页面最近一次被访问的时间visitedTime来找出最近最久未使用的页面,替换该页面。

4.3.4CLOCK算法

//CLOCK算法
int CLOCK()
{
    int frameNum = -1;//要替换的页帧号
    int pageNum = -1;//要替换的页帧所对应的页号
    printf("\n当前循环队列:\n");
    printf("%6s %8s %6s %6s\n", "页帧号", "对应页号", "访问位", "修改位");
    for(int k = 0; k < M; k++)//打印链式结构
    {
        if(k != 0)
        {
            printf("                                 |\n");
            printf("               |                 |\n");
            printf("               v                 |\n");
            printf("                                 |\n");
        }
        if(k == 0)
        {
            printf("%-6d %-8d %-6d %-6d<----\n", k, mem[k].pageNum, 
            p[0].pageTable[mem[k].pageNum].visit, p[0].pageTable[mem[k].pageNum].dirty);
        }
        else if(k == M - 1)
        {
            printf("%-6d %-8d %-6d %-6d-----\n", k, mem[k].pageNum,
            p[0].pageTable[mem[k].pageNum].visit, p[0].pageTable[mem[k].pageNum].dirty);
        }
        else
        {
            printf("%-6d %-8d %-6d %-6d    |\n", k, mem[k].pageNum,
            p[0].pageTable[mem[k].pageNum].visit, p[0].pageTable[mem[k].pageNum].dirty);
        }
    }
    printf("当前替换指针指向的页帧号:%d\n\n", clockPoint);
    for(int i = 1; i <= 4; i++)//从第一轮到第四轮
    {
        if(i == 1)//第一轮,查找第一个访问位和修改位均为0的页面,不修改任何标志位
        {
            for(int k = 0; k < M; k++)
            {
                int j = (clockPoint + k) % M;//扫描到的页帧号
                //如果该页面的访问位和修改位均为0,则替换
                if(p[0].pageTable[mem[j].pageNum].visit == 0 && p[0].pageTable[mem[j].pageNum].dirty == 0)
                {
                    pageNum = mem[j].pageNum;//保存要替换的页帧所对应的页号
                    frameNum = j;//保存要替换的页帧号
                    break;
                }
            }
        }
        else if(i == 2)//第二轮,查找第一个访问位为0,修改位为1的页面,将所有扫描过的帧访问位置为0
        {
            for(int k = 0; k < M; k++)
            {
                int j = (clockPoint + k) % M;//扫描到的页帧号
                //如果该页面的访问位为0,修改位为1,则替换
                if(p[0].pageTable[mem[j].pageNum].visit == 0 && p[0].pageTable[mem[j].pageNum].dirty == 1)
                {
                    pageNum = mem[j].pageNum;//保存要替换的页帧所对应的页号
                    frameNum = j;//保存要替换的页帧号
                    break;
                }
                p[0].pageTable[mem[j].pageNum].visit = 0;//将所有扫描过的帧访问位置为0
            }
        }
        else if(i == 3)//第三轮,查找第一个访问位和修改位均为0的页面,不修改任何标志位
        {
            for(int k = 0; k < M; k++)
            {
                int j = (clockPoint + k) % M;//扫描到的页帧号
                //如果该页面的访问位和修改位均为0,则替换
                if(p[0].pageTable[mem[j].pageNum].visit == 0 && p[0].pageTable[mem[j].pageNum].dirty == 0)
                {
                    pageNum = mem[j].pageNum;//保存要替换的页帧所对应的页号
                    frameNum = j;//保存要替换的页帧号
                    break;
                }
            }
        }
        else//第四轮,查找第一个访问位为0,修改位为1的页面,必然可以找到
        {
            for(int k = 0; k < M; k++)
            {
                int j = (clockPoint + k) % M;//扫描到的页帧号
                //如果该页面的访问位为0,修改位为1,则替换
                if(p[0].pageTable[mem[j].pageNum].visit == 0 && p[0].pageTable[mem[j].pageNum].dirty == 1)
                {
                    pageNum = mem[j].pageNum;//保存要替换的页帧所对应的页号
                    frameNum = j;//保存要替换的页帧号
                    break;
                }
            }
        }
        if(frameNum != -1)//如果找到了要替换的页帧,直接返回
        {
            p[0].pageTable[pageNum].status = 0;//原来的页面移除内存
            clockPoint = (frameNum + 1) % M;//指针指向下一个页帧
            return frameNum;//返回要替换的页帧号
        }
    }
}

  所有可能被置换的页面形成一个循环队列,clockPoint表示替换指针,visit表示访问位,dirty表示修改位。CLOCK最多经过四轮遍历,找出要替换的页面。

4.3.5RRA随机置换算法

//随机置换算法(RRA)
int RRA()
{
    int frameNum = -1;//要替换的页帧号
    int pageNum = -1;//要替换的页帧所对应的页号
    srand(time(NULL));
    frameNum = rand() % M;//页帧号取值区间为0到M-1
    pageNum = mem[frameNum].pageNum;//保存要替换的页帧所对应的页号
    p[0].pageTable[pageNum].status = 0;//原来的页面移除内存
    return frameNum;//返回要替换的页帧号
}

  利用srand函数模拟RRA算法,随机选择一个页面置换。

4.4用户交互

4.4.1输入模块

//输入模块
void Input()
{
    //初始化内存,每个内存块有PAGE_SIZE个字节
    for(int i = 0; i < M; i++)
        mem[i].memData.resize(PAGE_SIZE, 0);

    for(int i = 0; i < pcb_num; i++)
    {
        //初始化进程页表
        int pageTableLength;//页表长度
        cout << "请输入页表长度:" << '\n';
        cin >> pageTableLength;
        for(int j = 0; j < pageTableLength; j++)
        {
            PageTableItem pti;//每个页表项初始为默认值
            p[i].pageTable.push_back(pti);//将页表项放入页表
        }
        //初始化进程操作序列
        int optionNum;//操作数量
        cout << "请输入进程操作数量optionNum:" << '\n';
        cin >> optionNum;
        cout << "接下来optionNum行,请分别输入指令和操作类型:" << '\n';
        for(int j = 0; j < optionNum; j++)
        {
            string instruction;//指令
            int opType;//操作类型,0代表访问,1代表修改
            cin >> instruction >> opType;//输入指令和操作类型
            PcbOption po;
            po.instruction = instruction;
            if(opType == 0)
                po.option = VISIT;
            else
                po.option = MODIFY;
            p[i].options.push_back(po);//将该操作放入操作序列
        }
    }
}

  输入模块要求用户输入页表长度、操作数量和指令序列。

4.4.2算法选择模块

//算法选择模块
void ChooseAlgorithm()
{
    cout << "请选择将要使用的页面置换算法:" << '\n';
    cout << "1.先进先出算法(FIFO)" << '\n';
    cout << "2.最近最久未使用算法(LRU)" << '\n';
    cout << "3.时钟置换算法(CLOCK)" << '\n';
    cout << "4.随机置换算法(RRA)" << '\n';
    int op;
    cin >> op;
    switch(op)
    {
        case 1:
            algorithmOption = FIFO;
            break;
        case 2:
            algorithmOption = LRU;
            break;
        case 3:
            algorithmOption = CLOCK;
            break;
        case 4:
            algorithmOption = RRA;
            break;
        default:
            break;
    }
}

  算法选择模块要求用户选择要执行的页面置换算法。

4.4.3统计数据模块

//统计数据模块
void ShowData()
{
    cout << "内存数据:" << '\n';
    printf("%6s %8s\n", "页帧号", "进程页号");
    for(int i = 0; i < M; i++)
        printf("%-6d %-8d\n", i, mem[i].pageNum);

    cout << "页表数据:" << '\n';
    printf("%4s %6s %6s %6s %6s\n", "页号", "页帧号", "状态位", "访问位", "修改位");
    for(int i = 0; i < p[0].pageTable.size(); i++)
        printf("%-4d %-6d %-6d %-6d %-6d\n", i, 
            p[0].pageTable[i].frameNum, p[0].pageTable[i].status,
            p[0].pageTable[i].visit, p[0].pageTable[i].dirty);
    cout << "访问次数:" << p[0].options.size() << '\n';
    cout << "缺页中断次数:" << missingCount <<'\n';
    cout << "置换次数:" << replaceCount << '\n';
    cout << "缺页中断率:" << (double)missingCount / p[0].options.size() * 100 << "%\n";
}

  统计数据模块显示内存数据、页表数据和一些性能指标。

4.4.4主函数

int main()
{
    Input();//输入进程要执行的操作序列
    ChooseAlgorithm();//选择将要执行的页面置换算法
    RunTest();//运行测试
    ShowData();//显示统计数据
    return 0;
}

  主函数依次执行输入模块、算法选择模块、运行测试和统计数据模块。

五、实验结果

5.1FIFO算法测试结果

  如下图所示:
在这里插入图片描述
  FIFO测试数据(页面访问序列3 2 1 0 3 2 4 3 2 1 0 4):

5
12
011000000000000 0
010000000000000 0
001000000000000 0
000000000000000 0
011000000000000 0
010000000000000 0
100000000000000 0
011000000000000 0
010000000000000 0
001000000000000 0
000000000000000 0
100000000000000 0

5.2LRU算法测试结果

  如下图所示:
在这里插入图片描述
  LRU测试数据:(页面访问序列1 8 1 7 8 2 7 2 1 8 3 8 2 1 3 1 7 1 3 7):

9
20
0001000000000000 0
1000000000000000 0
0001000000000000 0
0111000000000000 0
1000000000000000 0
0010000000000000 0
0111000000000000 0
0010000000000000 0
0001000000000000 0
1000000000000000 0
0011000000000000 0
1000000000000000 0
0010000000000000 0
0001000000000000 0
0011000000000000 0
0001000000000000 0
0111000000000000 0
0001000000000000 0
0011000000000000 0
0111000000000000 0

5.3CLOCK算法测试结果

  如下图所示:
在这里插入图片描述

在这里插入图片描述
  CLOCK测试数据(两组,对比着用。页面访问序列1 3 4 2 5 6 3 4 7):

8
9
001000000000000 0
011000000000000 0
100000000000000 0
010000000000000 0
101000000000000 0
110000000000000 0
011000000000000 0
100000000000000 0
111000000000000 0
8
9
001000000000000 0
011000000000000 0
100000000000000 1
010000000000000 0
101000000000000 0
110000000000000 0
011000000000000 0
100000000000000 0
111000000000000 0

5.4随机置换算法测试结果

  这个没什么好说的了,毕竟是随机的对吧。

六、结语

​ 本实验设计思路参考王道考研操作系统,代码均为本人原创。如果有什么错误,欢迎大伙儿在评论区指正ヾ(●´▽‘●)ノ。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值