利用Qt制作可视动画模拟生产者-消费者问题且页面置换算法

目录

项目完整代码

前言

项目效果

开始页面

运行页面

代码

利用Qt实现动画效果

内存块模型

基于Label实现动画

其他函数

缓冲队列实现

生产者实现

消费者实现

页面置换算法


项目完整代码

​​​​​​​​​​​​​​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);
}

  • 8
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Leisure_水中鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值