目录
项目完整代码
GitHub查看完整代码{/supine0703/OS_QASys}
前言
在操作系统地址映射过程中,若在页面中发现所要访问的页面不再内存中,则产生缺页中断。当发生缺页中断时操作系统必须在内存选择一个页面将其移出内存,以便为即将调入的页面让出空间。而用来选择淘汰哪一页的规则叫做页面置换算。
本文通过分析操作系统进程同步与互斥的问题,然后利用互斥锁和线程模拟进程与同步,并利用该算法设计了生产者-消费者模拟演示系统。生产者模拟多个进程生产指令放入指令队列中,消费者模拟处理机从指令队列中取出指令并映射为页进行处理。利用Qt产生动画,模拟演示这一过程更加生动形象,可以用于操作系统的教学中。
项目效果
开始页面
运行页面
代码
本文只讲实现方法和部分代码,详细代码可点击文章开头链接进入github查看。
利用Qt实现动画效果
内存块模型
因为实现页面置换算法,是内存块,所以可以使用矩形+内容表示可视化,于是便可以利用QLabel实现。
#include <QLabel>
#include <QEasingCurve>
#define SHOWLABEL_ANIMATION_DEFAULT_TIME 1000
class ShowLabel : public QLabel
{
Q_OBJECT
private:
bool flg = false; // flag showing animation.
void Init();
void Mark(const QString&);
public:
ShowLabel(QWidget *parent = nullptr);
ShowLabel(const QString &text, QWidget *parent = nullptr);
ShowLabel(int number, QWidget *parent = nullptr);
void SetHexText(int number); // set hex number in text.
void MarkRed(); // the word is marker in red.
void MarkBlue(); // the word is marker in blue.
void MarkPurple(); // the word is marker in purple.
void UnMark(); // restore default.
bool ShowMoveTo(
const QPoint &pos,
int msecs = SHOWLABEL_ANIMATION_DEFAULT_TIME,
QEasingCurve::Type = QEasingCurve::InOutSine
); // show move to absolute coord.
bool ShowMove(
int x, int y,
int msecs = SHOWLABEL_ANIMATION_DEFAULT_TIME,
QEasingCurve::Type = QEasingCurve::InOutSine
); // show move to relative coord.
void ShowForcingMove(
int x, int y,
int msecs = SHOWLABEL_ANIMATION_DEFAULT_TIME
); // forcing show move to relative coord.
bool ShowLoad(
int msecs = SHOWLABEL_ANIMATION_DEFAULT_TIME,
const QString &color = "blue"
); // show process of load.
private:
void AnimationMoveTo(
const QPoint&, int, QEasingCurve::Type
); // show animation about move.
QPoint endPos;
public:
static void SetSpeed(int rate);
private:
static float speedRate;
signals:
void animation_finished();
};
基于Label实现动画
可以使用Qt提供的QPropertyAnimation类。下面是分别通过变换size和pos实现位移和加载的效果动画。
bool ShowLabel::ShowLoad(int msecs, const QString &color)
{
if (flg) return false;
flg = true;
this->setStyleSheet(QString("background: %1; color: %1;").arg(color));
QPropertyAnimation *animation = new QPropertyAnimation(this, "size");
animation->setDuration(msecs * speedRate); // time
animation->setStartValue(QSize(1, this->height()));
animation->setEndValue(this->size());
// animation->setEasingCurve(QEasingCurve::InOutQuad); // easing curve
animation->start();
connect(animation, &QPropertyAnimation::finished, this, [this]() {
this->MarkBlue();
flg = false;
emit animation_finished();
});
return true;
}
void ShowLabel::AnimationMoveTo(const QPoint &pos, int msecs, QEasingCurve::Type curve)
{
QPropertyAnimation *animation = new QPropertyAnimation(this, "pos");
animation->setDuration(msecs * speedRate); // time
animation->setStartValue(this->pos()); // start pos
animation->setEndValue(endPos = pos); // end pos
animation->setEasingCurve(curve); // easing curve
animation->start();
connect(animation, &QPropertyAnimation::finished, this, [this]() {
flg = false;
emit animation_finished();
});
}
bool ShowLabel::ShowMoveTo(const QPoint &pos, int msecs, QEasingCurve::Type curve)
{
if (flg) return false;
flg = true;
AnimationMoveTo(pos, msecs, curve);
return true;
}
bool ShowLabel::ShowMove(int x, int y, int msecs, QEasingCurve::Type curve)
{
return ShowMoveTo(this->pos() + QPoint(x, y), msecs, curve);
}
void ShowLabel::ShowForcingMove(int x, int y, int msecs)
{
flg = true;
AnimationMoveTo(endPos + QPoint(x, y), msecs, QEasingCurve::OutCubic);
}
其他函数
Mark函数通过修改样式标给予不同标记,方便呈现不同效果。
void ShowLabel::Mark(const QString &color)
{
this->setStyleSheet(
QString("border: 2px solid black; color: %1; background: white;").arg(color)
);
}
void ShowLabel::MarkRed()
{
this->Mark("red");
}
void ShowLabel::MarkBlue()
{
this->Mark("blue");
}
void ShowLabel::MarkPurple()
{
this->Mark("purple");
}
void ShowLabel::UnMark()
{
this->Mark("black");
}
缓冲队列实现
#include <QObject>
#include <QMutex>
#include "showlabel.h"
class BuffQue : public QObject
{
Q_OBJECT
BuffQue(const BuffQue&) = delete;
BuffQue& operator=(const BuffQue&) = delete;
public:
explicit BuffQue(int queSize, QWidget *parent = nullptr);
bool Push(ShowLabel *page);
bool Pop(ShowLabel *&page);
void MoveTo(const QPoint& pos);
private:
QLabel *back; // background
QPoint basicPos; // where buffer in widget
QVector<ShowLabel*> buffer;
QMutex mutex; // let push or pop one-off finish
int head = 0, tail = 0; // head output, tail input
int count = 0; // count in buffer
bool ibusy = false, obusy = false; // is busy before showing animation finished
bool stpFlg = true;
public:
static void SetSpeed(int rate);
private:
static float speedRate;
signals:
void push_finished();
void pop_finished();
void is_not_full();
void is_not_empty();
};
缓冲队列是互斥访问的,入队操作一次只能由一个生产者执行,其余被阻塞,出队也是只能由一个消费者执行出队操作。
利用锁一气呵成实现进队出队,利用busy标记动画是否结束。push是当动画结束才count++算完全加入队列,而pop是count--后才执行动画,就是说pop成功的那一刻就默认出队完成。
#include "buffque.h"
BuffQue::BuffQue(int queSize, QWidget *parent)
: QObject{parent}
, back(new QLabel(parent))
{
Q_ASSERT(parent != nullptr);
Q_ASSERT(4 <= queSize && queSize <= 24);
back->resize(84, queSize * 40 + 4);
back->setStyleSheet("background: gray;");
buffer = QVector<ShowLabel*>(queSize);
this->MoveTo(QPoint(0, 0));
}
bool BuffQue::Push(ShowLabel *page)
{
if (count == buffer.size()) // is full
return false;
mutex.lock();
if (ibusy)
{
mutex.unlock();
return false;
}
ibusy = true;
bool empty = (count == 0);
// push {
buffer[tail] = page; // push
tail = (tail + 1) % buffer.size();
// }
// show pushing animation
page->MarkRed();
// move to buffer's position which belong
if (!page->ShowMoveTo(basicPos + QPoint(0, count * 40), 1000 * speedRate))
Q_ASSERT_X(false, "class BuffQue", "try show animation which showing");
connect(page, &ShowLabel::animation_finished, this, [this, empty, page]() {
page->disconnect(this);
page->UnMark();
count++; // push successfully
ibusy = false; // animation finished
emit push_finished();
if (empty) {
emit is_not_empty();
}
});
mutex.unlock();
return true;
}
bool BuffQue::Pop(ShowLabel *&page)
{
if (count == 0) // is empty
return false;
mutex.lock();
if (obusy)
{
mutex.unlock();
return false;
}
obusy = true;
bool full = (count == buffer.size());
// pop {
page = buffer[head]; // pop
head = (head + 1) % buffer.size();
count--; // } push successfully
// show poping animation
page->MarkPurple();
for (int i = head; i != tail; i = (i + 1) % buffer.size())
buffer[i]->ShowForcingMove(0, -40, 1000 * speedRate); // all move up 40
if (!page->ShowMove(80, 0, 596 * speedRate, QEasingCurve::OutExpo))
Q_ASSERT_X(false, "class BuffQue", "try show animation which showing");
connect(page, &ShowLabel::animation_finished, this, [this, full, page]() {
page->disconnect(this);
obusy = false; // animation finished
emit pop_finished();
if (full) {
emit is_not_full();
}
});
mutex.unlock();
return true;
}
void BuffQue::MoveTo(const QPoint &pos)
{
basicPos = pos + QPoint(2, 2);
back->move(pos);
int cunt = 0; // calculate the relativate position
if (head == tail && !count) // is empty
return;
buffer[head]->move(basicPos + QPoint(0, cunt++ * 40));
for (int i = (head + 1) % buffer.size(); i != tail; i = (i + 1) % buffer.size())
buffer[i]->move(basicPos + QPoint(0, i * 40));
}
float BuffQue::speedRate = 1;
void BuffQue::SetSpeed(int rate)
{
speedRate = 0.2 + 0.018 * rate;
}
生产者实现
#include <QObject>
#include <QQueue>
class QThread;
class BuffQue;
class RandomProducer;
class Producers : public QObject
{
Q_OBJECT
Producers(const Producers&) = delete;
Producers& operator=(const Producers&) = delete;
public:
explicit Producers(int num, BuffQue *buffer, QWidget *parent = nullptr);
void MoveTo(const QPoint& pos);
void Start();
void Stop();
private:
BuffQue *bufque;
QVector<RandomProducer*> ers;
QQueue<int> productQue;
bool stpFlg = true;
signals:
void start_produce();
void try_send_to_buffer();
};
#include "producers.h"
#include "randomproducer.h"
#include "buffque.h"
#include <QThread>
Producers::Producers(int num, BuffQue *buffer, QWidget *parent)
: QObject{parent}
, ers(QVector<RandomProducer*>(num))
, bufque(buffer)
{
Q_ASSERT(parent != nullptr);
Q_ASSERT(0 < num && num <= 10);
Q_ASSERT(buffer != nullptr);
for (int i = 0; i < num; i++)
{ // 创建可以产生随机指令的生产者们
ers[i] = new RandomProducer(parent);
ers[i]->MoveTo(QPoint(0, 80 * i)); // 移动到指定位置
// 开始生产产品(随机指令)显示动画
connect(this, &Producers::start_produce, this, [this, i]() {
if (!stpFlg) {
ers[i]->Produce(static_cast<QWidget*>(this->parent()));
}
});
// 生产完成,尝试加入缓冲区
connect(ers[i], &RandomProducer::produce_finished, this, [this, i]() {
productQue.enqueue(i);
emit try_send_to_buffer();
});
// 加入缓冲区后生产新的产品
connect(ers[i], &RandomProducer::send_inc_finished, this, &Producers::start_produce);
}
// 缓冲区不为满、缓冲区进入产品完成,都会试图将产品送入缓冲区
connect(bufque, &BuffQue::push_finished, this, &Producers::try_send_to_buffer);
connect(bufque, &BuffQue::is_not_full, this, &Producers::try_send_to_buffer);
// 尝试进入缓冲区
connect(this, &Producers::try_send_to_buffer, this, [this]() {
if (!productQue.empty()) { // 产品队列不为空
if (ers[productQue.head()]->SendInc(bufque)) {
productQue.dequeue(); // 如果成功送入缓冲区
}
}
});
}
void Producers::MoveTo(const QPoint &pos)
{
int i = 0;
for (auto& er : ers)
er->MoveTo(pos + QPoint(0, 80 * i++));
}
void Producers::Start()
{
stpFlg = false;
emit start_produce();
}
void Producers::Stop()
{
stpFlg = true;
}
消费者实现
#include <QObject>
class UpdateAddBolck;
class ShowLabel;
class BuffQue;
class Memory;
class Consumers : public QObject
{
Q_OBJECT
Consumers(const Consumers&) = delete;
Consumers& operator=(const Consumers&) = delete;
public:
explicit Consumers(int size, BuffQue* buffer, QWidget *parent = nullptr);
virtual ~Consumers() { }
void MemoryMoveTo(const QPoint& pos);
void MemDataMoveTo(const QPoint& pos);
void Start(); // start to consume
void Stop(); // stop to consume
private:
void ConctUpdate(); // need update data about Memory if consume finished
bool MissingPage();
void UpdateData();
ShowLabel *label; // use to receive what buffer pop
bool stpFlg = true;
protected:
Memory* mem;
int hitPage = -1;
signals:
void start_consumer();
};
#include "consumers.h"
#include "buffque.h"
#include "memory.h"
Consumers::Consumers(int size, BuffQue *buffer, QWidget *parent)
: QObject{parent}
, mem(new Memory(size, parent))
{
Q_ASSERT(parent != nullptr);
mem->MarkRed(0);
// 开始消费
connect(this, &Consumers::start_consumer, mem, [this, buffer]() {
if (!stpFlg && !mem->Busy()) {
buffer->Pop(label); // 尝试出队一个产品
}
});
// 产品出队完成
connect(buffer, &BuffQue::pop_finished, this, [this]() {
mem->data->total++;
if (MissingPage()) { // replace page which is marked if missing page
// 如果产生缺页,则置换标记位置
if (mem->Replace(label)) { // 如果置换成功更新数据
ConctUpdate();
} else { // if page is showing other animation
// 如果没更新成功,等动画播放完成重新更新
connect(label, &ShowLabel::animation_finished, this, [this]() {
label->disconnect(this);
mem->Replace(label);
ConctUpdate();
});
}
}
});
// 置换完成或队列不为空则再次尝试消费
connect(mem, &Memory::mem_replace_finished, this, &Consumers::start_consumer);
connect(buffer, &BuffQue::is_not_empty, this, &Consumers::start_consumer);
}
void Consumers::MemoryMoveTo(const QPoint &pos)
{
mem->MoveTo(pos);
}
void Consumers::MemDataMoveTo(const QPoint &pos)
{
mem->MemDataMoveTo(pos);
}
void Consumers::Start()
{
stpFlg = false;
emit start_consumer();
}
void Consumers::Stop()
{
stpFlg = true;
}
void Consumers::ConctUpdate()
{
connect(label, &ShowLabel::animation_finished, this, [this]() {
label->disconnect(this);
UpdateData();
});
}
bool Consumers::MissingPage()
{ // 缺页除了遍历查找是否命中,其余同上方一样,区别在如果命中则并于命中页中
for (int i = 0, end = mem->memBlocks.size(); i < end; i++)
{
if (mem->memBlocks[i]->memBlock->text() == "")
break; // if memory is not full
else if (mem->memBlocks[i]->memBlock->text().toInt(nullptr, 16)
== (label->text().toInt(nullptr, 16) >> 4))
{ // replace page_i if not missing page
label->MarkBlue();
if (mem->Replace(label, i))
ConctUpdate();
else
{ // if page is showing other animation
connect(label, &ShowLabel::animation_finished, this, [this, i]() {
label->disconnect(this);
mem->Replace(label, i);
ConctUpdate();
});
}
mem->data->hit++;
hitPage = i;
return false;
}
}
// missing page
label->MarkRed();
mem->data->miss++;
hitPage = -1;
return true;
}
void Consumers::UpdateData()
{
mem->data->replace = mem->data->miss - mem->memBlocks.size();
if (mem->data->replace < 0)
mem->data->replace = 0;
mem->data->hitRate = 1.0 * mem->data->hit / mem->data->total;
mem->data->missRate = 1.0 * mem->data->miss / mem->data->total;
mem->data->Update();
}
页面置换算法
#include "memblock.h"
#include "consumers.h"
class FIFOConsumer : public Consumers, public UpdateAddBolck
{ // 先进先出算法
public:
FIFOConsumer(int size, BuffQue* buffer, QWidget *parent = nullptr);
void Update() override;
private:
int p = 0; // pointer to the page to be replaced
int count = 0; // input which count
};
class LRUConsumer : public Consumers, public UpdateAddBolck
{ // 最近最久未使用算法
public:
LRUConsumer(int size, BuffQue* buffer, QWidget *parent = nullptr);
void Update() override;
private:
int p = 0; // pointer to the page to be replaced
QVector<int> time; // time of each page from last access time
};
class LFUConsumer : public Consumers, public UpdateAddBolck
{ // 最少使用置换算法
public:
LFUConsumer(int size, BuffQue* buffer, QWidget *parent = nullptr);
void Update() override;
private:
int p = 0; // pointer to the page to be replaced
QVector<int> count; // count of all each page
};
class NRUConsumer : public Consumers, public UpdateAddBolck
{ // 最近未用算法
public:
NRUConsumer(int size, BuffQue* buffer, QWidget *parent = nullptr);
void Update() override;
private:
int p = 0; // pointer to the page to be replaced
QVector<bool> flag; // flag about is last visited
};
#include "pagereplace.h"
#include "memory.h"
FIFOConsumer::FIFOConsumer(int size, BuffQue *buffer, QWidget *parent)
: Consumers(size, buffer, parent)
{
mem->data->SetAlgorithm("FIFO");
mem->SetInterface(this);
for (auto& m : mem->memBlocks)
m->addBlock->setText("0");
}
void FIFOConsumer::Update()
{
if (hitPage != -1)
{
if (mem->whoMark == hitPage)
mem->MarkRed(hitPage);
return;
}
mem->memBlocks[p]->addBlock->setText(QString::number(++count));
p = (p + 1) % mem->memBlocks.size();
mem->MarkRed(p);
}
//------------------------------------------------------------------------------
LRUConsumer::LRUConsumer(int size, BuffQue *buffer, QWidget *parent)
: Consumers(size, buffer, parent)
{
mem->data->SetAlgorithm("LRU");
mem->SetInterface(this);
time = QVector<int>(mem->memBlocks.size(), 0);
for (auto& m : mem->memBlocks)
m->addBlock->setText("0");
}
void LRUConsumer::Update()
{
bool missFlg = (hitPage == -1);
if (missFlg)
time[p] = -1;
for (int i = 0, end = mem->memBlocks.size(), max = 0; i < end; i++)
{
time[i]++;
mem->memBlocks[i]->addBlock->setText(QString::number(time[i]));
if (missFlg && time[i] > max)
{
max = time[i];
p = i;
}
}
if (missFlg)
mem->MarkRed(p);
else
{
time[hitPage] = 0;
mem->memBlocks[hitPage]->addBlock->setText("0");
if (mem->whoMark == hitPage)
mem->MarkRed(hitPage);
}
}
//------------------------------------------------------------------------------
LFUConsumer::LFUConsumer(int size, BuffQue *buffer, QWidget *parent)
: Consumers(size, buffer, parent)
{
mem->data->SetAlgorithm("LFU");
mem->SetInterface(this);
count = QVector<int>(PAGE_MAX_NUM + 1, 0);
for (auto& m : mem->memBlocks)
m->addBlock->setText("0");
}
void LFUConsumer::Update()
{
auto index = [this](int num) {
return
(mem->memBlocks[num]->memBlock->text() == "") ? PAGE_MAX_NUM :
mem->memBlocks[num]->memBlock->text().toInt(nullptr, 16);
};
if (hitPage != -1)
{
int tmp = ++count[index(hitPage)];
mem->memBlocks[hitPage]->addBlock->setText(QString::number(tmp));
if (mem->whoMark == hitPage)
mem->MarkRed(hitPage);
return;
}
mem->memBlocks[p]->addBlock->setText(QString::number(++count[index(p)]));
p = 0;
for (int i = 0, end = mem->memBlocks.size(), min = 0x7fffffff; i < end; i++)
{
int tmp = index(i);
if (count[tmp] < min)
{
min = count[tmp];
p = i;
}
}
mem->MarkRed(p);
}
//------------------------------------------------------------------------------
NRUConsumer::NRUConsumer(int size, BuffQue *buffer, QWidget *parent)
: Consumers(size, buffer, parent)
{
mem->data->SetAlgorithm("NRU");
mem->SetInterface(this);
flag = QVector<bool>(mem->memBlocks.size(), 0);
for (auto& m : mem->memBlocks)
m->addBlock->setText("0");
}
void NRUConsumer::Update()
{
if (hitPage != -1)
{
flag[hitPage] = 1;
mem->memBlocks[hitPage]->addBlock->setText("1");
if (mem->whoMark == hitPage)
mem->MarkRed(hitPage);
return;
}
flag[p] = 1;
mem->memBlocks[p]->addBlock->setText("1");
p = (p + 1) % flag.size();
while (flag[p])
{
flag[p] = 0;
mem->memBlocks[p]->addBlock->setText("0");
p = (p + 1) % flag.size();
}
mem->MarkRed(p);
}