Bootstrap

操作系统课设——存储管理

记录于2022.7.7——南林操作系统课设心得

这个实验比较有意思,我理解题目都理解了半天,而且网上很少有关于本实验的博客,于是最后按我的理解来实现了这个程序:先用生产者消费者来产生一个随机的指令序列,然后再用FIFO和LRU算法实现页面置换。题目中所说的“用户内存容量为4页到40页”我则理解为用户内存容量从4一直增加到40,分别计算从4到40之间这两种算法的缺页率


<任务>

用程序实现生产者——消费者问题

将指令序列转换为用户虚存中的请求调用页面流。

具体要求:

  1. 页面大小为1K
  2. 用户内存容量为4页到40页
  3. 用户外存的容量为40k

在用户外存中,按每K存放10条指令,400条指令在外存中的存放方式为:

0-9条指令为第0页

10-19条指令为第1页

。。。。。

390-399条指令为第39页

按以上方式,用户指令可组成40页

  • 通过随机数产生一个指令序列,共400个指令(0-399)
  • 模拟请求页式存储管理中页面置换算法

执行一条指令,首先在外存中查找所对应的页面和页面号,然后将此页面调入内存中,模拟并计算下列各述算法在不同内存容量下的命中率(页面有效次数/页面流的个数): (注意:至少完成下面两个算法)

  1. 先进先出的算法(FIFO)
  2. 最久未使用算法(LRU)

提示

  • 随机指令的产生

rand() 或srand()

  • 用户内存中页面控制结构采用链表

页面控制结构

struct p_str{

         int pagenum; /* 页号 */

        int count; /* 访问页面的次数 */

        struct p_str next; /* 下一指针 */

}p_str;

#include <pthread.h>
#include <iostream>
#include <vector>
#include <set>
#include <semaphore.h>
#include <stdlib.h>
#include <unistd.h>

#define CommandCount 400	// 指令数
#define PageCount 40		// 页面数
#define MinPage 4			// 用户内存中最少4页
#define MaxPage 40			// 用户内存中最多40页
#define K 10                // 缓冲区数量    

// 用户内存中页面控制结构
typedef struct p_str {
    int pagenum;		/* 页号 */
    int count;			/* 访问页面的次数 */
    struct p_str* next; /* 下一指针 */
}p_str;

pthread_mutex_t mutex;      // 互斥锁
sem_t s1, s2;               // 信号量s1, s2
int in = 0;    // in用于记录生产者生产的下一个商品应存贮在仓库的位置
int out = 0;   // out用于记录消费者消费的下一个商品在仓库中的位置
std::set<int> Commands;   // set集合快速判断该指令是否产生
std::vector<int> CommandSequence;   // 记录生成的指令序列
int buffer[K];              // 缓冲区

void produce(int& product);
void* producer(void* ptr);
void* consumer(void* ptr);
bool initList(p_str*& L, int size);
void destoryList(p_str*& L);
int getPageNum(int command);
bool containPageInList(p_str* L, int PageNum);
double FIFO(int size);
bool initStack(p_str*& S);
void destoryStack(p_str*& S);
bool containPageInStack(p_str* S, int PageNum);
double LRU(int size);

int main(void)
{
    // 通过生产者消费者线程,产生指令序列
    pthread_t pro, con;

    sem_init(&s1, 0, K);        // 空缓冲区数量为K
    sem_init(&s2, 0, 0);        // 满缓冲区数量为0

    // int pthread_mutex_init(pthread_mutex_t * restrict mutex, const pthread_mutexattr_t * restrict attr)
    // 参数 1:定义的锁;参数2: 互斥锁属性,通常传 NULL);
    pthread_mutex_init(&mutex, NULL);

    // 创建线程
    pthread_create(&con, 0, consumer, 0);
    pthread_create(&pro, 0, producer, 0);
    pthread_join(pro, 0);
    pthread_join(con, 0);

    pthread_mutex_destroy(&mutex);  // 销毁互斥信号量

    sem_destroy(&s1);   // 销毁同步信号量
    sem_destroy(&s2);

    std::cout << "The instruction sequence has been generated!" << std::endl;

    for (int i = MinPage; i <= MaxPage; ++i) {
        double missRate1 = FIFO(i);
        double missRate2 = LRU(i);
        std::cout << "When the number of pages in memory is " << i
            << ", the page miss rate of FIFO is " << missRate1
            << ", the page miss rate of LRU is " << missRate2 
            << std::endl;
    }

    return 0;
}

void produce(int& product) {    // 生成一个指令
    product = rand() % CommandCount;
    while (Commands.count(product)) {
        product = rand() % CommandCount;
    }
    Commands.insert(product);
}

// 生产者线程
void* producer(void* ptr) {
    int i;
    for (i = 0; i < CommandCount; i++) {
        int product;
        produce(product);
        // 消耗一个空缓冲区,为0则阻塞等待
        sem_wait(&s1);
        // 缓冲区上锁
        pthread_mutex_lock(&mutex);

        buffer[in] = product;
        in = (in + 1) % K;

        // 缓冲区解锁
        pthread_mutex_unlock(&mutex);
        // 产生一个满缓冲区
        sem_post(&s2);
        // 睡眠1~100微秒
        int sleepTime = rand() % 100 + 1;
        usleep(sleepTime);
    }
    pthread_exit(0);
}

// 消费者线程
void* consumer(void* ptr) {
    int i;
    for (i = 0; i < CommandCount; i++) {
        // 消耗一个满缓冲区,为0则阻塞等待
        sem_wait(&s2);
        // 缓冲区上锁
        pthread_mutex_lock(&mutex);

        CommandSequence.push_back(buffer[out]);
        out = (out + 1) % K;

        // 缓冲区解锁
        pthread_mutex_unlock(&mutex);
        // 产生一个空缓冲区
        sem_post(&s1);
        // 睡眠1~100微秒
        int sleepTime = rand() % 100 + 1;
        usleep(sleepTime);
    }
    pthread_exit(0);
}

// 初始化页面链表(带头结点)
bool initList(p_str*& L, int size) {
    L = (p_str*)malloc(sizeof(p_str));
    if (L == NULL) {
        return false;
    }
    L->pagenum = -1;
    L->count = -1;
    L->next = NULL;

    p_str* p;
    p = L;
    for (int i = 0; i < size; ++i) {
        p_str* q = (p_str*)malloc(sizeof(p_str));
        q->pagenum = -1;        // 初始时页面链表中没用页面
        q->count = 0;

        p->next = q;
        p = q;
    }

    p->next = NULL;

    return true;
}

// 销毁链表
void destoryList(p_str*& L) {
    p_str* p = L->next;
    while (p != NULL) {
        p_str* q = p;
        p = p->next;
        free(q);
    }
    free(L);
}

// 在外存中查找该指令所对应的页面号
int getPageNum(int command) {
    return command / 10;
}

// 判断内存页面链表中是否有该页
bool containPageInList(p_str* L, int PageNum) {
    p_str* p = L->next;
    while (p != NULL) {
        if (p->pagenum == PageNum) {
            p->count++;     // 访问该页面的次数+1
            return true;
        } else {
            p = p->next;
        }
    }

    return false;
}

// 先进先出(FIFO)页面置换算法
double FIFO(int size) {    // size为内存中的最大页面数
    p_str* L;           // 用户内存中的页面链表

    initList(L, size);
        
    int missCount = 0;  // 缺页次数

    p_str* p;   // p指向的结点的下一个结点为将要替换出去的页面
    p = L;

    for (int i = 0; i < CommandCount; ++i) {
        // 模拟在外存中查找该页所对应的页面号
        int pageNum = getPageNum(CommandSequence[i]);

        if (!containPageInList(L, pageNum)) {     // 如果该页不在内存中,则换入
            p = p->next;            // 让p指向所要替换的页面
            if (p == NULL) {        // 如果p指向NULL,则让p指向第一个结点
                p = L->next;
            }

            // 更新页面信息
            p->pagenum = pageNum;
            p->count = 0;
            missCount++;
        }
    }

    destoryList(L);

    return (double) missCount / CommandCount;
}

// 初始化链栈(带头结点)
bool initStack(p_str*& S) {
    S = (p_str*)malloc(sizeof(p_str));
    if (S == NULL) {
        return false;
    }

    S->pagenum = -1;
    S->count = -1;
    S->next = NULL;

    return true;
}

// 销毁链栈
void destoryStack(p_str*& S) {
    p_str* p = S->next;
    while (p != NULL) {
        p_str* q = p;
        p = p->next;
        free(q);
    }

    free(S);
}

// 判断页面是否在链栈中
// 如果存在,则将该页面调到栈顶
bool containPageInStack(p_str* S, int PageNum) {
    p_str* p = S;
    while (p->next != NULL) {
        if (p->next->pagenum == PageNum) {  // 如果该页在链栈中
            p->next->count++;     // 访问该页面的次数+1

            // 将该页调到栈顶
            p_str* q = p->next;
            p->next = q->next;
            q->next = S->next;
            S->next = q;

            return true;
        }
        else {
            p = p->next;
        }
    }

    return false;
}

// 最近最近未使用(LRU)页面置换算法
double LRU(int size) {      // size为内存中的最大页面数
    p_str* S;               // 用户内存中的页面链栈(这里用栈,栈底为最近最近未使用页面)
    int S_size = 0;         // 记录链栈的大小

    initStack(S);

    int missCount = 0;  // 缺页次数
    
    for (int i = 0; i < CommandCount; ++i) {
        // 模拟在外存中查找该页所对应的页面号
        int pageNum = getPageNum(CommandSequence[i]);

        if (!containPageInStack(S, pageNum)) {     // 如果该页不在内存中,则换入
            if (S_size < size) {        // 当物理块未用完,直接压入;
                p_str* p = (p_str*)malloc(sizeof(p_str));
                p->pagenum = pageNum;
                p->count = 0;
                p->next = S->next;
                S->next = p;
                S_size++;
            } else {    // 物理块用完,则换出最近最久未使用页面
                p_str* p = S;
                while (p->next->next != NULL) {
                    p = p->next;
                }
                p_str* q = p->next; // q指向要换出的页面;
                p->next = q->next;
                free(q);

                // 将新页面压入栈顶
                p = (p_str*)malloc(sizeof(p_str));
                p->pagenum = pageNum;
                p->count = 0;
                p->next = S->next;
                S->next = p;
            }

            missCount++;
        }
    }

    destoryStack(S);

    return (double)missCount / CommandCount;
}

结果:

 

说明:该程序是利用生产者消费者线程产生随机的指令序列,生产者随机产生一个0~399编号的商品(不重复),放入缓冲区内,消费者从缓冲区拿出一个商品,放入指令序列CommandSequence中,为了使生产者消费者进程也具有随机性,设置生产者消费者进程随机睡眠1~100微妙。

FIFO算法采用链表结构,每次换出最先进入该链表的页面,可以看到,随着内存中页面数量的增加,缺页率在逐步减少。

LRU算法采用链栈结构,每次换出最近最近未使用的页面(即处于栈底的页面),栈顶始终是最新被访问页面的编号。可以看到,随着内存中页面数量的增加,缺页率在逐步减少。


FIFO算法比较容易实现,利用单链表就可以很容易的完成。

LRU算法根据汤小丹的《操作系统》第四版中介绍(最新版P176页),LRU算法的硬件支持须有寄存器和栈两类硬件之一的支持,我这里采用链栈来实现LRU算法(具体实现看代码或书,原理比较简单,只需要关注一些细节方面的实现就行)。

另外,这里的生产者消费者问题我是通过线程来实现,用进程实现生产者消费者问题请看我另一篇博客:

进程的同步与互斥_陈阿土i的博客-CSDN博客

;