抽烟者问题。假设一个系统中有三个抽烟者进程,每个抽烟者不断地卷烟并抽烟。抽烟者卷起并抽掉一颗烟需要有三种材料:烟草、纸和胶水。一个抽烟者有烟草,一个有纸,另一个有胶水。系统中还有两个供应者进程,它们无限地供应所有三种材料,但每次仅轮流提供三种材料中的两种。得到缺失的两种材料的抽烟者在卷起并抽掉一颗烟后会发信号通知供应者,让它继续提供另外的两种材料。这一过程重复进行。 请用以上介绍的 IPC 同步机制编程,实现该问题要求的功能。
while(1){
P(all);
P(producer);
生产产品
V(producer);
if(生产的产品)
V(对应的消费者);
}
while(1){
P(自己的信号量);
抽烟
V(all);
}
#include
#include
#include
#include
#include
#include
#include
#define BUFSZ 256
//建立或获取 ipc 的一组函数的原型说明
int get_ipc_id(char *proc_file,key_t key);
char *set_shm(key_t shm_key,int shm_num,int shm_flag);
int set_msq(key_t msq_key,int msq_flag);
int set_sem(key_t sem_key,int sem_val,int sem_flag);
int down(int sem_id);
int up(int sem_id);
/*信号灯控制用的共同体*/
typedef union semuns
{
int val;
} Sem_uns;
/* 消息结构体*/
typedef struct msgbuf
{
long mtype;
char mtext[1];
} Msg_buf;
//生产消费者共享缓冲区即其有关的变量
key_t buff_key;
int buff_num;
char *buff_ptr;
//生产者放产品位置的共享指针
key_t pput_key;
int pput_num;
int *pput_ptr;
//消费者取产品位置的共享指针
key_t cget_key;
int cget_num;
int *cget_ptr;
//生产者和消费者有关的信号量
key_t ab_key;
key_t ac_key;
key_t bc_key;
key_t all_key;
key_t produce_key;
int ab_int;
int ac_int;
int bc_int;
int all_int;
int produce_int;
int sem_val;
int sem_flg;
int shm_flg;
#include "ipc.h"
/*
* get_ipc_id() 从/proc/sysvipc/文件系统中获取 IPC 的 id 号
* pfile: 对应/proc/sysvipc/目录中的 IPC 文件分别为
* msg-消息队列,sem-信号量,shm-共享内存
* key: 对应要获取的 IPC 的 id 号的键值
*/
int get_ipc_id(char *proc_file,key_t key)
{
FILE *pf;
int i,j;
char line[BUFSZ],colum[BUFSZ];
if((pf = fopen(proc_file,"r")) == NULL)
{
perror("Proc file not open");
exit(EXIT_FAILURE);
}
fgets(line, BUFSZ,pf);
while(!feof(pf))
{
i = j = 0;
fgets(line, BUFSZ,pf);
while(line[i] == ' ')
i++;
while(line[i] !=' ')
colum[j++] = line[i++];
colum[j] = '\0';
if(atoi(colum) != key)
continue;
j=0;
while(line[i] == ' ')
i++;
while(line[i] !=' ')
colum[j++] = line[i++];
colum[j] = '\0';
i = atoi(colum);
fclose(pf);
return i;
}
fclose(pf);
return -1;
}
/*
* 信号灯上的 down/up 操作
* semid:信号灯数组标识符
* semnum:信号灯数组下标
* buf:操作信号灯的结构
*/
int down(int sem_id)
{
struct sembuf buf;
buf.sem_op = -1;
buf.sem_num = 0;
buf.sem_flg = SEM_UNDO;
if((semop(sem_id,&buf,1)) <0)
{
perror("down error ");
exit(EXIT_FAILURE);
}
return EXIT_SUCCESS;
}
int up(int sem_id)
{
struct sembuf buf;
buf.sem_op = 1;
buf.sem_num = 0;
buf.sem_flg = SEM_UNDO;
if((semop(sem_id,&buf,1)) <0)
{
perror("up error ");
exit(EXIT_FAILURE);
}
return EXIT_SUCCESS;
}
/*
* set_sem 函数建立一个具有 n 个信号灯的信号量
* 如果建立成功,返回 一个信号灯数组的标识符 sem_id
* 输入参数:
* sem_key 信号灯数组的键值
* sem_val 信号灯数组中信号灯的个数
* sem_flag 信号等数组的存取权限
*/
int set_sem(key_t sem_key,int sem_val,int sem_flg)
{
int sem_id;
Sem_uns sem_arg;
//测试由 sem_key 标识的信号灯数组是否已经建立
if((sem_id = get_ipc_id("/proc/sysvipc/sem",sem_key)) < 0 )
{
//semget 新建一个信号灯,其标号返回到 sem_id
if((sem_id = semget(sem_key,1,sem_flg)) < 0)
{
perror("semaphore create error");
exit(EXIT_FAILURE);
}
//设置信号灯的初值
sem_arg.val = sem_val;
if(semctl(sem_id,0,SETVAL,sem_arg) <0)
{
perror("semaphore set error");
exit(EXIT_FAILURE);
}
}
return sem_id;
}
/*
* set_shm 函数建立一个具有 n 个字节 的共享内存区
* 如果建立成功,返回 一个指向该内存区首地址的指针 shm_buf
* 输入参数:
* shm_key 共享内存的键值
* shm_val 共享内存字节的长度
* shm_flag 共享内存的存取权限
*/
char* set_shm(key_t shm_key,int shm_num,int shm_flg)
{
int i,shm_id;
char* shm_buf;
//测试由 shm_key 标识的共享内存区是否已经建立
if((shm_id = get_ipc_id("/proc/sysvipc/shm",shm_key)) < 0 )
{
//shmget 新建 一个长度为 shm_num 字节的共享内存,其标号返回到 shm_id
if((shm_id = shmget(shm_key,shm_num,shm_flg)) <0)
{
perror("shareMemory set error");
exit(EXIT_FAILURE);
}
//shmat 将由 shm_id 标识的共享内存附加给指针 shm_buf
if((shm_buf = (char *)shmat(shm_id,0,0)) < (char *)0)
{
perror("get shareMemory error");
exit(EXIT_FAILURE);
}
for(i=0; i<shm_num; i++)
shm_buf[i] = 0; //初始为 0
}
//shm_key 标识的共享内存区已经建立,将由 shm_id 标识的共享内存附加给指针 shm_buf
if((shm_buf = (char *)shmat(shm_id,0,0)) < (char *)0)
{
perror("get shareMemory error");
exit(EXIT_FAILURE);
}
return shm_buf;
}
/*
* set_msq 函数建立一个消息队列
* 如果建立成功,返回 一个消息队列的标识符 msq_id
* 输入参数:
* msq_key 消息队列的键值
* msq_flag 消息队列的存取权限
*/
int set_msq(key_t msq_key,int msq_flg)
{
int msq_id;
//测试由 msq_key 标识的消息队列是否已经建立
if((msq_id = get_ipc_id("/proc/sysvipc/msg",msq_key)) < 0 )
{
//msgget 新建一个消息队列,其标号返回到 msq_id
if((msq_id = msgget(msq_key,msq_flg)) < 0)
{
perror("messageQueue set error");
exit(EXIT_FAILURE);
}
}
return msq_id;
}
#include "ipc.h"
int main(int argc,char *argv[])
{
int rate;
//可在在命令行第一参数指定一个进程睡眠秒数,以调解进程执行速度
if(argv[1] != NULL)
rate = atoi(argv[1]);
else
rate = 1; //不指定为 1 秒
//共享内存使用的变量
buff_key = 101;//缓冲区任给的键值
buff_num = 1;//缓冲区任给的长度
pput_key = 102;//生产者放产品指针的键值
pput_num = 1; //指针数
shm_flg = IPC_CREAT | 0644;//共享内存读写权限
//获取缓冲区使用的共享内存,buff_ptr 指向缓冲区首地址
buff_ptr = (char *)set_shm(buff_key,buff_num,shm_flg);
//获取生产者放产品位置指针 pput_ptr
pput_ptr = (int *)set_shm(pput_key,pput_num,shm_flg);
//信号量使用的变量
ab_key = 201;//有C的消费者控制键值
ac_key = 202;//有B的消费者控制键值
bc_key = 203;//有A的消费者控制键值
all_key = 204;//对一个缓冲区的控制键值
produce_key = 205;//对两个生产者的同步键值
sem_flg = IPC_CREAT | 0644;
//生产者同步信号灯初值设为缓冲区最大可用量
sem_val = buff_num;
//获取生产者同步信号灯,引用标识存 all_int
all_int = set_sem(all_key,sem_val,sem_flg);
//消费者初始无产品可取,同步信号灯初值设为 0
sem_val = 0;
ab_int = set_sem(ab_key,sem_val,sem_flg);
ac_int = set_sem(ac_key,sem_val,sem_flg);
bc_int = set_sem(bc_key,sem_val,sem_flg);
//生产者互斥信号灯初值为 1
sem_val = 1;
//获取生产者互斥信号灯,引用标识存 produce_int
produce_int = set_sem(produce_key,sem_val,sem_flg);
int i = 0;
while(1){
int d = (i++)%3;
down(all_int);
down(produce_int);
buff_ptr[*pput_ptr] = 'A'+ d;
sleep(rate);
if(d==0)
printf("%d把烟草和纸放入[%d]缓存区\n",getpid(),*pput_ptr);
else if(d==1)
printf("%d把胶水和纸放入[%d]缓存区\n",getpid(),*pput_ptr);
else
printf("%d把烟草和胶水放入[%d]缓存区\n",getpid(),*pput_ptr);
*pput_ptr = (*pput_ptr+1) % buff_num;
up(produce_int);
if(d==0)
up(bc_int);
else if(d==1)
up(ac_int);
else
up(ab_int);
}
return EXIT_SUCCESS;
}
#include "ipc.h"
int main(int argc,char *argv[])
{
int rate;
//可在在命令行第一参数指定一个进程睡眠秒数,以调解进程执行速度
if(argv[1] != NULL)
rate = atoi(argv[1]);
else
rate = 1; //不指定为 1 秒
//共享内存 使用的变量
buff_key = 101; //缓冲区任给的键值
buff_num = 1; //缓冲区任给的长度
cget_key = 103; //消费者取产品指针的键值
cget_num = 1; //指针数
shm_flg = IPC_CREAT | 0644; //共享内存读写权限
//获取缓冲区使用的共享内存,buff_ptr 指向缓冲区首地址
buff_ptr = (char *)set_shm(buff_key,buff_num,shm_flg);
//获取消费者取产品指针,cget_ptr 指向索引地址
cget_ptr = (int *)set_shm(cget_key,cget_num,shm_flg);
//信号量使用的变量
ab_key = 201;//有C的消费者控制键值
ac_key = 202;//有B的消费者控制键值
bc_key = 203;//有A的消费者控制键值
all_key = 204;//对一个缓冲区的控制键值
produce_key = 205;//对两个生产者的同步键值
sem_flg = IPC_CREAT | 0644;
//生产者同步信号灯初值设为缓冲区最大可用量
sem_val = buff_num;
//获取生产者同步信号灯,引用标识存 all_int
all_int = set_sem(all_key,sem_val,sem_flg);
//消费者初始无产品可取,同步信号灯初值设为 0
sem_val = 0;
// ab_int = set_sem(ab_key,sem_val,sem_flg);
// ac_int = set_sem(ac_key,sem_val,sem_flg);
bc_int = set_sem(bc_key,sem_val,sem_flg);
生产者互斥信号灯初值为 1
// sem_val = 1;
获取生产者互斥信号灯,引用标识存 produce_int
// produce_int = set_sem(produce_key,sem_val,sem_flg);
while(1)
{
down(bc_int);
sleep(rate);
printf("%d 1号吸烟者得到了:烟草和纸[%d]%c\n",getpid(),*cget_ptr ,buff_ptr[*cget_ptr]);
*cget_ptr = (*cget_ptr+1) % buff_num;
up(all_int);
}
return EXIT_SUCCESS;
}
#include "ipc.h"
int main(int argc,char *argv[])
{
int rate;
//可在在命令行第一参数指定一个进程睡眠秒数,以调解进程执行速度
if(argv[1] != NULL)
rate = atoi(argv[1]);
else
rate = 1; //不指定为 1 秒
//共享内存 使用的变量
buff_key = 101; //缓冲区任给的键值
buff_num = 1; //缓冲区任给的长度
cget_key = 103; //消费者取产品指针的键值
cget_num = 1; //指针数
shm_flg = IPC_CREAT | 0644; //共享内存读写权限
//获取缓冲区使用的共享内存,buff_ptr 指向缓冲区首地址
buff_ptr = (char *)set_shm(buff_key,buff_num,shm_flg);
//获取消费者取产品指针,cget_ptr 指向索引地址
cget_ptr = (int *)set_shm(cget_key,cget_num,shm_flg);
//信号量使用的变量
ab_key = 201;//有C的消费者控制键值
ac_key = 202;//有B的消费者控制键值
bc_key = 203;//有A的消费者控制键值
all_key = 204;//对一个缓冲区的控制键值
produce_key = 205;//对两个生产者的同步键值
sem_flg = IPC_CREAT | 0644;
//生产者同步信号灯初值设为缓冲区最大可用量
sem_val = buff_num;
//获取生产者同步信号灯,引用标识存 all_int
all_int = set_sem(all_key,sem_val,sem_flg);
//消费者初始无产品可取,同步信号灯初值设为 0
sem_val = 0;
// ab_int = set_sem(ab_key,sem_val,sem_flg);
ac_int = set_sem(ac_key,sem_val,sem_flg);
// bc_int = set_sem(ab_key,sem_val,sem_flg);
生产者互斥信号灯初值为 1
// sem_val = 1;
获取生产者互斥信号灯,引用标识存 produce_int
// produce_int = set_sem(produce_key,sem_val,sem_flg);
while(1)
{
down(ac_int);
sleep(rate);
printf("%d 2号吸烟者得到了:胶水和纸 [%d]%c\n",getpid(),*cget_ptr ,buff_ptr[*cget_ptr]);
*cget_ptr = (*cget_ptr+1) % buff_num;
up(all_int);
}
return EXIT_SUCCESS;
}
#include "ipc.h"
int main(int argc,char *argv[])
{
int rate;
//可在在命令行第一参数指定一个进程睡眠秒数,以调解进程执行速度
if(argv[1] != NULL)
rate = atoi(argv[1]);
else
rate = 1; //不指定为 1 秒
//共享内存 使用的变量
buff_key = 101; //缓冲区任给的键值
buff_num = 1; //缓冲区任给的长度
cget_key = 103; //消费者取产品指针的键值
cget_num = 1; //指针数
shm_flg = IPC_CREAT | 0644; //共享内存读写权限
//获取缓冲区使用的共享内存,buff_ptr 指向缓冲区首地址
buff_ptr = (char *)set_shm(buff_key,buff_num,shm_flg);
//获取消费者取产品指针,cget_ptr 指向索引地址
cget_ptr = (int *)set_shm(cget_key,cget_num,shm_flg);
//信号量使用的变量
ab_key = 201;//有C的消费者控制键值
ac_key = 202;//有B的消费者控制键值
bc_key = 203;//有A的消费者控制键值
all_key = 204;//对一个缓冲区的控制键值
produce_key = 205;//对两个生产者的同步键值
sem_flg = IPC_CREAT | 0644;
//生产者同步信号灯初值设为缓冲区最大可用量
sem_val = buff_num;
//获取生产者同步信号灯,引用标识存 all_int
all_int = set_sem(all_key,sem_val,sem_flg);
//消费者初始无产品可取,同步信号灯初值设为 0
sem_val = 0;
ab_int = set_sem(ab_key,sem_val,sem_flg);
// ac_int = set_sem(ac_key,sem_val,sem_flg);
// bc_int = set_sem(ab_key,sem_val,sem_flg);
//生产者互斥信号灯初值为 1
// sem_val = 1;
//获取生产者互斥信号灯,引用标识存 produce_int
// produce_int = set_sem(produce_key,sem_val,sem_flg);
while(1)
{
down(ab_int);
sleep(rate);
printf("%d 3号吸烟者得到了:胶水和烟草 [%d]%c\n",getpid(),*cget_ptr ,buff_ptr[*cget_ptr]);
*cget_ptr = (*cget_ptr+1) % buff_num;
up(all_int);
}
return EXIT_SUCCESS;
}
hdrs = ipc.h
opts = -g -c
p_src = producer.c ipc.c
p_obj = producer.o ipc.o
a_src = haveA.c ipc.c
a_obj = haveA.o ipc.o
b_src = haveB.c ipc.c
b_obj = haveB.o ipc.o
c_src = haveC.c ipc.c
c_obj = haveC.c ipc.o
all: haveA haveB haveC producer
haveA: $(a_obj)
gcc $(a_obj) -o haveA
haveA.o: $(a_src) $(hdrs)
gcc $(opts) $(a_src) -lrt
haveB: $(b_obj)
gcc $(b_obj) -o haveB
haveB.o: $(b_src) $(hdrs)
gcc $(opts) $(b_src) -lrt
haveC: $(c_obj)
gcc $(c_obj) -o haveC
haveC.o: $(c_src) $(hdrs)
gcc $(opts) $(c_src) -lrt
producer: $(p_obj)
gcc $(p_obj) -o producer
producer.o: $(p_src) $(hdrs)
gcc $(opts) $(p_src) -lrt
clean:
rm haveA haveB haveC producer *.o
信号量的key似乎是运行一次结束后并不会删除,导致成功运行一次之后关闭terminal,再次开启时就没有正确的阻塞出现了(生产者无法被阻塞而疯狂生产)。
目前还不太知道 ipc.c 这个文件的具体实现原理,所以现在只能被迫通过重启虚拟机来重现实验的成功情景。
凡是出现问题,重启一下说不定就好了呢?
之后是更难的实验五和实验六,心态出现了一丝变化。