实验内容:
抽烟者问题。假设⼀个系统中有三个抽烟者进程,每个抽烟者不断地卷烟并抽 烟。抽烟者卷起并抽掉⼀
颗烟需要有三种材料:烟草、纸和胶⽔。⼀个抽烟者有烟草,⼀个有纸,另⼀个有胶⽔。系统中还有两
个供应者进程,它们⽆限地供应所有三种材料,但每次仅轮流提供三种材料中的两种。得到缺失的两种
材料的抽烟者在卷起并抽掉⼀颗烟后会发信号通知供应者,让它继续提供另外的两种材料。这⼀过程重
复进⾏。 请⽤IPC 同步机制编程,实现该问题要求的功能。
ipc.h
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <sys/msg.h>
#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 prod_key;
key_t pmtx_key;
int prod_sem;
int pmtx_sem;
//消费者有关的信号量
key_t cons_key;
int cons_sem;
key_t cmtx_key;
int cmtx_sem;
key_t x_cons_key;
int x_cons_sem;
key_t y_cons_key;
int y_cons_sem;
key_t z_cons_key;
int z_cons_sem;
int sem_val;
int sem_flg;
int shm_flg;
生产者代码
#include "ipc.h"
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) {
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;
}
int main(int argc,char *argv[])
{
int rate;
//可在在命令⾏第⼀参数指定⼀个进程睡眠秒数,以调解进程执⾏速度
if (argv[1] != NULL) rate = atoi(argv[1]);
else rate = 3; //不指定为 3 秒
//共享内存使⽤的变量
buff_key = 1010;//缓冲区任给的键值
buff_num = 1;//缓冲区任给的⻓度
pput_key = 1020;//⽣产者放产品指针的键值
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);
//信号量使⽤的变量
prod_key = 2010;//⽣产者同步信号灯键值
pmtx_key = 2020;//⽣产者互斥信号灯键值
x_cons_key = 4010;//消费者同步信号灯键值
y_cons_key = 4020;//消费者同步信号灯键值
z_cons_key = 4030;//消费者同步信号灯键值
cmtx_key = 4040;//消费者互斥信号灯键值
sem_flg = IPC_CREAT | 0644;
//⽣产者同步信号灯初值设为缓冲区最⼤可⽤量
sem_val = buff_num;
//获取⽣产者同步信号灯,引⽤标识存
prod_sem prod_sem = set_sem(prod_key, sem_val, sem_flg);
//消费者初始⽆产品可取,同步信号灯初值设为 0
sem_val = 0;
//获取消费者同步信号灯,引⽤标识存 cons_sem
x_cons_sem = set_sem(x_cons_key, sem_val, sem_flg);
y_cons_sem = set_sem(y_cons_key, sem_val, sem_flg);
z_cons_sem = set_sem(z_cons_key, sem_val, sem_flg);
//⽣产者互斥信号灯初值为 1
sem_val = 1;
//获取⽣产者互斥信号灯,引⽤标识存 pmtx_sem
pmtx_sem = set_sem(pmtx_key, sem_val, sem_flg);
int pid=fork();
if(pid<0)
{
printf("create fork fail");
exit(1);
}
else if(pid==0)
{
while(1)
{
int r=rand()%3;
if(r==0)
{
//如果缓冲区满则⽣产者阻塞
down(prod_sem);
//如果另⼀⽣产者正在放产品,本⽣产者阻塞
down(pmtx_sem);
buff_ptr[*pput_ptr]='x';
sleep(rate);
printf("child %d producer put: %c to buffer[%d]\n",getpid(),buff_ptr[*pput_ptr],*pput_ptr);
//唤醒阻塞的⽣产者
up(pmtx_sem);
//唤醒阻塞的消费者
up(x_cons_sem);
}
else if(r==1)
{
//如果缓冲区满则⽣产者阻塞
down(prod_sem);
//如果另⼀⽣产者正在放产品,本⽣产者阻塞
down(pmtx_sem);
buff_ptr[*pput_ptr]='y';
sleep(rate);
printf("child %d producer put: %c to buffer[%d]\n",getpid(),buff_ptr[*pput_ptr],*pput_ptr);
//唤醒阻塞的⽣产者
up(pmtx_sem);
//唤醒阻塞的消费者
up(y_cons_sem);
}
else{
//如果缓冲区满则⽣产者阻塞
down(prod_sem);
//如果另⼀⽣产者正在放产品,本⽣产者阻塞
down(pmtx_sem);
buff_ptr[*pput_ptr]='z';
sleep(rate);
printf("child %d producer put: %c to buffer[%d]\n",getpid(),buff_ptr[*pput_ptr],*pput_ptr);
//唤醒阻塞的⽣产者
up(pmtx_sem);
//唤醒阻塞的消费者
up(z_cons_sem);
}
}
return EXIT_SUCCESS;
}
else
{
while(1)
{
int r=rand()%3;
if(r==0)
{
//如果缓冲区满则⽣产者阻塞
down(prod_sem);
//如果另⼀⽣产者正在放产品,本⽣产者阻塞
down(pmtx_sem);
buff_ptr[*pput_ptr]='x';
sleep(rate);
printf("child %d producer put: %c to buffer[%d]\n",getpid(),buff_ptr[*pput_ptr],*pput_ptr);
//唤醒阻塞的⽣产者
up(pmtx_sem);
//唤醒阻塞的消费者
up(x_cons_sem);
}
else if(r==1)
{
//如果缓冲区满则⽣产者阻塞
down(prod_sem);
//如果另⼀⽣产者正在放产品,本⽣产者阻塞
down(pmtx_sem);
buff_ptr[*pput_ptr]='y';
sleep(rate);
printf("child %d producer put: %c to buffer[%d]\n",getpid(),buff_ptr[*pput_ptr],*pput_ptr);
//唤醒阻塞的⽣产者
up(pmtx_sem);
//唤醒阻塞的消费者
up(y_cons_sem);
}
else{
//如果缓冲区满则⽣产者阻塞
down(prod_sem);
//如果另⼀⽣产者正在放产品,本⽣产者阻塞
down(pmtx_sem);
buff_ptr[*pput_ptr]='z';
sleep(rate);
printf("child %d producer put: %c to buffer[%d]\n",getpid(),buff_ptr[*pput_ptr],*pput_ptr);
//唤醒阻塞的⽣产者
up(pmtx_sem);
//唤醒阻塞的消费者
up(z_cons_sem);
}
}
}
return EXIT_SUCCESS;
}
消费者代码
#include "ipc.h"
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) {
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;
}
int main(int argc,char *argv[])
{
int rate;
//可在在命令⾏第⼀参数指定⼀个进程睡眠秒数,以调解进程执⾏速度
if (argv[1] != NULL) rate = atoi(argv[1]);
else rate = 3; //不指定为 3 秒
//共享内存使⽤的变量
buff_key = 1010;//缓冲区任给的键值
buff_num = 1;//缓冲区任给的⻓度
pput_key = 1020;//⽣产者放产品指针的键值
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);
//信号量使⽤的变量
prod_key = 2010;//⽣产者同步信号灯键值
pmtx_key = 2020;//⽣产者互斥信号灯键值
x_cons_key = 4010;//消费者同步信号灯键值
y_cons_key = 4020;//消费者同步信号灯键值
z_cons_key = 4030;//消费者同步信号灯键值
cmtx_key = 4040;//消费者互斥信号灯键值
sem_flg = IPC_CREAT | 0644;
//⽣产者同步信号灯初值设为缓冲区最⼤可⽤量
sem_val = buff_num;
//获取⽣产者同步信号灯,引⽤标识存
prod_sem prod_sem = set_sem(prod_key, sem_val, sem_flg);
//消费者初始⽆产品可取,同步信号灯初值设为 0
sem_val = 0;
//获取消费者同步信号灯,引⽤标识存 cons_sem
x_cons_sem = set_sem(x_cons_key, sem_val, sem_flg);
y_cons_sem = set_sem(y_cons_key, sem_val, sem_flg);
z_cons_sem = set_sem(z_cons_key, sem_val, sem_flg);
//消费者互斥信号灯初值为 1
sem_val = 1;
//获取消费者互斥信号灯,引⽤标识存 cmtx_sem;
cmtx_sem = set_sem(cmtx_key, sem_val, sem_flg);
int pid1,pid2;
pid1=fork();
if(pid1<0)
{
printf("create fork pid1 fail\n");
exit(1);
}
else if(pid1==0)
{
while(1){
//若没有相应产品,消费者阻塞
down(x_cons_sem);
//如果另一消费者正在取产品,本消费者阻塞
down(cmtx_sem);
sleep(rate);
printf("%d 进程拥有 x,得到另外两种材料,可以抽烟\n",getpid());
//唤醒阻塞的消费者
up(cmtx_sem);
//唤醒阻塞的生产者
up(prod_sem);
}
}
else{
pid2=fork();
if(pid2<0)
{
printf("create fork pid2 fail\n");
exit(1);
}
else if(pid2==0)
{
while(1)
{
//若没有相应产品,消费者阻塞
//printf("%d\n",y_cons_sem);
down(y_cons_sem);
//如果另一消费者正在取产品,本消费者阻塞
//puts("234");
down(cmtx_sem);
sleep(rate);
printf("%d 进程拥有 y,得到另外两种材料,可以抽烟\n",getpid());
//唤醒阻塞的消费者
up(cmtx_sem);
//唤醒阻塞的生产者
up(prod_sem);
}
}
else{
while(1)
{
//若没有相应产品,消费者阻塞
down(z_cons_sem);
//如果另一消费者正在取产品,本消费者阻塞
down(cmtx_sem);
sleep(rate);
printf("%d 进程拥有 z,得到另外两种材料,可以抽烟\n",getpid());
//唤醒阻塞的消费者
up(cmtx_sem);
//唤醒阻塞的生产者
up(prod_sem);
}
}
}
return EXIT_SUCCESS;
}