linux编程综合案例(生产者消费者问题)
前面一系列练习已经把进程控制、线程、进程间通信的大概知识过了一遍,现在进入综合练习,首先练习经典问题:生产者和消费者问题。(不要问我前面一系列到底是个什么东西,csdn上基本上全都是转载,且都有这句话,我查了半天这个篇文章哪里来的,最早我只能追溯到Linux编程日日练 --生产者消费者问题2010年5月18日的文章,结果文章上面也标明了是转载,也不知道从哪转的,更早的我已经找不到了。
因为强迫症,实在看不惯前面的人随便转载,为了操作系统的实验又必须学(ノ`Д)ノ,自己又小白不怎么懂/(ㄒoㄒ)/~~,就简单排版,再加些自己了解的简单内容,重新整理编排了下。前半部分主要对这个例子简单加了点分析,后半部分加了点修正。简单看看就好。
好像有点长,可以从左边目录跳转到需要看的内容。
1.问题概述
多个生产/消费者在有界缓冲上操作。它利用N个字节的共享内存作为有界循环缓冲区,利用写一字符模拟放一个产品,利用读一字符模拟消费一个产品。当缓冲区空时消费者应阻塞睡眠,而当缓冲区满时生产者应阻塞睡眠。一旦缓冲区中有空单元,生产者进程就向空单元中入写字符,并报告写的内容和位置。一旦缓冲区中有未读过的字符,消费者进程就从该单元中读出字符,并报告读取位置。生产者不能向同一单元中连续写两次以上相同的字符,消费者也不能从同一单元中连续读两次以上相同的字符。
2.问题分析
首先看阶层,有两个,分别是生产者和消费者,他们之间的缓冲区是共享内存,首先想到一点:System V共享内存实现这一个缓冲区;又因为缓冲区是临界资源,所以要用一个互斥信号量实现;生产者和消费者要采用PV信号量操作实现进程同步
因为要求多个进程能同步,所以进程访问缓冲的指针也需要共享内存实现
大致框图:
3.原语
进程:Producer - 生产者进程,Consumer - 消费者进程
共有的数据结构:
buffer: array [0…k-1] of integer;
in,out: 0…k-1;
— in记录第一个空缓冲区,out记录第一个不空的缓冲区
prod_key(缓冲区空的个数),cons_key(缓冲区满的个数),mutex(临界区): semaphore;
— prod_key控制缓冲区不满,cons_key控制缓冲区不空,mutex保护临界区;
初始化prod_key=k,cons_key=0,mutex=1
下面的部分为伪代码
producer(生产者进程):
Item_Type item;
{
while (true)
{
produce(&item);
p(prod_key);
p(mutex);
buffer[in]:=item;
in:=(in+1) mod k;
v(mutex);
v(cons_key);
}
}
consumer(消费者进程):
Item_Type item;
{
while (true)
{
p(cons_key);
p(mutex);
item:=buffer[out];
out:=(out+1) mod k;
v(mutex);
v(prod_key);
consume(&item);
}
}
在这个实验中,缓冲区大小被设置为8,producer初始可以申请8个资源,comsumer初始设置为可申请0个,也就是,必须要produce先释放,comsumer,才可以申请,然后运行。
4.函数原型
自定义的函数
get_ipc_id
int get_ipc_id(char *proc_file,key_t key);
get_ipc_id() 从/proc/sysvipc/文件系统中获取IPC 的id 号,msg-消息队列,sem-信号量,shm-共享内存
set_sem()
int set_sem(key_t sem_key,int sem_val,int sem_flag);
set_sem 函数建立一个具有n 个信号灯的信号量
如果建立成功,返回一个信号灯数组的标识符sem_id
输入参数:
- sem_key 信号灯数组的键值
- sem_val 信号灯数组中信号灯的个数
- sem_flag 信号灯数组的存取权限
- 返回id
实现过程:建立信号灯->设置信号灯初值->返回ID
set_shm()
char *set_shm(key_t shm_key,int shm_num,int shm_flag);
set_shm 函数建立一个具有n 个字节的共享内存区
如果建立成功,返回一个指向该内存区首地址的指针shm_buf
输入参数:
- shm_key 共享内存的键值
- shm_val 共享内存字节的长度
- shm_flag 共享内存的存取权限
实现过程:创建共享内存->映射到进程的指针并返回
set_msq()
int set_msq(key_t msq_key,int msq_flag);
set_msq 函数建立一个消息队列
如果建立成功,返回一个消息队列的标识符msq_id
- 输入参数:
- msq_key 消息队列的键值
- msq_flag 消息队列的存取权限
- 返回id
实现过程:创建消息队列->返回ID
P/V操作
int P_operation(int sem_id)
请求P操作
int V_operation(int sem_id)
释放V操作
信号灯上的P/V 操作,sem_num:信号灯数组下标,buf:操作信号灯的结构
原有的函数
<sys/sem.h>
引自Linux进程间通信(五)
semget()
int semget(key_t key, int num_sems, int sem_flags);
创建一个新的信号量或取得一个已有的信号量。
-
参数key是整数值(唯一非零),不相关的进程可以通过它访问同一个信号量,它代表程序可能要使用的某个资源,程序对所有信号量的访问都是间接的,程序先通过调用semget()函数并提供一个键,再由系统生成一个相应的信号标识符(semget()函数的返回值),只有semget()函数才直接使用信号量键,所有其他的信号量函数使用由semget()函数返回的信号量标识符。如果多个程序使用相同的key值,key将负责协调工作。
-
参数num_sems指定需要的信号量数目,它的值几乎总是1。
-
参数sem_flags是一组标志,当想要当信号量不存在时创建一个新的信号量,可以和值IPC_CREAT做按位或操作。设置了IPC_CREAT标志后,即使给出的键是一个已有信号量的键,也不会产生错误。而IPC_CREAT | IPC_EXCL则可以创建一个新的,唯一的信号量,如果信号量已存在,返回一个错误。
-
semget()函数成功返回一个相应信号标识符(非零),失败返回-1。
semop()
int semop(int sem_id, struct sembuf *sem_opa, size_t num_sem_ops);
用来改变信号量作用
struct sembuf{
short sem_num;
short sem_op;
short sem_flg;
};
调用成功返回0,失败返回-1。
semctl
int semctl(int sem_id, int sem_num, int command, ...)
该函数用来直接控制信号量信息
-
参数sem_id同上。
-
参数sem_num同上中sembuf结构体中的sem_num。
-
command通常是下面两个值中的其中一个。
SETVAL:用来把信号量初始化为一个已知的值。p 这个值通过union semun中的val成员设置,其作用是在信号量第一次使用前对它进行设置。
IPC_RMID:用于删除一个已经无需继续使用的信号量标识符。
-
arg union semun结构体,详细见链接内容
-
失败时 semctl() 返回 -1 并设置 errno 指明错误,否则该系统调用返回一个依赖于 cmd 的非负值。
<sys/shm.h>
引自Linux进程间通信(六)
shmget()
int shmget(key_t key, size_t size, int shmflg);
该函数用来创建共享内存。
-
参数key,与信号量的semget函数一样,程序需要提供一个参数key(非0整数),它有效地为共享内存段命名,shmget()函数成功时返回一个与key相关的共享内存标识符(非负整数),用于后续的共享内存函数。调用失败返回-1。
不相关的进程可以通过该函数的返回值访问同一共享内存,它代表程序可能要使用的某个资源,程序对所有共享内存的访问都是间接的,程序先通过调用shmget()函数并提供一个键,再由系统生成一个相应的共享内存标识符(shmget()函数的返回值),只有shmget()函数才直接使用信号量键,所有其他的信号量函数使用由semget函数返回的信号量标识符。
-
参数size,size以字节为单位指定需要共享的内存容量。
-
参数shmflg,shmflg是权限标志,它的作用与open函数的mode参数一样,如果要想在key标识的共享内存不存在时,创建它的话,可以与IPC_CREAT做或操作。共享内存的权限标志与文件的读写权限一样,举例来说,0644,它表示允许一个进程创建的共享内存被内存创建者所拥有的进程向共享内存读取和写入数据,同时其他用户创建的进程只能读取共享内存。
-
成功返回共享存储的id,失败返回-1。
shmat()
void *shmat(int shm_id, const void *shm_addr, int shmflg);
-
参数shm_id,shm_id是由shmget()函数返回的共享内存标识。
-
参数shma_ddr,shm_addr指定共享内存连接到当前进程中的地址位置,通常为空,表示让系统来选择共享内存的地址。
-
参数shmflg,shm_flg是一组标志位,通常为0。
-
调用成功时返回一个指向共享内存第一个字节的指针,如果调用失败返回-1。
其他
shmdt()分离共享内存 shmctl()与semctl()一样,控制共享内存。本实验未使用,详见上链接。
<sys/msg.h>
引自消息队列函数
msgget()
int msgget(key_t key, int msgflg);
得到消息队列标识符或创建一个消息队列对象并返回消息队列标识符。
-
key:
0(IPC_PRIVATE):会建立新的消息队列;
大于0的32位整数:视参数msgflg来确定操作。通常要求此值来源于ftok返回的IPC键值。
-
msgflg:
IPC_CREAT:当msgflg&IPC_CREAT为真时,如果内核中不存在键值与key相等的消息队列,则新建一个消息队列;如果存在这样的消息队列,返回此消息队列的标识符;IPC_CREAT|IPC_EXCL:如果内核中不存在键值与key相等的消息队列,则新建一个消息队列;如果存在这样的消息队列则报错。
-
成功:返回消息队列的标识符,出错:-1,错误原因存于error中。
其他
msgctl()、msgsnd()、msgrcv()详见上链接。
5.IPC操作函数
我们编写一个函数实现对IPC 信息队列、共享内存、信号量的包装,以备接下来更好的编写程序
头文件ipc.h:
声明ipc操作函数和一些变量
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <sys/msg.h>
#define BUFSZ 256
int get_ipc_id(char *proc_file,key_t key);
int set_sem(key_t sem_key,int sem_val,int sem_flag);
char *set_shm(key_t shm_key,int shm_num,int shm_flag);
int set_msq(key_t msq_key,int msq_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;
int prod_sem;
key_t cons_key;
int cons_sem;
int sem_val;
int sem_flg;
int shm_flg;
key_t mtx_key;
int mtx_sem;
ipc.c:
这里包装了一些ipc的操作函数,包括信息队列、共享内存、信号量的创建/获得,及PV操作
这里逐一分析它们的实现过程:(在注释中)
#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;
}
int P_operation(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 V_operation(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;
}
int set_sem(key_t sem_key,int sem_val,int sem_flg)
{
int sem_id;
Sem_uns sem_arg;
if((sem_id = get_ipc_id("/proc/sysvipc/sem",sem_key)) < 0 )
{
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;
}
char * set_shm(key_t shm_key,int shm_num,int shm_flg)
{
int i,shm_id;
char * shm_buf;
if((shm_id = get_ipc_id("/proc/sysvipc/shm",shm_key)) < 0 )
{
if((shm_id = shmget(shm_key,shm_num,shm_flg)) <0)
{
perror("shareMemory set error");
exit(EXIT_FAILURE);
}
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;
}
if((shm_buf = (char *)shmat(shm_id,0,0)) < (char *)0)
{
perror("get shareMemory error");
exit(EXIT_FAILURE);
}
return shm_buf;
}
int set_msq(key_t msq_key,int msq_flg)
{
int msq_id;
if((msq_id = get_ipc_id("/proc/sysvipc/msg",msq_key)) < 0 )
{
if((msq_id = msgget(msq_key,msq_flg)) < 0)
{
perror("messageQueue set error");
exit(EXIT_FAILURE);
}
}
return msq_id;
}
6.生产者程序实现
首先建立(已存在时为打开)一系列的信号量和共享内存,接着就按照操作原语去实现了,代码如下:
#include "ipc.h"
int main(int argc,char *argv[])
{
int rate;
if(argv[1] != NULL)
rate = atoi(argv[1]);
else
rate = 3;
buff_key = 101;
buff_num = 8;
pput_key = 102;
pput_num = 1;
shm_flg = IPC_CREAT | 0644;
buff_ptr = (char *)set_shm(buff_key,buff_num,shm_flg);
pput_ptr = (int *)set_shm(pput_key,pput_num,shm_flg);
prod_key = 201;
mtx_key = 202;
cons_key = 301;
sem_flg = IPC_CREAT | 0644;
sem_val = buff_num;
prod_sem = set_sem(prod_key,sem_val,sem_flg);
sem_val = 0;
cons_sem = set_sem(cons_key,sem_val,sem_flg);
sem_val = 1;
mtx_sem = set_sem(mtx_key,sem_val,sem_flg);
while(1)
{
P_operation(prod_sem);
P_operation(mtx_sem);
buff_ptr[*pput_ptr] = 'A'+ *pput_ptr;
sleep(rate);
printf("%d producer put: %c to Buffer[%d]\n",getpid(),buff_ptr[*pput_ptr],*pput_ptr);
*pput_ptr = (*pput_ptr+1) % buff_num;
V_operation(mtx_sem);
V_operation(cons_sem);
}
return EXIT_SUCCESS;
}
7.消费者程序实现
如同生产者上述,代码如下:
#include "ipc.h"
int main(int argc,char *argv[])
{
int rate;
if(argv[1] != NULL)
rate = atoi(argv[1]);
else
rate = 3;
buff_key = 101;
buff_num = 8;
cget_key = 103;
cget_num = 1;
shm_flg = IPC_CREAT | 0644;
buff_ptr = (char *)set_shm(buff_key,buff_num,shm_flg);
cget_ptr = (int *)set_shm(cget_key,cget_num,shm_flg);
prod_key = 201;
mtx_key = 202;
cons_key = 301;
sem_flg = IPC_CREAT | 0644;
sem_val = buff_num;
prod_sem = set_sem(prod_key,sem_val,sem_flg);
sem_val = 0;
cons_sem = set_sem(cons_key,sem_val,sem_flg);
sem_val = 1;
mtx_sem = set_sem(mtx_key,sem_val,sem_flg);
while(1)
{
P_operation(cons_sem);
P_operation(mtx_sem);
sleep(rate);
printf("%d consumer get: %c fromBuffer[%d]\n",getpid(),buff_ptr[*cget_ptr],*cget_ptr);
*cget_ptr = (*cget_ptr+1) % buff_num;
V_operation(mtx_sem);
V_operation(prod_sem);
}
return EXIT_SUCCESS;
}
8.编写Makefile
由于本项目工程有多个文件,所以需要Makefile来方便编译
$emacs makefile
hdrs = ipc.h
opts = -g -c
c_src = consumer.c ipc.c
c_obj = consumer.o ipc.o
p_src = producer.c ipc.c
p_obj = producer.o ipc.o
all: producer consumer
consumer: $(c_obj)
gcc $(c_obj) -o consumer
consumer.o: $(c_src) $(hdrs)
gcc $(opts) $(c_src)
producer: $(p_obj)
gcc $(p_obj) -o producer
producer.o: $(p_src) $(hdrs)
gcc $(opts) $(p_src)
clean:
rm consumer producer *.o
![在这里插入图片描述](https://img-blog.csdnimg.cn/d27935982d9c4cc8ae80e8048ea52c08.png)
9.编译
$ make
运行时打开多个终端窗口,输入
$./producer 1
另一个窗口输入:
$./consumer 1
…
这时可以看到同步过程
![在这里插入图片描述](https://img-blog.csdnimg.cn/8d000bb050a14697bcc4b09fdbc88653.png)
这里的多个,不是指两个,可以多输入几个producer,consumer,时间也可以不一样,这样才是多个生产/消费者。
总结
这个实验,建立了ipc.h头文件,ipc.c,consumer.c,producer.c四个文件,然后用makefile进行编译。在编写过程中,用了共享内存,消息队列,还有信号,所以,就很复杂。
它的consumer和producer是分开进行的,也可以写多个fork(),将这几个程序合并为一个,一起运行。
小白大概就似懂非懂写了这么些(´。_。`)
补充
合并后的源代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <sys/msg.h>
#define BUFSZ 256
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;
int prod_sem;
key_t cons_key;
int cons_sem;
int sem_val;
int sem_flg;
int shm_flg;
key_t mtx_key;
int mtx_sem;
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;
}
int P_operation(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 V_operation(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;
}
int set_sem(key_t sem_key,int sem_val,int sem_flg)
{
int sem_id;
Sem_uns sem_arg;
if((sem_id = get_ipc_id("/proc/sysvipc/sem",sem_key)) < 0 )
{
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;
}
char * set_shm(key_t shm_key,int shm_num,int shm_flg)
{
int i,shm_id;
char * shm_buf;
if((shm_id = get_ipc_id("/proc/sysvipc/shm",shm_key)) < 0 )
{
if((shm_id = shmget(shm_key,shm_num,shm_flg)) <0)
{
perror("shareMemory set error");
exit(EXIT_FAILURE);
}
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;
}
if((shm_buf = (char *)shmat(shm_id,0,0)) < (char *)0)
{
perror("get shareMemory error");
exit(EXIT_FAILURE);
}
return shm_buf;
}
int set_msq(key_t msq_key,int msq_flg)
{
int msq_id;
if((msq_id = get_ipc_id("/proc/sysvipc/msg",msq_key)) < 0 )
{
if((msq_id = msgget(msq_key,msq_flg)) < 0)
{
perror("messageQueue set error");
exit(EXIT_FAILURE);
}
}
return msq_id;
}
int main()
{
int rate = 3;
pid_t fpid = fork();
fpid = fork();
buff_key = 101;
buff_num = 8;
pput_key = 102;
pput_num = 1;
cget_key = 103;
cget_num = 1;
shm_flg = IPC_CREAT | 0644;
buff_ptr = (char *)set_shm(buff_key,buff_num,shm_flg);
pput_ptr = (int *)set_shm(pput_key,pput_num,shm_flg);
cget_ptr = (int *)set_shm(cget_key,cget_num,shm_flg);
prod_key = 201;
mtx_key = 202;
cons_key = 301;
sem_flg = IPC_CREAT | 0644;
sem_val = buff_num;
prod_sem = set_sem(prod_key,sem_val,sem_flg);
sem_val = 0;
cons_sem = set_sem(cons_key,sem_val,sem_flg);
sem_val = 1;
mtx_sem = set_sem(mtx_key,sem_val,sem_flg);
if(fpid != 0)
{
while(1)
{
P_operation(prod_sem);
P_operation(mtx_sem);
buff_ptr[*pput_ptr] = 'A'+ *pput_ptr;
sleep(rate-1);
printf("%d producer put: %c to Buffer[%d]\n",getpid(),buff_ptr[*pput_ptr],*pput_ptr);
*pput_ptr = (*pput_ptr+1) % buff_num;
V_operation(mtx_sem);
V_operation(cons_sem);
}
}else
{
while(1)
{
P_operation(cons_sem);
P_operation(mtx_sem);
printf("%d consumer get: %c fromBuffer[%d]\n",getpid(),buff_ptr[*cget_ptr],*cget_ptr);
*cget_ptr = (*cget_ptr+1) % buff_num;
V_operation(mtx_sem);
V_operation(prod_sem);
}
}
return EXIT_SUCCESS;
}
根据代码重置流程图
关于源代码中的一点问题
仔细分析运行的流程图后发现似乎程序有些内容并不重要
- 头文件ipc.h中,”int down(int sem_id);” 与 “int up(int sem_id);”这两个函数在这个实验中并没有用到。而ipc.c中的”int P_operation(int sem_id)“ 与 ”int V_operation(int sem_id)“并没有被声明,所以此处的两个函数声明因换为PV操作的函数声明;
- <sys/msg.h>,事实上,这份代码只在ipc.h与ipc.c中写了关于msg的内容,实验过程中,完全没有用上这部分内容,可以删去所有相关内容;
- int get_ipc_id(char * proc_file,key_t key) ;这个函数是为了检查原本是否有sem_id,检查信号量是否建立,如果没有,就调用 semget() 与 shmget(),但事实上这两个函数本身就会根据键值去查找是否已经建立,如果建立就返回已建立的信息,没有就建立,所以这个函数也可以删去<sys/ipc.h>也可以直接删掉;
- 这个源程序反复运行,后,单独运行producer或consumer可能有bug,嗯,小白搞不明白。
- (这之后都是新加的)补充上一点,后来发现sem_id在系统文件中未删除,也就是上一次的运行数据仍然保留,第一次运行后再次运行都是基于之前的结果运行,不知道为什么会出现bug
- 为了解决这个问题,就在最终代码删除了这三个的sem_id,之后每一次运行这个程序都会重新创建来避免出现bug
- 这个代码原本,往共享区放东西,然后生产者取,取了之后只是读取而已,也就是说,只要共享区被放过东西,那么生产者就可以无限的从中读出数据。大概就是这样……,也正式,第四点,运行过一次后,可以单独运行consumer.c,还能取出东西的原因。
最终的精简代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <sys/types.h>
int set_sem(key_t sem_key,int sem_val,int sem_flg)
{
int sem_id;
int sem_arg;
if((sem_id = semget(sem_key,1,sem_flg)) < 0)
{
perror("semaphore create error");
exit(1);
}
if(semctl(sem_id,0,SETVAL,sem_val) <0)
{
perror("semaphore set error");
exit(1);
}
return sem_id;
}
char * set_shm(key_t shm_key,int shm_num,int shm_flg)
{
int i,shm_id;
char * shm_buf;
if((shm_id = shmget(shm_key,shm_num,shm_flg)) <0)
{
perror("shareMemory set error");
exit(1);
}
if((shm_buf = (char *)shmat(shm_id,0,0)) < (char *)0)
{
perror("get shareMemory error");
exit(1);
}
for(i=0; i<shm_num; i++)
shm_buf[i] = 0;
return shm_buf;
}
int P_operation(int sem_id)
{
struct sembuf buf={0,-1,SEM_UNDO};
if((semop(sem_id,&buf,1)) <0)
{
perror("请求失败");
exit(1);
}
return 0;
}
int V_operation(int sem_id)
{
struct sembuf buf={0,1,SEM_UNDO};
if((semop(sem_id,&buf,1)) < 0)
{
perror("释放失败");
exit(1);
}
return 0;
}
int main()
{
int rate = 3;
pid_t fpid = fork();
fpid = fork();
key_t buff_key = 104;
int buff_num = 8;
key_t pput_key = 105;
int pput_num = 1;
key_t cget_key = 106;
int cget_num = 1;
int shm_flg = IPC_CREAT | 0644;
char* buff_ptr = (char *)set_shm(buff_key,buff_num,shm_flg);
int* pput_ptr = (int *)set_shm(pput_key,pput_num,shm_flg);
int* cget_ptr = (int *)set_shm(cget_key,cget_num,shm_flg);
key_t prod_key = 205;
key_t mtx_key = 206;
key_t cons_key = 207;
int sem_flg = IPC_CREAT | 0644;
int prod_sem = set_sem(prod_key,buff_num,sem_flg);
int cons_sem = set_sem(cons_key,0,sem_flg);
int mtx_sem = set_sem(mtx_key,1,sem_flg);
if (fpid != 0)
{
for (int i = 0; i < 20; i++)
{
int s = rand()%2+1;
P_operation(prod_sem);
P_operation(mtx_sem);
sleep(s);
buff_ptr[*pput_ptr] = 65 + (*pput_ptr);
printf("%d producer put: %c to Buffer[%d]|\n",getpid(),buff_ptr[*pput_ptr],*pput_ptr);
*pput_ptr = (*pput_ptr+1) % buff_num;
V_operation(mtx_sem);
V_operation(cons_sem);
}
}
else
{
for (int i = 0; i < 20; i++)
{
int s = rand()%2+1;
P_operation(cons_sem);
P_operation(mtx_sem);
sleep(s);
printf(" |");
printf("%d consumer get: %c fromBuffer[%d]\n", getpid(),buff_ptr[*cget_ptr], *cget_ptr);
buff_ptr[*cget_ptr] = 0;
*cget_ptr = (*cget_ptr+1) % buff_num;
V_operation(mtx_sem);
V_operation(prod_sem);
}
}
semctl(cons_sem, 0, IPC_RMID);
semctl(prod_sem, 0, IPC_RMID);
semctl(mtx_sem, 0, IPC_RMID);
return 0;
}
运行结果是这样的
![左边生产者,右边消费者](https://img-blog.csdnimg.cn/7362494e43bc47ebbf66aad0cc513403.png)
这个代码的fork了两次,两个生产者,两个消费者,谁先到最后,会删除sem_id,然后后面的进程会因为没有id了就停止了。
然后,我亲爱的好证明老师问sleep如果去掉,运行结果是否会相同,我实测是有部分差别的,但是得到的回答是应该相同的。呃,因为互斥的话,似乎,确实该相同,但是,为什么最后运行结果又不同。欸,对,sleep的初衷是为了我能在运行的时候看清咋运行的,随机的睡眠时间则是因为我起初也认为睡眠时间不同会使结果不同。
头大,不懂,岂可修!/(ㄒoㄒ)/~~
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)