记录于2022.7.7——南林操作系统课设心得
这个实验比较有意思,我理解题目都理解了半天,而且网上很少有关于本实验的博客,于是最后按我的理解来实现了这个程序:先用生产者消费者来产生一个随机的指令序列,然后再用FIFO和LRU算法实现页面置换。题目中所说的“用户内存容量为4页到40页”我则理解为用户内存容量从4一直增加到40,分别计算从4到40之间这两种算法的缺页率
<任务>
用程序实现生产者——消费者问题
将指令序列转换为用户虚存中的请求调用页面流。
具体要求:
- 页面大小为1K
- 用户内存容量为4页到40页
- 用户外存的容量为40k
在用户外存中,按每K存放10条指令,400条指令在外存中的存放方式为:
0-9条指令为第0页
10-19条指令为第1页
。。。。。
390-399条指令为第39页
按以上方式,用户指令可组成40页
- 通过随机数产生一个指令序列,共400个指令(0-399)
- 模拟请求页式存储管理中页面置换算法
执行一条指令,首先在外存中查找所对应的页面和页面号,然后将此页面调入内存中,模拟并计算下列各述算法在不同内存容量下的命中率(页面有效次数/页面流的个数): (注意:至少完成下面两个算法)
- 先进先出的算法(FIFO)
- 最久未使用算法(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算法(具体实现看代码或书,原理比较简单,只需要关注一些细节方面的实现就行)。
另外,这里的生产者消费者问题我是通过线程来实现,用进程实现生产者消费者问题请看我另一篇博客: