页面淘汰算法模拟实现与比较

1. 实验目的

理解并掌握主要页面淘汰算法的设计和实现要旨。

2. 实验内容

利用标准 C 语言,编程设计与实现最佳淘汰算法、先进先出淘汰算法、最近最久未使用淘汰算法、简单 Clock 淘汰算法及改进型 Clock 淘汰算法,并随机发生页面访问序列开展有关算法的测试及性能比较。

3. 实验要求

本实验课题功能设计要求如下:

  1. 编程设计实现最佳淘汰算法OPT、先进先出淘汰算法FIFO、最近最久未使用淘汰算法LRU、简单Clock 淘汰算法及改进型 Clock 淘汰算法
  2. 编程设计实现页面访问序列的随机发生机制,包括各页面读写访问方式的设定以满足改进型 Clock 淘汰算法的要求;
  3. 在执行进程和访问各页面过程中,每访问一个(或一次)页面应显示输出当时的进程页表内容(包括页号、物理块号、状态位、读/写访问方式等字段)及本次页面访问操作情况(譬如页面已在内存或触发缺页中断);
  4. 基于相同的条件,包括系统均采用固定分配局部置换策略相同的进程逻辑地址空间大小(暨逻辑页面数,设进程逻辑地址空间的页面总数为 N,则其页号取值区间为[0, N))、分配给进程同样多的物理块(设进程分配获得 S 个物理块,则相应物理块号分别标记为 PF0、PF1、……、PFS-1)、相同的页面访问序列(整数序列,整数取值区间为[0, N))、均预装入前三个页面,进行有关算法的测试;
  5. 变换上述条件实施多次测试,统计分析和比较有关算法的性能(譬如缺页率、淘汰页查找时间开销)。

关于页面访问序列随机发生机制的设计思想:

  • 初始化进程逻辑地址空间页面总数 N、各逻辑页面的读写访问方式(是否支持写访问,即 R、RW)、工作集起始页号 s(s∈[0, N))、工作集中包含的页数 w,工作集移动速率 v(每处理 v 个页面访问,就将工作集起始页号递增即 s+1)以及一个取值区间为[0, 1]的值 t;
  • 生成取值区间为[s, min(s+w, N-1)]的 v 个随机数并添加保存到页面访问序列中,同 时为每次页面访问分别生成一个取值区间为[0, 1]的随机数,若该随机数值大于 0.7 且对应所访问页面支持写访问则设定以写方式访问相应页面,否则以读方式访问对应页面;
  • 生成取值区间为[0, 1]的一个随机数 r,并比较 r 与 t 的大小;
  • 若 r < t,则为 s 生成一个新值(s∈[0, N)),否则 s = (s + 1) mod N;
  • 如果想继续加大页面访问序列的长度,返回第 2 步,否则结束。

4. 实验过程

4.0 整体设计

4.0.1 数据结构

  • 页表结构设计

假设虚拟内存的地址是16位,页面大小为1K,则进程的大小为 2 16 = 64 K B 2^{16}=64KB 216=64KB

分页个数 = 进程的大小 页面的大小 = 逻辑地址表示的大小 页面的大小 = 2 16 B 1 K B = 2 6 页 分页个数=\frac{进程的大小}{页面的大小}=\frac{逻辑地址表示的大小}{页面的大小}=\frac{2^{16B}}{1KB}=2^{6}页 分页个数=页面的大小进程的大小=页面的大小逻辑地址表示的大小=1KB216B=26

因此页号P占用前6位,页内偏移量W=16-6=10位。每一个页表项占用2B,因此页表存储空间需要 2 6 ∗ 2 B = 128 B 2^6*2B=128B 262B=128B

页面结构以及页表项设计如下:

struct PageInfo       //页面信息结构
 {
    int  pages[MAX];  // 模拟的最大访问页面数
    int  isVisited;         // 标志位,0表示无页面访问数据
    int  page_missing_num;    // 缺页中断次数
    int  allocated_page_num;     // 分配的页框数
    int  visit_list_length;     // 访问页面序列长度
} pInfo;

struct MemInfo  // 页表项信息
{
    int time; //记录页框中数据的访问次数
    int isVisit;//访问位
    int isModify;//修改位
    int pages;//页号
}mInfo;
  • 关键全局变量
MemInfo pageList[MAX];         // 分配的页框
int  page_loss_num = 0;        // 缺页次数
int  current_page;             //页面访问指针
int  replace_page;             //页面替换指针
int  is_loss_page;             //缺页标志:1->缺页,0->命中

4.0.2 主要函数

  • Init 初始化
void Init()
{
    int i, pn;
    is_loss_page = 0;              //缺页标志,0为不缺页,1为缺页
    pInfo.page_missing_num = 0;   // 缺页次数
    pInfo.isVisited = 0;        // 标志位,0表示无页面访问数据
    printf("请输入分配的页框数:");   // 自定义分配的页框数
    scanf("%d", &pn);
    pInfo.allocated_page_num = pn;
    for (i = 0; i < MAX; i++)   // 清空页面序列
    {
            pInfo.pages[i] = -1;
    }
}
  • getRandomList 随机生成访问序列

算法流程如下:

  1. 确定虚拟内存的尺寸N,工作集的起始位置p,工作集中包含的页数e,工作集移动率m(每处理m个页面访问则将起始位置p +1),
  2. 以及一个范围在0和1之间的值t;
  3. 生成m个取值范围在p和p + e间的随机数,并记录到页面访问序列串中;
  4. 生成一个随机数r,0 ≤ r ≤ 1;如果r < t,则为p生成一个新值,否则p = (p + 1) mod N;
  5. 如果想继续加大页面访问序列串的长度,请返回第2步,否则结束。

具体实现如下:

void getRandomList(int m, int e, int s)
{
    int pl;
    Init();     //初始化
    printf("请输入访问序列的长度:");
    scanf("%d", &pl); //随机生成访问序列的长度
    pInfo.visit_list_length = pl;
    srand((unsigned)time(NULL)); // 随机种子
    int  p = rand() % MAX; //在0——MAX范围内随机初始化工作集的起始位置p
    int i, j=0;
    double t= rand() % 10 / 10.0; // 一个范围在0和1之间的值t
    for (i = 0; i < s; i++) // 重复s次
    {
        if (j > pInfo.visit_list_length) // 不能超过访问序列的长度
            break;
        for (j = i * m; j < (i + 1) * m; j++)//生成m个取值范围在p~p + e间的随机数
        {
            pInfo.pages[j]= (p + (rand() % e)) % MAX; //并记录到页面访问序列串中;
        }
        double r = (rand() % 10) / 10.0;// 生成一个范围在0-1之间的随机数r
        if (r < t) // 如果r < t,则为p生成一个新值
            p = rand() % MAX;
        else
        {
            p = (p + 1) % MAX; //否则向后循环移位
        }
    }
}
  • showState跟踪显示状态信息

算法流程如下:

  1. 如果current_page=0, 则说明当前页面指针处于起始位置,则首先遍历pInfo.pages[i]数组,打印页面的访问序列。其具体格式为每行显示10个等访问的页面。
  2. 打印当前要访问的页号
  3. 打印当前页框内已经有的页面号
  4. 通过is_loss_page 判断是否发生中断。如果发生中断,则用缺页数/已访问的页面数计算得到缺页率并显示给用户。

具体实现如下:

void showState(void)
{
    int i, n;
    if (current_page == 0)
    {
        printf("\n~~~~~~~~~~~~~~~~~页面访问序列~~~~~~~~~~~~~~~~~\n");
        for (i = 0; i < pInfo.visit_list_length; i++)
        {
            printf("%4d", pInfo.pages[i]);
            if ((i + 1) % 10 == 0) printf("\n");   //每行显示10个
        }
        printf("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n");
    }
    printf("访问%3d -->内存空间[", pInfo.pages[current_page]); // 访问当前页面信息
    for (n = 0; n < pInfo.allocated_page_num; n++)     // 页框信息
        {
        if (pageList[n].pages >= 0)
            printf("%3d", pageList[n].pages);
        else
            printf("   ");
        }
    printf(" ]");
    if (is_loss_page == 1)     //缺页标志,0为不缺页,1为缺页
        {
        printf(" --> 缺页中断 --> ");
        if (current_page == 0)
        {
            printf("缺页率 = %3.1f", (float)(pInfo.page_missing_num) * 100.00 );
        }
        else{
            printf("缺页率 = %3.1f", (float)(pInfo.page_missing_num) * 100.00 / current_page); }
        }
    printf("\n");
}

  • isExist 查找页面是否已经在内存中

算法流程如下:

  1. 遍历所有在内存中的页面,同时使得访问次数加1
  2. 然后再次遍历内存,寻找匹配页号的页表项
  3. 如果找到该页表项,则置is_loss_page缺页标志=0(不缺页),同时访问次数time 也置为0,然后通过随机生成的0或者1,来设置该访问页面是否被修改。并返回1。
  4. 如果没有找到该页面,则缺页中断的次数+1,同时返回0。
int isExist(int page)
{
    int n;
    for (n = 0; n < pInfo.allocated_page_num; n++)
        pageList[n].time++;   // 访问次数加1
    for (n = 0; n < pInfo.allocated_page_num; n++)
    {
        if (pageList[n].pages == page)
        {
            is_loss_page = 0;    //is_loss_page缺页标志=0(不缺页)
            pageList[n].time = 0;   //置访问次数为0
            pageList[n].isVisit = 1; // 访问标志
            if (randBool()) {       
                pageList[n].isModify = 1; // 修改标志
            }
            return 1;
        }
    }
    is_loss_page = 1;  	//页面不存在,缺页
    return 0;
}

其中调用了randBool()函数,随机的生成0或者1,其具体实现如下

bool randBool()
{
    if ((rand() % 2 + 1) == 1)
        return true;
    else
        return false;
}

4.1 最佳淘汰算法(OPT)

4.1.1 原理介绍

算法规则:选择永不使用或是在最长时间内不再被访问(即距现在最长时间才会被访问)的页面淘汰出内存。

假定系统为某进程分配了三个物理块,并考虑有以下页面访问序列:
[7, 0, 1, 2, 0, 3, 0, 4, 2, 3, 0, 3, 2, 1, 2, 0, 1, 7, 0, 1]
进程运行时,先将7, 0, 1三个页面依次装入内存。进程要访问页面2时,产生缺页中断,根据最佳置换算法,选择第18次访问才需调入的页面7予以淘汰。然后,访问页面0时,因为已在内存中所以不必产生缺页中断。访问页面3时又会根据最佳置换算法将页面1淘汰……依此类推。

页面70120304230321201701
页面1777222227
页面200004000
页面31133311
缺页

4.1.2 代码实现

算法流程图如下:

在这里插入图片描述

OPT 算法具体实现如下:

void OPT()
{
    int n, full, status;
    replace_page = 0;   // 页面替换指针初始化为0
    page_loss_num = 0;  // 缺页数初始化为0
    full = 0;           // 已经占用的页框数
    is_loss_page = 0;   //缺页标志,0为不缺页,1为缺页
    for (n = 0; n < pInfo.allocated_page_num; n++) // 清除页框信息
        pageList[n].pages = -1; 
    for (current_page = 0; current_page < pInfo.visit_list_length; current_page++)  // 遍历所有的访问页面
        {
        status = isExist(pInfo.pages[current_page]);  //查找页面是否在内存
        if (full < pInfo.allocated_page_num)    // 当已经占用的页框数< 分配的页框数时,则不可能发生缺页
         {
            if (status == 0)    // 页不存在则装入页面
                {
                    pageList[replace_page].pages = pInfo.pages[current_page]; // 当前位置放入新的页面
                    replace_page = (replace_page + 1) % pInfo.allocated_page_num; //循环后移一位
                    full++; // 已经装入的页面数+1
                }
         }
        else      // 页框已经满,缺页置换
        {
            if (status == 0)    // 页不存在则置换页面
                {
                int min,max = 0 ; //很大的数
                for (int m = 0; m < pInfo.allocated_page_num ; m++) // 遍历页框中以占用的页号
                {
                    min = 1000;
                    for (int n = current_page; n < pInfo.visit_list_length; n++)// 向后查找访问序列
                    {
                        if (pInfo.pages[n] == pageList[m].pages) // 找到在当前内存出现过的页面
                        {
                            min = n;
                            break;
                        }
                    }
                    if (max < min)
                    {
                        max = min;
                        replace_page = m; // 距离当前页面最远的被置换
                    }
                }
                pageList[replace_page].pages = pInfo.pages[current_page]; // 置换动作
                replace_page = (replace_page + 1) % pInfo.allocated_page_num;
                pInfo.page_missing_num++;     // 置换次数加1
                }
        }
        Sleep(10);
            showState();       // 显示当前状态
        }	 // 置换算法循环结束
        _getch();
    return;
}

4.1.3 测试结果

  • 初始化设置
    • m=8,e=8,s=10; 页框数=3,访问序列长度=20;
    • 选择算法1:最佳淘汰算法OPT

在这里插入图片描述

  • 页面置换过程
    • 当发生缺页中断,需要置换时,所选择的被淘汰页面将是以后永不使用的,或者是在最长时间内不再被访问的页面。验证了程序的正确性。
    • 置换率=42.1% 运行时长=0.327s

在这里插入图片描述

4.2 先进先出淘汰算法(FIFO)

4.2.1 原理介绍

算法规则:在发生页面替换时,被替换的对象应该是最早进入内存的。

仍然采用上述的例子,此时页面置换过程如下表所示

页面70120304230321201701
页面1777222444000777
页面200033322211100
页面31110003332221
缺页

4.2.2 代码实现

  • FIFO算法流程图如下所示:
    在这里插入图片描述

  • FIFO 页面置换算法具体实现如下:

void FIFO(void)
{
    int n, full, status;
    replace_page = 0;   // 页面替换指针初始化为0
    page_loss_num = 0;  // 缺页数初始化为0
    full = 0;           // 是否装满是所有的页框
    for (n = 0; n < pInfo.allocated_page_num; n++) // 清除页框信息
        pageList[n].pages = -1;
    is_loss_page = 0;   //缺页标志,0为不缺页,1为缺页
    for (current_page = 0; current_page < pInfo.visit_list_length; current_page++)  // 执行算法
        {
        status = isExist(pInfo.pages[current_page]);  //查找页面是否在内存
        if (full < pInfo.allocated_page_num)    // 开始时不计算缺页
            {
            if (status == 0)    // 页不存在则装入页面
                {
                pageList[replace_page].pages = pInfo.pages[current_page];
                replace_page = (replace_page + 1) % pInfo.allocated_page_num;
                full++;
                }
            }
        else      // 正常缺页置换
        {
            if (status == 0)    // 页不存在则置换页面
                {
                    pageList[replace_page].pages = pInfo.pages[current_page];
                    replace_page = (replace_page + 1) % pInfo.allocated_page_num;
                    pInfo.page_missing_num++;     // 缺页次数加1
                }
        }
        Sleep(10);
            showState();       // 显示当前状态
        }	 // 置换算法循环结束
        _getch();
    return;
}

4.2.3 测试结果

  • 初始化设置

    • m=8,e=8,s=10; 页框数=3,访问序列长度=20;

    • 选择算法2:先进先出淘汰算法

在这里插入图片描述

  • 置换过程

    • 从如下测试结果可以看出,当需要淘汰一个页面时,总是选择驻留主存时间最长的页面进行淘汰,即先进入主存的页面先淘汰。证明了程序的正确性
    • 置换率=63.2 运行时长=0.327
      在这里插入图片描述

4.3 最近最久未使用淘汰算法(LRU)

4.3.1 原理介绍

算法规则:在发生页面替换时,被替换的页面应该满足,在之前的访问队列中,该对象截止目前未被访问的时间最长。

仍然采用上述的例子,此时页面置换过程如下表所示

页面70120304230321201701
页面1777224440111
页面200000033300
页面31133222227
缺页

4.3.2 代码实现

  • LRU算法流程图如下:

在这里插入图片描述

  • LRU页面置换算法具体实现如下:
void LRU(void)
{
    int n, full, status, max;
    replace_page = 0;          // 页面替换指针
    page_loss_num = 0;  // 缺页次数初始化为0

    full = 0;           // 是否装满所有的页框
    for (n = 0; n < pInfo.allocated_page_num; n++)
    {
        pageList[n].pages = -1;    // 清除页框信息
        pageList[n].time = 0;   // 清除页框历史
    }
    is_loss_page = 0;    //缺页标志,0为不缺页,1为缺页
    for (current_page = 0; current_page < pInfo.visit_list_length; current_page++)  // 执行算法
        {
        status = isExist(pInfo.pages[current_page]);  //查找页面是否在内存
        if (full < pInfo.allocated_page_num)   // 开始时不计算缺页
            {
            if (status == 0)   // 页不存在则装入页面
                {
                pageList[replace_page].pages = pInfo.pages[current_page]; //把要调入的页面放入一个空的页框里
                replace_page = (replace_page + 1) % pInfo.allocated_page_num;
                full++;
                }
            }
        else //正常缺页置换
        {
            if (status == 0)//页不存在则置换页面
                {
                max = 0;
                for (n = 1; n < pInfo.allocated_page_num; n++)
                {
                    if (pageList[n].time > pageList[max].time)
                    {
                        max = n;
                    }
                }
                replace_page = max;
                pageList[replace_page].pages = pInfo.pages[current_page];
                pageList[replace_page].time = 0;
                pInfo.page_missing_num++;  // 缺页次数加1
                }
        }
        Sleep(10);
            showState();    // 显示当前状态
        } 	// 置换算法循环结束
        _getch();
    return;
}

4.3.3 测试结果

  • 初始化设置

    • m=8,e=8,s=10; 页框数=3,访问序列长度=20;

    • 选择算法3:先进先出淘汰算法

在这里插入图片描述

  • 页面置换过程

    • 如下图可以看出,当需要淘汰一个页面时,总是选择在最近一段时间内最久不用的页面予以淘汰。证明了程序的正确性。
    • 置换率=57.9 运行时长=0.33

在这里插入图片描述

4.4 简单Clock 淘汰算法(NRU)

4.4.1 原理介绍

算法规则:简单的CLOCK算法实现方法:为每个页面设置一个访问位,再将内存中的页面都通过链接指针链接成一个循环队列。当某页被访问时,其访问位置为1。当需要淘汰一个页面时,只需检查页的访问位。如果是0,就选择该页换出;如果是1,则将它置为0,暂不换出,继续检查下一个页面,若第一轮扫描中所有页面都是1,则将这些页面的访问位依次置为0后,再进行第二轮扫描(第二轮扫描中一定会有访问位为0的页面,因此简单的CLOCK算法选择一个淘汰页面最多会经过两轮扫描)

4.4.2 代码实现

  • 简单CLOCk算法流程图如下:

在这里插入图片描述

  • 算法具体实现如下:

CLOCK_pro 是CLOCK算法的入口参数,其中会传入参数choose。当choose =1时采用改进的clock算法,当choose =0时采用基本的clock算法。

void CLOCK_pro(int choose)
{
    int n, full, status;
    int num = -1;
    replace_page = 0;          // 页面替换指针初始化为0
    page_loss_num = 0;  // 缺页数初始化为0

    full = 0;           // 是否装满是所有的页框
    for (n = 0; n < pInfo.allocated_page_num; n++) // 清除页框信息
    {
        pageList[n].pages = -1;
        pageList[n].isModify = 0;
        pageList[n].isVisit = 0;
        pageList[n].time = 0;
     }
    is_loss_page = 0;   //缺页标志,0为不缺页,1为缺页
    for (current_page = 0; current_page < pInfo.visit_list_length; current_page++)  // 执行算法
        {
        status = isExist(pInfo.pages[current_page]);  //查找页面是否在内存
        if (full < pInfo.allocated_page_num)    // 开始时不计算缺页
        {
            if (status == 0)    // 页不存在则装入页面
            {
                pageList[replace_page].pages = pInfo.pages[current_page];
                replace_page = (replace_page + 1) % pInfo.allocated_page_num;
                pageList[n].isVisit = 1;
                full++;
            }
        }
        else      // 正常缺页置换
        {
            if (status == 0)    // 页不存在则置换页面
                {
                    if(choose==1)
                        replace_page = replace_page_pro(++num); // 当choose =1时采用改进的clock算法
                    else if(choose==0)
                        replace_page = replace_page_clock(++num); // 当choose =0时采用基本的clock算法
                    pageList[replace_page].pages = pInfo.pages[current_page];
                    pageList[replace_page].isVisit = 1;
                    pInfo.page_missing_num++;     // 缺页次数加1
                }
        }
        Sleep(10);
            showState();       // 显示当前状态
        }	 // 置换算法循环结束
        _getch();
    return;
}

然后replace_page_clock 实现了基本CLOCK算法的置换策略,具体代码如下:

int replace_page_clock(int num) {
    int j;
    // 将所有可能被置换的页面排成一个循环队列,用(访问位,修改位)的形式表示各页面状态
    // 1.当需要淘汰一个页面时,只需检查页的访问位。如果是0,就选择该页换出;如果是1,则将它置为0,暂不换出,继续检查下一个页面
    for (j = 0; j < pInfo.allocated_page_num; j++){
        if (pageList[(j + num) % pInfo.allocated_page_num].isVisit == 0 )
            return (j+num) % pInfo.allocated_page_num;
        pageList[(j + num) % pInfo.allocated_page_num].isVisit = 0;
    }
    //2. 若第一轮扫描中所有页面都是1,则将这些页面的访问位依次置为0后,再进行第二轮扫描(第二轮扫描中一定会有访问位为0的页面
    for (j = 0; j < pInfo.allocated_page_num; j++){
        if (pageList[(j + num) % pInfo.allocated_page_num].isVisit == 0 )
            return (j + num) % pInfo.allocated_page_num;
    }
}

4.4.3 测试结果

  • 初始化设置

    • m=8,e=8,s=10; 页框数=3,访问序列长度=20;

    • 选择算法4:基本的CLOCK算法

在这里插入图片描述

  • 页面置换过程

    • 如下图程序所示,正进行页面置换的过程中,总是优先淘汰未访问的页面。 证明了程序的正确性。
    • 置换率=42.1 运行时长=0.323s
      在这里插入图片描述

4.5 改进型 Clock 淘汰算法

4.5.1 原理介绍

算法规则:简单的时钟置换算法仅考虑到一个页面最近是否被访问过。事实上,如果被淘汰的页面没有被修改过,就不需要执行I/o操作写回外存。只有被淘汰的页面被修改过时,才需要写回外存。
因此,除了考虑一个页面最近有没有被访问过之外,操作系统还应考虑页面有没有被修改过。在其他条件都相同时,应优先淘汰没有修改过的页面,避免I/o操作。这就是改进型的时钟置换算法的思想。修改位=o,表示页面没有被修改过;修改位=1,表示页面被修改过。为方便讨论,用(访问位,修改位)的形式表示各页面状态。如(1,1)表示一个页面近期被访问过,且被修改过。

将所有可能被置换的页面排成一个循环队列

  1. 从当前位置开始扫描到第一个(0,0)的帧用于替换。本轮扫描不修改任何标志位
  2. 若第一轮扫描失败,则重新扫描,查找第一个(0,1)的帧用于替换。本轮将所有扫描过的帧访问位设为0
  3. 若第二轮扫描失败,则重新扫描,查找第一个(0,0)的帧用于替换。本轮扫描不修改任何标志位
  4. 若第三轮扫描失败,则重新扫描,查找第一个(0,1)的帧用于替换。由于第二轮已将所有帧的访问位设为0,因此经过第三轮、第四轮扫描一定会有一个帧被选中,因此改进型CLOCK置换算法选择一个淘汰页面最多会进行四轮扫描

4.5.2 代码实现

  • 改进的CLOCk算法流程图如下

在这里插入图片描述

  • 代码具体实现如下
int replace_page_pro(int num) {
    int j;
    // 将所有可能被置换的页面排成一个循环队列,用(访问位,修改位)的形式表示各页面状态
    // 1.从当前位置开始扫描到第一个(0,0)的帧用于替换。本轮扫描不修改任何标志位
    for (j = 0; j < pInfo.allocated_page_num; j++){
        if (pageList[(j + num) % pInfo.allocated_page_num].isVisit == 0 && pageList[(j + num) % pInfo.allocated_page_num].isModify == 0)
            return (j+num) % pInfo.allocated_page_num;
    }
    //2. 若第一轮扫描失败,则重新扫描,查找第一个(0,1)的帧用于替换。本轮将所有扫描过的帧访问位设为0
    for (j = 0; j < pInfo.allocated_page_num; j++){
        if (pageList[(j + num) % pInfo.allocated_page_num].isVisit == 0 && pageList[(j + num) % pInfo.allocated_page_num].isModify == 1)
            return (j + num) % pInfo.allocated_page_num;
        pageList[(j + num) % pInfo.allocated_page_num].isVisit = 0;
    }
    // 3.若第二轮扫描失败,则重新扫描,查找第一个(0,0)的帧用于替换。本轮扫描不修改任何标志位
    for (j = 0; j < pInfo.allocated_page_num; j++){
        if (pageList[(j + num) % pInfo.allocated_page_num].isVisit == 0 && pageList[(j + num) % pInfo.allocated_page_num].isModify == 0)
            return (j+num) % pInfo.allocated_page_num;
    }
    //4. 若第三轮扫描失败,则重新扫描,查找第一个(0,1)的帧用于替换。
    for (j = 0; j < pInfo.allocated_page_num; j++){
        if (pageList[(j + num) % pInfo.allocated_page_num].isVisit == 0 && pageList[(j + num) % pInfo.allocated_page_num].isModify == 1)
            return (j + num) % pInfo.allocated_page_num;
        // pageList[(j + num) % pInfo.allocated_page_num].isVisit = 0;
    }
}

4.5.3 测试结果

  • 初始化设置

    • m=8,e=8,s=10; 页框数=3,访问序列长度=20;

    • 选择算法5:改进的CLOCK算法

在这里插入图片描述

页面置换过程

  • 如下图程序所示,正进行页面置换的过程中,总是在每次页面替换时,总是尽可能地先替换掉既未被访问又未被修改的页面。证明了程序的正确性。
  • 置换率=42.1 运行时长=0.334s
    在这里插入图片描述

4.6 算法性能对比

由于上述的生成访问随机序列较小,且只考虑了页框大小为3的情况。因此,需要改变页框数和增大访问序列来对比算法的命中率,并对不同的实验情况的置换率和系统开销求得平均值,统计如下表所示:

算法名称 \ 性能置换率系统开销
OPT25.4%431ms
FIFO43.1%336ms
LRU42.7%362ms
CLOCK35.2%384ms
CLOCK_pro32.6%398ms
  • 置换率比较

    最佳置换算法 < 改进型clock置换算法 <clock算法<最近最久未使用算法 <先进先出置换算法

  • 系统开销比较

    先进先出置换算法 < 最近最久未使用算法 < clock算法<改进型clock置换算法 < 最佳置换算法

5. 心得体会

通过这次实验我更加熟悉了操作系统页面置换算法的流程,并通过编写代码实现了5种置换算法。通过对比其置换率和系统开销,分析出了不同算法的优缺点和特性。同时也提升了我的编程能力,受益匪浅。

6. 源代码附录

#include "windows.h"
#include <conio.h>
#include <stdlib.h>
#include <io.h>
#include <string.h>
#include <stdio.h>
#include<iostream>
#include<time.h>
using namespace std;
#define MAX 64


void FIFO();          //先进先出算法
void OPT();          //最佳置换算法
void LRU();           //最近最久未使用算法
void CLOCK_pro(int choose);        //改进型Clock置换算法
void Init();    //初始化相关数据结构
void getRandomList(int m, int e, int s);      //随机生成访问序列
void showState();   //显示当前状态及缺页情况
int  isExist(int page);      //查找页面是否在内存
bool randBool(); // 随机生成0或1

struct PageInfo       //页面信息结构
 {
    int  pages[MAX];  // 模拟的最大访问页面数
    int  isVisited;         // 标志位,0表示无页面访问数据
    int  page_missing_num;    // 缺页中断次数
    int  allocated_page_num;     // 分配的页框数
    int  visit_list_length;     // 访问页面序列长度
} pInfo;

struct MemInfo  // 页表项信息
{
    int time; //记录页框中数据的访问历史
    int isVisit;//访问位
    int isModify;//修改位
    int pages;//页号,16位信息,前6位代表页号,后10位为偏移地址 0~2^16
}mInfo;

MemInfo pageList[MAX];      // 分配的页框

int  page_loss_num = 0;                 // 缺页次数
int  current_page;             //页面访问指针
int  replace_page;             //页面替换指针
int  is_loss_page;            //缺页标志,0为不缺页,1为缺页


//初始化相关数据结构
void Init()
{
    int i, pn;
    is_loss_page = 0;              //缺页标志,0为不缺页,1为缺页
    pInfo.page_missing_num = 0;   // 缺页次数
    pInfo.isVisited = 0;        // 标志位,0表示无页面访问数据
    printf("请输入分配的页框数:");   // 自定义分配的页框数
    scanf("%d", &pn);
    pInfo.allocated_page_num = pn;
    for (i = 0; i < MAX; i++)   // 清空页面序列
    {
            pInfo.pages[i] = -1;
    }
}


// 随机生成访问序列
void getRandomList(int m, int e, int s)
{
    int pl;
    Init();     //初始化
    printf("请输入访问序列的长度:");
    scanf("%d", &pl); //随机生成访问序列的长度
    pInfo.visit_list_length = pl;
    srand((unsigned)time(NULL)); // 随机种子
    int  p = rand() % MAX; //在0——MAX范围内随机初始化工作集的起始位置p
    int i, j=0;
    double t= rand() % 10 / 10.0; // 一个范围在0和1之间的值t
    for (i = 0; i < s; i++) // 重复s次
    {
        if (j > pInfo.visit_list_length) // 不能超过访问序列的长度
            break;
        for (j = i * m; j < (i + 1) * m; j++)//生成m个取值范围在p~p + e间的随机数
        {
            pInfo.pages[j]= (p + (rand() % e)) % MAX; //并记录到页面访问序列串中;
        }
        double r = (rand() % 10) / 10.0;// 生成一个范围在0-1之间的随机数r
        if (r < t) // 如果r < t,则为p生成一个新值
            p = rand() % MAX;
        else
        {
            p = (p + 1) % MAX; //否则向后循环移位
        }
    }
}


//  显示当前状态及缺页情况
void showState(void)
{
    int i, n;
    if (current_page == 0)
    {
        printf("\n~~~~~~~~~~~~~~~~~页面访问序列~~~~~~~~~~~~~~~~~\n");
        for (i = 0; i < pInfo.visit_list_length; i++)
        {
            printf("%4d", pInfo.pages[i]);
            if ((i + 1) % 10 == 0) printf("\n");   //每行显示10个
        }
        printf("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n");
    }
    printf("访问%3d -->内存空间[", pInfo.pages[current_page]);
    for (n = 0; n < pInfo.allocated_page_num; n++)     // 页框信息
        {
        if (pageList[n].pages >= 0)
            printf("%3d", pageList[n].pages);
        else
            printf("   ");
        }
    printf(" ]");
    if (is_loss_page == 1)     //缺页标志,0为不缺页,1为缺页
        {
            printf(" --> 缺页中断 --> ");
            if (current_page == 0)
            {
                printf("置换率 = %3.1f ", (float)(pInfo.page_missing_num) * 100.00 );
            }
            else{
                printf("置换率 = %3.1f", (float)(pInfo.page_missing_num) * 100.00 / current_page);
            }
        }
    printf("\n");
}


// 查找页面是否在内存,1为在内存,0为不在即缺页
int isExist(int page)
{
    int n;
    for (n = 0; n < pInfo.allocated_page_num; n++)
    {
        pageList[n].time++;   // 访问历史加1
    }
    for (n = 0; n < pInfo.allocated_page_num; n++)
    {
        if (pageList[n].pages == page)
        {
            is_loss_page = 0;    //is_loss_page缺页标志,0为不缺页,1为缺页
            pageList[n].time = 0;   //置访问历史为0
            if (randBool()) {
                pageList[n].isVisit = 1;
                pageList[n].isModify = 1;
            }
            else {
                pageList[n].isVisit = 1;
            }
            return 1;
        }
    }
    is_loss_page = 1;  	//页面不存在,缺页
    return 0;
}


//  FIFO页面置换算法
void FIFO(void)
{
    int n, full, status;
    replace_page = 0;   // 页面替换指针初始化为0
    page_loss_num = 0;  // 缺页数初始化为0
    full = 0;           // 是否装满是所有的页框
    for (n = 0; n < pInfo.allocated_page_num; n++) // 清除页框信息
        pageList[n].pages = -1;
    is_loss_page = 0;   //缺页标志,0为不缺页,1为缺页
    for (current_page = 0; current_page < pInfo.visit_list_length; current_page++)  // 执行算法
        {
        status = isExist(pInfo.pages[current_page]);  //查找页面是否在内存
        if (full < pInfo.allocated_page_num)    // 开始时不计算缺页
            {
            if (status == 0)    // 页不存在则装入页面
                {
                pageList[replace_page].pages = pInfo.pages[current_page];
                replace_page = (replace_page + 1) % pInfo.allocated_page_num;
                full++;
                }
            }
        else      // 正常缺页置换
        {
            if (status == 0)    // 页不存在则置换页面
                {
                    pageList[replace_page].pages = pInfo.pages[current_page];
                    replace_page = (replace_page + 1) % pInfo.allocated_page_num;
                    pInfo.page_missing_num++;     // 缺页次数加1
                }
        }
        Sleep(10);
            showState();       // 显示当前状态
        }	 // 置换算法循环结束
        _getch();
    return;
}
// OPT 页面置换算法
void OPT()
{
    int n, full, status;
    replace_page = 0;          // 页面替换指针初始化为0
    page_loss_num = 0;  // 缺页数初始化为0

    full = 0;           // 是否装满是所有的页框
    for (n = 0; n < pInfo.allocated_page_num; n++) // 清除页框信息
    {
        pageList[n].pages = -1;
    }
    is_loss_page = 0;   //缺页标志,0为不缺页,1为缺页
    for (current_page = 0; current_page < pInfo.visit_list_length; current_page++)  // 执行算法
        {
        status = isExist(pInfo.pages[current_page]);  //查找页面是否在内存
        if (full < pInfo.allocated_page_num)    // 开始时不计算缺页
        {
            if (status == 0)    // 页不存在则装入页面
            {
                pageList[replace_page].pages = pInfo.pages[current_page];
                replace_page = (replace_page + 1) % pInfo.allocated_page_num;
                full++;
            }
        }
        else      // 正常缺页置换
        {
            if (status == 0)    // 页不存在,则置换页面
            {
                int min,max = 0 ; //很大的数
                for (int m = 0; m < pInfo.allocated_page_num ; m++)
                {
                    min = 1000;
                    for (int n = current_page; n < pInfo.visit_list_length; n++)
                    {
                        if (pInfo.pages[n] == pageList[m].pages)
                        {
                            min = n;
                            break;
                        }
                    }
                    if (max < min)
                    {
                        max = min;
                        replace_page = m;
                    }

                }
                pageList[replace_page].pages = pInfo.pages[current_page];
                replace_page = (replace_page + 1) % pInfo.allocated_page_num;
                pInfo.page_missing_num++;     // 缺页次数加1
            }
        }
        Sleep(10);
            showState();       // 显示当前状态
        }	 // 置换算法循环结束
        _getch();
    return;
}



//查找替换页面算法,递归实现
int replace_page_pro(int num) {
    int j;
    // 将所有可能被置换的页面排成一个循环队列,用(访问位,修改位)的形式表示各页面状态
    // 1.从当前位置开始扫描到第一个(0,0)的帧用于替换。本轮扫描不修改任何标志位
    for (j = 0; j < pInfo.allocated_page_num; j++){
        if (pageList[(j + num) % pInfo.allocated_page_num].isVisit == 0 && pageList[(j + num) % pInfo.allocated_page_num].isModify == 0)
            return (j+num) % pInfo.allocated_page_num;
    }
    //2. 若第一轮扫描失败,则重新扫描,查找第一个(0,1)的帧用于替换。本轮将所有扫描过的帧访问位设为0
    for (j = 0; j < pInfo.allocated_page_num; j++){
        if (pageList[(j + num) % pInfo.allocated_page_num].isVisit == 0 && pageList[(j + num) % pInfo.allocated_page_num].isModify == 1)
            return (j + num) % pInfo.allocated_page_num;
        pageList[(j + num) % pInfo.allocated_page_num].isVisit = 0;
    }
    // 3.若第二轮扫描失败,则重新扫描,查找第一个(0,0)的帧用于替换。本轮扫描不修改任何标志位
    for (j = 0; j < pInfo.allocated_page_num; j++){
        if (pageList[(j + num) % pInfo.allocated_page_num].isVisit == 0 && pageList[(j + num) % pInfo.allocated_page_num].isModify == 0)
            return (j+num) % pInfo.allocated_page_num;
    }
    //4. 若第三轮扫描失败,则重新扫描,查找第一个(0,1)的帧用于替换。
    for (j = 0; j < pInfo.allocated_page_num; j++){
        if (pageList[(j + num) % pInfo.allocated_page_num].isVisit == 0 && pageList[(j + num) % pInfo.allocated_page_num].isModify == 1)
            return (j + num) % pInfo.allocated_page_num;
        // pageList[(j + num) % pInfo.allocated_page_num].isVisit = 0;
    }
}

int replace_page_clock(int num) {
    int j;
    // 将所有可能被置换的页面排成一个循环队列,用(访问位,修改位)的形式表示各页面状态
    // 1.当需要淘汰一个页面时,只需检查页的访问位。如果是0,就选择该页换出;如果是1,则将它置为0,暂不换出,继续检查下一个页面
    for (j = 0; j < pInfo.allocated_page_num; j++){
        if (pageList[(j + num) % pInfo.allocated_page_num].isVisit == 0 )
            return (j+num) % pInfo.allocated_page_num;
        pageList[(j + num) % pInfo.allocated_page_num].isVisit = 0;
    }
    //2. 若第一轮扫描中所有页面都是1,则将这些页面的访问位依次置为0后,再进行第二轮扫描(第二轮扫描中一定会有访问位为0的页面
    for (j = 0; j < pInfo.allocated_page_num; j++){
        if (pageList[(j + num) % pInfo.allocated_page_num].isVisit == 0 )
            return (j + num) % pInfo.allocated_page_num;
    }
}
//随机化函数
bool randBool()
{
    if ((rand() % 2 + 1) == 1)
        return true;
    else
        return false;
}


void CLOCK_pro(int choose)
{
    int n, full, status;
    int num = -1;
    replace_page = 0;          // 页面替换指针初始化为0
    page_loss_num = 0;  // 缺页数初始化为0

    full = 0;           // 是否装满是所有的页框
    for (n = 0; n < pInfo.allocated_page_num; n++) // 清除页框信息
        {
        pageList[n].pages = -1;
        pageList[n].isModify = 0;
        pageList[n].isVisit = 0;
        pageList[n].time = 0;
        }
    is_loss_page = 0;   //缺页标志,0为不缺页,1为缺页
    for (current_page = 0; current_page < pInfo.visit_list_length; current_page++)  // 执行算法
        {
        status = isExist(pInfo.pages[current_page]);  //查找页面是否在内存
        if (full < pInfo.allocated_page_num)    // 开始时不计算缺页
        {
            if (status == 0)    // 页不存在则装入页面
            {
                pageList[replace_page].pages = pInfo.pages[current_page];
                replace_page = (replace_page + 1) % pInfo.allocated_page_num;
                pageList[n].isVisit = 1;
                full++;
            }
        }
        else      // 正常缺页置换
        {
            if (status == 0)    // 页不存在则置换页面
                {
                    if(choose==1)
                        replace_page = replace_page_pro(++num); // 当choose =1时采用改进的clock算法
                    else if(choose==0)
                        replace_page = replace_page_clock(++num); // 当choose =0时采用基本的clock算法
                    pageList[replace_page].pages = pInfo.pages[current_page];
                    pageList[replace_page].isVisit = 1;
                    pInfo.page_missing_num++;     // 缺页次数加1
                }
        }
        Sleep(10);
            showState();       // 显示当前状态
        }	 // 置换算法循环结束
        _getch();
    return;
}


//  LRU页面置换算法
void LRU(void)
{
    int n, full, status, max;
    replace_page = 0;          // 页面替换指针
    page_loss_num = 0;  // 缺页次数初始化为0

    full = 0;           // 是否装满所有的页框
    for (n = 0; n < pInfo.allocated_page_num; n++)
    {
        pageList[n].pages = -1;    // 清除页框信息
        pageList[n].time = 0;   // 清除页框历史
    }
    is_loss_page = 0;    //缺页标志,0为不缺页,1为缺页
    for (current_page = 0; current_page < pInfo.visit_list_length; current_page++)  // 执行算法
        {
        status = isExist(pInfo.pages[current_page]);  //查找页面是否在内存
        if (full < pInfo.allocated_page_num)   // 开始时不计算缺页
            {
            if (status == 0)   // 页不存在则装入页面
                {
                pageList[replace_page].pages = pInfo.pages[current_page]; //把要调入的页面放入一个空的页框里
                replace_page = (replace_page + 1) % pInfo.allocated_page_num;
                full++;
                }
            }
        else //正常缺页置换
        {
            if (status == 0)//页不存在则置换页面
                {
                max = 0;
                for (n = 1; n < pInfo.allocated_page_num; n++)
                {
                    if (pageList[n].time > pageList[max].time)
                    {
                        max = n;
                    }
                }
                replace_page = max;
                pageList[replace_page].pages = pInfo.pages[current_page];
                pageList[replace_page].time = 0;
                pInfo.page_missing_num++;  // 缺页次数加1
                }
        }
        Sleep(10);
            showState();    // 显示当前状态
        } 	// 置换算法循环结束
        _getch();
    return;
}



//  主函数

int main()
{
    char ch;
    system("cls");
    printf("**********页面置换算法********\n");
    int m,e,s;
    printf("请输入工作集移动率(m):");
    scanf("%d",&m); //8
    printf("请输入工作集包含的页数(e):");
    scanf("%d",&e); //8
    printf("请输入重复次数(s):");
    scanf("%d",&s);  // 10
    getRandomList(m, e, s);  // 随机生成访问序列
    clock_t start, finish;
    double totaltime;
    while (true)
    {
        printf("====================MENU=========================\n");
        printf("1. 最佳淘汰算法(OPT)\n");
        printf("2. 先进先出淘汰算法(FIFO)\n");
        printf("3. 最近最久未使用淘汰算法(LRU)\n");
        printf("4. 基本的Clock 淘汰算法\n");
        printf("5. 改进型的Clock 淘汰算法\n");
        printf("=================================================\n");
        printf("请输入选择的算法(1/2/3/4/5): ");
        ch = (char)_getch();
        switch (ch) {
            case '1':
                printf("\n《------------1. 最佳淘汰算法(OPT)--------------》\n");
                start = clock();
                OPT();
                finish = clock();
                totaltime = (double)(finish - start) / CLOCKS_PER_SEC;
                cout << "\n运行总时长= " << totaltime << " 秒" << endl;
                break;
            case '2':
                printf("\n\n《----------2. 先进先出淘汰算法(FIFO)------------》\n");
                start = clock();
                FIFO();
                finish = clock();
                totaltime = (double)(finish - start) / CLOCKS_PER_SEC;
                cout << "\n运行总时长= " << totaltime << " 秒" << endl;
                break;
            case '3':
                printf("\n\n《--------3. 最近最久未使用淘汰算法(LRU)-----------》\n");
                start = clock();
                LRU();
                finish = clock();
                totaltime = (double)(finish - start) / CLOCKS_PER_SEC;
                cout << "\n运行总时长= " << totaltime << " 秒" << endl;
                break;
            case '4':
                printf("\n\n《------------ 4. Clock 淘汰算法--------------》\n");
                start = clock();
                CLOCK_pro(0);
                finish = clock();
                totaltime = (double)(finish - start) / CLOCKS_PER_SEC;
                cout << "\n运行总时长= " << totaltime << "秒" << endl;
                break;
            case '5':
                printf("\n\n《------------ 5. 改进的Clock 淘汰算法--------------》\n");
                start = clock();
                CLOCK_pro(1);
                finish = clock();
                totaltime = (double)(finish - start) / CLOCKS_PER_SEC;
                cout << "\n运行总时长= " << totaltime << "秒" << endl;
                break;
            default:
                return 0;
        }
    }
    return 0;
}

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐