操作系统实验四-同步机构(一)

简介: 操作系统实验四-同步机构

一、实验内容

 模拟实现用同步机构避免并发进程执行时可能出现的与时间有关的错误。

 实验报告要求:

 (1)实验题目。

 (2)打印源程序并附上注释。

 (3)从键盘上输入一组字符,由生产者每次读入一个字符供消费者输出。运行模拟程序,打印依次读入的字符和消费者输出的字符。

 (4)把生产者和消费者进程中的P操作、V操作都改成空操作指令,观察在两者不同步的情况下可能出现的与时间有关的错误。打印依次读入的字符和消费者输出的字符。

二、实验目的

 进程是程序在一个数据集合上运行的过程,进程是并发执行的,也即系统中的多个进程轮流地占用处理器运行。

 我们把若干个进程都能进行访问和修改的那些变量称为公共变量。由于进程是并发执行的,所以,如果对进程访问公共变量不加限制,那么就会产生“与时间有关”的错误,即进程执行后,所得到的结果与访问公共变量的时间有关。为了防止这类错误,系统必须要用同步机构来控制进程对公共变量的访问。一般说,同步机构是由若干条原语——同步原语——所组成。本实验要求学生模拟PV操作同步机构的实现,模拟进程的并发执行,了解进程并发执行时同步机构的作用。

三、实验题目

模拟PV操作同步机构,且用PV操作解决生产者——消费者的问题。

(1) PV操作同步机构,由P操作原语和V操作原语组成,它们的定义如下:

P操作原语P(s):将信号量s减去1,若结果小于0,则执行原语的进程被置成等待信号量s的状态。

V操作原语V(s): 将信号量s加1,若结果不大于0,则释放一个等待信号量s的进程。

(2)生产者-消费者问题

假定有一个生产者和消费者,生产者每次生产一件产品,并把生产的产品存入共享缓冲器以供消费者取走使用。消费者每次从缓冲器内取出一件产品去消费。禁止生产者将产品放入已满的缓冲器内,禁止消费者从空缓冲器内取产品。假定缓冲器内可同时存放10件产品。那么,用PV操作来实现生产者和消费者之间的同步。

(3)进程控制块PCB

为了记录进程执行时的情况、以及进程让出处理器后的状态、断点等信息,每个进程都有一个进程控制块PCB。在模拟实验中,假设进程控制块的结构如图1。其中进程的状态有:运行态、就绪态、等待态和完成态。当进程处于等待态时,在进程控制块PCB中要说明进程等待原因(在模拟实验中进程等待原因为等待信号量s 1 s_1s1s 2 s_2s2。当进程处于等待态或就绪态时,PCB 中保留了断点信息,一旦进程再度占有处理器则就从断点位置继续运行;当进程处于完成状态,表示进程执行结束。

进程名
状态
等待原因
断点

(4)处理器的模拟

 计算机硬件提供了一组机器指令,处理器的主要职责是解释执行机器指令。为了模拟生产者和消费者进程的并发执行,我们必须模拟一组指令和处理器职能。

 模拟的一组指令见图2,其中每条指令的功能由一个过程来实现。用变量PC来模拟“指令计数器”,假设模拟的指令长度为1,每执行一条模拟指令后, PC加1,指出下一条指令地址。使用模拟的指令,可把生产者和消费者进程的程序表示为图3的形式。定义两个一维数组PA[5]和SA[5],每一个PA[i]存放生产者程序中的一条模拟指令执行的入口地址;每个SA[i]存放消费者程序中的一条模拟指令执行的入口地址。于是模拟处理器执行条指令的过程为: 取出PC之值, 按PA[PC] 或SA[PC]得模拟指令执行的入口地址,将PC之值加1,转向由入口地址确定的相应的过程执行。

模拟的指令 功能
P(s) 执行P操作原语
V(s) 执行V操作原语
put B[in]=product;in=(in+1)%10
get x=B[out],out=(out+1)%10
produce 输出一个字符放入C中
consume 消费一个x中的字符
Goto L 跳转,回到第0个过程
nop 空操作
序号 生产者程序 消费者程序
0 produce P(s 2 s_2s2)
1 P(s 1 s_1s1) get
2 put V(s 2 s_2s2)
3 V(s 1 s_1s1) consume
4 Goto 0 Goto 0

(5)程序设计

 本实验中的程序由三部分组成:初始化程序、处理器调度程序、模拟处理器指令执行程序。各部分程序的功能及相互间的关系由图4至图7指出。

 初始化程序:模拟实验的程序从初始化程序入口启动,初始化工作包括对信号量S1、S2赋初值,对生产者、消费者进程的PCB初始化。初始化后转向处理器调度程序,其流程如图4。

 处理器调度程序:在计算机系统中,进程并发执行时,任一进程占用处理器执行完一条指令后就有可能被打断而让出处理器由其他进程运行。故在模拟系统中也类似处理,每当执行一条模拟的指令后,保护当前进程的现场,让它成为非运行状态,由处理器调度程序按随机数再选择一个就绪进程占用处理器运行。处理器调度程序流程见图5.

 模拟处理器指令执行程序:按“ 指令计数器”PC之值执行指定的过程,且PC加1指向下一条指令。模拟处理器指令执行的程序流程见图6和7。

 另外,为了使得模拟程序有一个结束条件,在图6中附加了“生产者运行结束”的条件判断,模拟时可以采取人工选择的方法实现。图7给出了P (S)和V (S)模拟指令执行过程的流程。其他模拟指令的执行过程已在图4-2中指出。

总体流程

处理器调度流程

处理器执行流程(实验指导书有误!

PV操作流程(实验指导书有误!!!

四、程序代码

1.数据结构与变量

struct PCB {//进程控制块
    string name;//进程名
    int status;  // 1 就绪,  2 等待,3 完成,0 正在运行
    int waitreason;//等待原因
    int breakingpoint;//断点
};

 因为本问题有且只有一个生产者进程,有且只有一个消费者进程,所以不需要用到队列等数据结构。只需一个指针指向当前运行的进程。

PCB producer;//生产者进程
PCB consumer;//消费者进程
PCB* currp;//当前进程

 本程序需要两个信号量,分别为s1和s2。

 s1值<0时,表示缓冲区已经放满了,生产者不能再放数据了。S2值>=0,表示有数据,可以让消费者去拿数据。

int s1;//信号量:生产者可以往缓冲区放物品
int s2;//消费者可以从缓冲区拿,生产者有物品

 同样的,也需要缓冲区。缓冲区命名为B,与图2是一致的。缓冲区的长度为10。设置指针in为生产者放的位置,out为消费者拿的位置。Char变量c为生产者生产的字符,变量x为消费者消费的字符。

int in;//生产者放的位置
int out;//消费者拿的位置
char B[10];
char c;//生产者生产的字符
char x;//消费者消费的字符

 Pc为程序计数器,指向生产者或消费者当前在运行哪条程序。生产者需要运行的程序地址在pa数组中,消费者运行的程序地址在sa数组中。“地址”并非真正的地址,在后文的算法说明中会体现,算法会读出“地址”之值,利用case语句根据“地址”值决定执行哪条指令。

int pc;
int pa[5];//生产者的程序
int sa[5];//消费者程序

2.程序设计与算法

 在主程序中,整体流程为:先初始化,后进行处理机运行。

int main() {
      init();
      cpuwork();
      return 0;
}

 init()函数进行初始化。在初始化时,先初始化信号量,再初始化当前生产者和消费者指向的缓冲区的位置0。初始化生产者进程和消费者进程的状态。初始化程序的“地址”。

void init() {
    s1 = 10;
    s2 = 0;//初始化信号量
    out = 0;
    in = 0;//初始化生产者与消费者在缓冲区放的商品的位置
    producer.name = "producer";
    producer.status = 1;
    producer.waitreason = 0;
    producer.breakingpoint = 0;//pcb状态为就绪,断点为0
    consumer.name = "consumer";
    consumer.status = 1;
    consumer.waitreason = 0;
    consumer.breakingpoint = 0;//pcb状态为就绪,断点为0
    currp = &producer;//将现行进程设置为生产者进程
    pc = 0;//pc=0
    for (int i = 0; i <= 4; i++) {
        pa[i] = i;
        sa[i] = i;//初始化生产者程序与消费者程序
    }
}

 P操作根据信号量值决定当前状态是被阻塞,并设置断点,或者是程序能够继续就绪运行。

void p(int& s, int p, PCB* pcb) {
    s = s - 1;
    cout << "mutex s" << p<<endl;
    if (s < 0) {
        pcb->status = 2;//等待状态
        pcb->waitreason = p;//等待的信号量是p
        cout << pcb->name << " begins waiting!\n";
    }
    else {
        cout << pcb->name << " doesn't need waiting!\n";
        pcb->status = 1;//将调用p(s)的进程置为就绪
    }
}

 V操作根据信号量值决定是否要将等待当前信号量的进程设置为就绪。需要说明的是,判断条件是s<=0,而不是s<0。指导书有误

void v(int& s, int p, PCB* pcb) {
    s = s + 1;
    if (s <= 0) {
        if (producer.waitreason == p)
            producer.status = 1;
        else if (consumer.waitreason == p)
            consumer.status = 1;//将一个等待s信号量的进程置为就绪
        if (currp->name == "producer")
            cout << "consumer released!";
        else
            cout << "producer released!";
    }
    pcb->status = 1;//将调用V(s)的进程置为就绪
}

 在处理机调度时,若有多个就绪进程,随机选择一个就绪进程置为运行态。把pcb断点值赋值,并对该进程进行运行。

while (1) {
        currp->breakingpoint = pc;//当前进程pcb断点为pc
        if (consumer.status != 1 && producer.status != 1)
            return;//无就绪进程
        if (consumer.status == 1 && producer.status == 1) {
            srand(time(NULL));//随机选择一个就绪进程
            int randnum = rand() % 2;
            if (randnum >= 1)
                currp =& consumer;
            else
                currp =& producer; 
        }
        else if (consumer.status == 1)
            currp = &consumer;
        else if(producer.status==1)
            currp = &producer;
        currp->status = 0;//将现行进程改为运行态
        pc = currp->breakingpoint;//现行进程pcb断点值赋值给pc
        exec();
    }

 在处理器执行当前正在运行的程序时,利用程序计数器选择当前进程指令“地址”i,通过“地址”i找到需要运行的指令j。之后,程序计数器指向下一条地址。利用case语句,根据j值执行对应指令。指令如图3所示。每条指令的内容如图2所示。可以发现,对于每条指令,在程序中的执行与图表要求执行的语句是一致的。

void exec() {//模拟处理器指令执行
    int i = pc;
    int j;
    int curq;
    cout << "current process " << currp->name << endl;
    if (currp->name == "producer") {//现行进程为生产者?
        j = pa[i];
        curq = 0;
    }
    else {
        j = sa[i];
        curq = 1;
    }
    pc = i + 1;
switch (j) {//按j转向各模拟指令对应的过程
    case 0:
        if (curq == 0) {//produce
            char pronum;
            cout << "please produce a char";
            cin >> pronum;
            c = pronum;
            cout << "produced!";
            currp->status = 1;
        }
        else
            p(s2, 2, currp);//p(s2)
        break;
    case 1:
        if (curq == 0)
            p(s1, 1, currp);//p(s1)
        else {//GET
            cout << "get goods!";
            x = B[out];
            out = (out + 1) % 10;
            cout << x << endl;
            currp->status = 1;
        }
        break;
    case 2:
        if (curq == 0) {//PUT
            cout << "put goods!";
            B[in] = c;
            in = (in + 1) % 10;
            cout << c << endl;
            currp->status = 1;
        }
        else
            v(s1, 1, currp);//V(s1)
        break;
    case 3:
        if (curq == 0)//V(s2)
            v(s2, 2, currp);
        else {//consume
            cout << "goods consumed!";
            cout << x << endl;
            currp->status = 1;
        }
        break;
    case 4://goto 0
        pc = 0;
        currp->status = 1;
        cout << "goto 0!";
        break;
    }
    if (producer.status != 3) {//模拟时采用人工选择的办法实现“生产者运行结束”的判断
        cout << "Has producer finished?Y/N";
        char st;
        cin >> st;
        if (st == 'Y')
            producer.status = 3;
    }
    return;
}


目录
相关文章
|
1月前
|
算法
数据结构实验之操作系统打印机管理器问题
本实验旨在通过实现操作系统中的打印机管理器问题,掌握队列的基本操作如入队、出队等,利用队列的先进先出特性解决先申请先打印的问题。实验包括队列的初始化、入队、出队、打印队列内容等功能,并通过菜单式界面进行交互。实验结果显示基本功能可正常执行,但在连续操作时存在执行失败的情况,需进一步优化。
42 4
|
5月前
|
弹性计算 运维
阿里云操作系统智能助手OS Copilot实验测评报告
**OS Copilot 产品体验与功能反馈摘要** 运维人员发现OS Copilot易上手,文档清晰,助其高效排查故障(8/10分)。愿意推荐并参与开源开发。亮点在于知识问答,能快速筛选答案。相较于竞品,优点是新手友好、文档清晰,但功能扩展性待增强。期望增加系统错误排查与解决方案,并集成ECS等,以优化系统安装流程。
阿里云操作系统智能助手OS Copilot实验测评报告
|
4月前
|
安全
操作系统中的同步和监视器经典问题
【8月更文挑战第23天】
39 0
|
5月前
|
弹性计算 运维 自然语言处理
阿里云操作系统智能助手OS Copilot实验测评报告
OS Copilot是针对Linux的智能助手,助力学习、运维及编程。用户界面直观,自然语言交互方便新手。官方文档详尽,但初次配置略复杂,适合学生和开发者。在提高代码编写和调试效率、系统学习上得分高,功能亮点包括代码生成、问答和命令执行。用户期待更多操作系统支持、自动错误分析和系统排查功能。
190 3
|
5月前
|
弹性计算 人工智能 运维
阿里云操作系统智能助手OS Copilot实验测评报告
阿里云操作系统智能助手OS Copilot实验测评报告
115 2
|
5月前
|
弹性计算 运维
阿里云操作系统智能助手OS Copilot的实验测评报告
OS Copilot 产品体验摘要 用户角色与场景:一位计算机学生使用辅助学习和解决问题,特别是通过代码解释功能加深理解。 易用性与文档:初者可能会觉得有些细节不明确。 帮助程度:用户给予极高评价,对学习帮助大,评分10分,快速定位和解决代码问题,提升学习效率。 推荐与参与:用户愿意推荐给他人。 功能体验:用户尝试了所有功能,对知识问答、辅助编程和命令执行特别感兴趣,尤其是命令执行帮助大。 对比其他产品:OS Copilot优点是便捷、准确。 期望功能:用户希望增加自动报错分析和系统错误排查。 联动体验:用户期待,以实现更全面的工具集。 总结:整体体验积极,用户看好其潜力,期待改进和未来联动。
|
5月前
|
弹性计算 运维 Python
阿里云操作系统智能助手OS Copilot实验测评报告
**OS Copilot 产品测评摘要** - 学生使用,用于学习和编码,发现上手难度较高,指引文档不清晰,特别是Access ID设置和代码复制流程。 - 功能上,评分9分,辅助编程和知识问答功能显著提升了学习效率,减少了错误。 - 愿意推荐,并有兴趣参与开源开发以提升自我。 - 希望增强错误排查,提供具体错误原因和位置。 - 联动ACK智能助手可增强学习效果。 [链接]: https://developer.aliyun.com/topic/instructions-for-os-copilot
|
1月前
|
安全 Linux 数据安全/隐私保护
Vanilla OS:下一代安全 Linux 发行版
【10月更文挑战第30天】
59 0
Vanilla OS:下一代安全 Linux 发行版
|
1月前
|
NoSQL Linux PHP
如何在不同操作系统上安装 Redis 服务器,包括 Linux 和 Windows 的具体步骤
本文介绍了如何在不同操作系统上安装 Redis 服务器,包括 Linux 和 Windows 的具体步骤。接着,对比了两种常用的 PHP Redis 客户端扩展:PhpRedis 和 Predis,详细说明了它们的安装方法及优缺点。最后,提供了使用 PhpRedis 和 Predis 在 PHP 中连接 Redis 服务器及进行字符串、列表、集合和哈希等数据类型的基本操作示例。
59 4