一.实验内容
模拟实现用同步机构避免并发进程执行时可能出现的与时间有关的错误。
二.实验题目
模拟 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。在模拟实验中,假设进程控制块的结构如图 2-1。其中进程的状态有:运行态、就绪态、等待态和完成态。当进程处于等待态时,在进程控制块 PCB 中要说 明进程等待原因(在模拟实验中进程等待原因为等待信号量 s1或 s2);当进程处于等待态或 就绪态时,PCB 中保留了断点信息,一旦进程再度占有处理器则就从断点位置继续运行;当 进程处于完成状态,表示进程执行结束。
(4).指令模拟
模拟的一组指令见图 2-2,其中每条指令的功能由一个过程来实现。用变量 PC 来模拟 “指令计数器”,假设模拟的指令长度为 1,每执行一条模拟指令后,PC 加 1,指出下一条 指令地址。使用模拟的指令,可把生产者和消费者进程的程序表示为图 2-3 的形式。
三.代码实现
写这个实验程序的时候没有很好的真正模拟出生产者消费者进程,只实现了基本的功能。并且为了写实验报告的方便,进程步骤的选择需要手动输入0和1选择,没有实现真正的自动调度功能。
基本功能包括:
①缺资源时进程挂起且不能选择挂起进程,只能强制选择执行另一个进程,否则一直在选择界面。
②生产者进程模拟生产产品。
③消费者进程模拟消费产品。
注:由图2-3可知,通常一个进程包括五条指令,因此程序执行时若想模拟完成一个【完整】的生产者/消费者进程,需连续选择五次“0”或者“1”。
#include <stdio.h>
#include <stdlib.h>
#include<string.h>
#define Total 10 //生产/消费缓存区最大数量
#define Num 5 //表示一个进程的指令总数
struct PCB {
char name[20]; //进程名
char state; //进程状态 r:运行 R:就绪 W:等待 E:完成
int waitReason; //表示S1和S2 0为s1 1为s2
int point; //断点
}producer, consumer, *currentPC; //定义生产者,消费者PCB和指针
int s1, s2; //s1:当前资源可存量 s2:当前资源可取量
int PA[Num], PB[Num]; //分别表示两个进程的指令标号集
int PC; //指令计数器
int Buffer[Total] = {0}; //缓冲区
int Buflag = 0; //缓冲区指针
int OUTPUT = -999;
int S = 0; //存放产品
//P操作
void P(int s,int nq) //s为某类公共资源,类别可由nq来判断,s为对应实际操作数据
{
if (nq == 0)
{
s1--;
s = s1;
}
else
{
s2--;
s = s2;
}
if (s < 0) //无资源剩余
{
currentPC->state = 'W'; //状态为等待
currentPC->waitReason = nq; //等待原因 0为S1 1为S2
printf("【提示】等待原因为:S%d\n", currentPC->waitReason+1); //0对应S1 1对应S2 加一即可
}
else //有资源剩余
{
printf("【提示】无需等待\n");
currentPC->state = 'R'; //状态为就绪
}
}
void V(int s, int nq) //s为实际操作数 nq为标识符
{
if (nq == 0)
{
s1 = s1 + 1;
s = s1;
}
else
{
s2 = s2 + 1;
s = s2;
}
if (s <=0) //单生产者/消费者 阻塞值最小只有-1
{
if (producer.waitReason == nq)
{
producer.state = 'R';
producer.waitReason = -1;
printf("【提示】释放一个producer");
}
if (consumer.waitReason == nq)
{
consumer.state = 'R';
consumer.waitReason = -1;
printf("【提示】释放一个consumer");
}
}
else
{
printf("无进程释放");
currentPC->state = 'R';//将现有进程置为就绪态
}
}
void initial() //初始化各参数
{
//name
char Pname[10] = "producer";
char Cname[10] = "consumer";
int i = 0;
while (Pname[i] != '\0')
{
producer.name[i] = Pname[i];
i++;
}
i = 0;
while (Cname[i] != '\0')
{
consumer.name[i] = Cname[i];
i++;
}
//指令标识符数组初始化
for (i = 0; i < Num; i++)
{
PA[i] = i;
PB[i] = i;
}
//PC初始化
PC = 0;
//状态初始化
producer.state = 'R';
consumer.state = 'R';
//资源初始化
s1 = 10; s2 = 0;
//断点初始化
producer.point = 0;
consumer.point = 0;
//等待原因初始化
producer.waitReason = -1;
consumer.waitReason = -1;
}
void show_Buffer()
{
int i = 0;
for (i = 0; i < Total; i++)
{
printf("%d ", Buffer[i]);
}
}
void show_PCB(int flag)
{
//flag表示打印类型
if (flag == 0) //打印生产者PCB
{
printf("Name:");
puts(producer.name);
printf("State:%c\n", producer.state);
if (producer.waitReason == 0)
printf("Waitreason:S1\n");
else if (producer.waitReason == 1)
printf("Waitreason:S2\n");
else
printf("Waitreason:None\n");
printf("Point:%d\n\n", producer.point);
}
if (flag == 1) //打印消费者PCB
{
printf("Name:");
puts(consumer.name);
printf("State:%c\n", consumer.state);
if (consumer.waitReason == 0)
printf("Waitreason:S1\n");
else if (consumer.waitReason == 1)
printf("Waitreason:S2\n");
else
printf("Waitreason:None\n");
printf("Point:%d\n\n", consumer.point);
}
}
void produce(int s) //将临时产品暂时保存
{
S = s;
}
void Running() //模拟处理器执行
{
int q=0,j=-1,i=0; //q用来标识生产者or消费者 j用来标识switch函数入口函数标号 i用来过度PC值
i = PC; //保存PC值为i
//初始化入口函数下标
if (currentPC->name[0] == 'p') //选中生产者
{
j = PA[i];
q = 0; //标识符
}
if (currentPC->name[0] == 'c') //选中消费者
{
j = PB[i];
q = 1;
}
PC = i + 1;
//执行过程
//char S;//临时存放生产商品
int s=0;
switch (j)
{
case 0:
if (q == 0) //生产者执行【produce】
{
printf("【提示】请输入一件产品:");
scanf("%d", &s);//产品暂存到S中
produce(s);//暂存商品
}
else //消费者【P(S2)】
{
P(s2, 1); //1表示操作S2
}
break;
case 1:
if (q == 0) //生产者【P(s1)】
{
P(s1, 0);
}
else //消费者【GET】
{
printf("【提示】消费者取一个产品\n");
//consumerList[cflag] = producerList[cflag];//取商品
//cflag = (cflag + 1) % Total;//下标循环右移
OUTPUT = Buffer[Buflag - 1];
Buffer[Buflag - 1] = -1;
Buflag = (Buflag - 1) % Total;
currentPC->state = 'R'; //设置为就绪态
}
break;
case 2:
if (q == 0) //生产者【PUT】
{
printf("【提示】生产者产入一个商品");
//producerList[pflag] = S; //将产品存入生产者数组
Buffer[Buflag] = S;
//pflag = (pflag + 1) % Total;
Buflag = (Buflag + 1) % Total;
currentPC->state = 'R';//设置为就绪态
}
else //消费者【V(s1)】
{
V(s1, 0);
}
break;
case 3:
if (q == 0) //生产者【V(s2)】
{
V(s2, 1);
}
else //消费者【consume】--打印
{
printf("【提示】消费了一个产品:%d\n",OUTPUT);
}
break;
case 4:
if (q == 0) //生产者【goto 0】
{
PC = 0;
}
else //消费者【goto 0】
{
PC = 0;
}
break;
}
if (q==0&&j==4)//判断生产者进程是否运行结束
{
printf("【提示】生产者程序已执行完\n");
}
}
void Scheduling()//处理器调度程序--选择出执行阶段的currentPC
{
int select; //0为生产者 1为消费者
//手动选择模拟调度
while (producer.state == 'R' || consumer.state == 'R')
{
printf("【提示】请选择本次执行的程序(0为生产者,1为消费者)");
scanf("%d", &select);
while ((select == 0 && producer.state != 'R') || (select == 1 && consumer.state != 'R'))
{
if (select == 0)
{
printf("【提示】生产者进程被挂起,请重新选择:");
scanf("%d", &select);
}
else
{
printf("【提示】消费者进程被挂起,请重新选择:");
scanf("%d", &select);
}
}
if (select == 0)
{
if (producer.state == 'R')
{
currentPC = &producer;
}
else
{
printf("【提示】生产者进程被挂起,无法执行");
//currentPC = &consumer;
}
}
else
{
if (select == 1)
{
if (consumer.state == 'R')
{
currentPC = &consumer;
}
else
{
printf("【提示】消费者进程被挂起,无法执行");
//currentPC = &consumer;
}
}
}
//设置currentPC的参数
PC = currentPC->point;
Running();//执行进程
currentPC->point = PC;
//printf("\n当前PC值为:%d\n", PC);
printf("【提示】当前缓冲区为:");
show_Buffer();
printf("\n【提示】当前S1值为:%d 当前S2值为:%d\n\n", s1,s2);
show_PCB(0);
show_PCB(1);
printf("-----分割线-----\n");
}
}
void main() {
initial(); //初始化
while (1)
{
Scheduling(); //循环执行调度器
}
}
四.运行结果
测试说明:本次测试模拟逻辑:初始化后先执行消费者进程,消费者进程被挂起,随后执行完整的生产者进程,完成后被挂起的消费者进程自动解挂,方可继续执行消费者进程对应图2-3中1-4的指令。
A.执行消费者进程入口地址0的指令,进程由于缺少s2资源被挂起
【P(s2)】指令--消费者
B.执行完整的生产者进程(连续执行入口函数0-4对应模拟指令)
【produce】指令--生产者
【P(s1)】指令--生产者
【PUT】指令--生产者
【V(s2)】指令--生产者
【goto 0】指令--生产者
【GET】指令--消费者
【V(s1)】指令--消费者
【consume】指令--消费者
【goto 0】指令--消费者
五.流程图
1.调度器Scheduling()函数
2.Running()运行函数流程图