【操作系统|linux案例】centos7|ubuntu|linux进程同步生产者消费者问题【代码注释超详解】
实验:用P,V操作和信号量实现进程同步和互斥生产者消费者问题思路:1.同步关系:缓冲区不为空时才能消费,缓冲区满时就不能生产;2.互斥关系:缓冲区是一个临界资源,因此必须互斥使用;设置两个同步信号量FULL,EMPTY,一个互斥信号量MUTEX。*信号量操作使用到的函数:semget(),semctl(),semop():用semget()函数,semctl()函数来对信号量进行创建...
一、实验要求:
用P,V操作和信号量实现进程同步和互斥生产者消费者问题 。
二、代码思路:
1.同步关系:缓冲区不为空时才能消费,缓冲区满时就不能生产;
2.互斥关系:缓冲区是一个临界资源,因此必须互斥使用;
设置两个同步信号量FULL,EMPTY,一个互斥信号量MUTEX。
*信号量操作使用到的函数:semget(),semctl(),semop():
用semget()函数,semctl()函数来对信号量进行创建和初始化
程序最后使用semctl()函数删除信号量来释放内存。
3.缓冲区的实现:使用共享内存代表缓冲区。
共享内存的原理是将进程的地址空间映射到一个共享存储段。
*共享内存操作使用到的函数:shmget(),shmat(),shmctl():
使用shmget()函数实现共享主存段的创建,shmget()返回共享内存区的ID。
对于已经申请到的共享段,进程需用shmat()函数把它附加到自己的虚拟空间中才能对其进行读写
对共享内存操作完毕后使用shmctl()函数撤销共享内存段。
4.缓冲区的数据结构:缓冲区采用环形队列表示,利用头、尾指针来存放读取数据,
以及判断队列是否为空。
5.P,V操作:使用semop()函数对信号量进行+1,-1操作来实现P,V操作。
6.创建进程:使用循环创建2个生产者和2个消费者进程,用fork()函数来创建;
7.生产过程:利用rand()函数产生A~Z的随机字符来作为生产内容,存放到缓冲区中(环形队列的对尾)。
8.消费过程:从环形队列的队首取出存放的字符表示消费。
三、代码
#include <stdio.h>//支持printf
#include <stdlib.h>//支持exit
#include <unistd.h>//支持sleep
#include <time.h>
#include <sys/ipc.h>
#include <sys/shm.h>//支持shmget shmat等
#include <sys/sem.h>//支持semget等
#include <sys/types.h>
#include <sys/wait.h>//支持wait
#define MAX_BUFFER_SIZE 3//缓冲区最大值
/*信号量的索引,用于semctl函数参数*/
#define SEM_FULL 0//同步信号量,表示满缓冲区的值,初始值0
#define SEM_EMPTY 1//同步信号量,表示空缓冲区的值初始值为MAX_BUFFER_SIZE
#define MUTEX 2 //互斥信号量,用于对缓冲区进行操作
struct my_buffer//缓冲区采用环形队列表示
{
int head;//头
int tail;//尾
char str[MAX_BUFFER_SIZE];//用来存放字母的数组
int num; /*缓冲区里字母数量*/
int is_empty;//是否为空,=1空
};
const int N_CONSUMER = 2;/*消费者数量*/
const int N_PRODUCER = 2;/*生产者数量*/
const int N_WORKTIME = 5;/*工作次数*/
pid_t pid_c;//消费者
pid_t pid_p;//生产者
/*得到A~Z的一个随机字母*/
char getRandChar()
{
char letter;
srand((unsigned)time(NULL));//根据当前时间产生随机数种子
letter = (char)((rand() % 26) + 'A');
return letter;
}
/*sem_id 表示信号量集合的 id*/
/*sem_num 表示要处理的信号量在信号量集合中的索引*/
/*P操作*/
void P(int sem_id, int sem_num)
{
/*semop()函数:
* 用来对信号量集表示符为semid中的一个或多个信号量进行P或V操作
* 函数原型:int semop(int semid, struct sembuf *sops, unsigned nsops)
* 函数调用失败返回-1
* 第一个参数semid表示信号量集的标识符;
* 第三个参数nsops表示进行操作信号量的个数,设置等于1,表示只完成对一个信号量的操作
* 第二个参数spos是指向进行操作的信号量集结构体数组的首地址,
* 此结构的具体说明如下:
* struct sembuf {
* short sem_num; //信号量集合中的信号量序号,0代表第1个信号量
* short sem_op;//对信号量的操作
* //若sem_op>0进行V操作,信号量值加sem_op,表示进程释放控制的资源
* //若sem_op<0进行P操作,信号量值减sem_op,若(semval-val)<0(semval为该信号量值),则调用进程阻塞,直到资源可用
* short sem_flg; //0 设置信号量的默认操作
* };
*/
struct sembuf spos;
spos.sem_num = sem_num;//信号量编号
spos.sem_op = -1;/*表示要把信号量减一*/
spos.sem_flg = SEM_UNDO;
if (semop(sem_id, &spos, 1) < 0) {
perror("P failed");
exit(1);
}
}
/*V操作*/
void V(int sem_id, int sem_num)
{
struct sembuf spos;
spos.sem_num = sem_num;
spos.sem_op = 1;/*表示要把信号量+1*/
spos.sem_flg = SEM_UNDO;
if (semop(sem_id, &spos, 1) < 0) {
perror("V failed");
exit(1);
}
}
void produce(struct my_buffer *buffer,int i){
/*生产产品*/
printf("我是第 %d 个生产者进程,PID = %d\n", i + 1, getpid());
char c = getRandChar();/*随机获取字母*/
buffer->str[buffer->tail] = c;//写入队尾
buffer->tail = (buffer->tail + 1) % MAX_BUFFER_SIZE;//尾指针+1
buffer->is_empty = 0;//非空标记设置为0
buffer->num++;//缓冲区数据+1
/*打印缓冲区中的数据*/
printf("缓冲区数据(%d个):", buffer->num);
/*tail-1>=head表示没有满*/
int p;//缓冲区的数据的下标,从队尾往对首输出
p = (buffer->tail - 1 >= buffer->head) ? (buffer->tail - 1) : (buffer->tail - 1 + MAX_BUFFER_SIZE);
for (p; !(buffer->is_empty) && p >= buffer->head; p--)
{
printf("%c", buffer->str[p % MAX_BUFFER_SIZE]);
}
printf("\t 生产者 %d 放入 '%c'. \n", i + 1, c);
fflush(stdout);
}
void consume(struct my_buffer *buffer,int i){
/*消费数据*/
printf("我是第 %d 个消费者进程,PID = %d\n", i + 1, getpid());
char lt = buffer->str[buffer->head];//取出对首数据
buffer->head = (buffer->head + 1) % MAX_BUFFER_SIZE;//头指针+1
buffer->is_empty = (buffer->head == buffer->tail); //头指针追上尾指针则为空
buffer->num--;//缓冲区数据-1
/*打印缓冲区中的数据*/
printf("缓冲区数据(%d个):", buffer->num);
int p;
p = (buffer->tail - 1 >= buffer->head) ? (buffer->tail - 1) : (buffer->tail - 1 + MAX_BUFFER_SIZE);
for (p; !(buffer->is_empty) && p >= buffer->head; p--)
{
printf("%c", buffer->str[p % MAX_BUFFER_SIZE]);
}
printf("\t 消费者 %d 取出 '%c'. \n", i + 1, lt);
fflush(stdout);
}
int main(int argc, char ** argv)
{
int i,j;
/*shmget()函数用来创建一个共享内存对象,创建成功则返回共享内存标识符,失败返回-1
* 原型:int shmget(key_t key, size_t size, int shmflg)
* size表示新建的共享内存大小,以字节为单位。
* shmflg参数为模式标志参数,使用时需要与IPC对象存取权限
*(如0600表示用户可以读写改内存)进行|运算来确定信号量集的存取权限 */
int shm_id;
shm_id = shmget(IPC_PRIVATE, MAX_BUFFER_SIZE, 0600 | IPC_CREAT);
if (shm_id < 0)
{
perror("create shared memory failed");
exit(1);
}
/*shmat()函数:连接共享内存标识符为shmid的共享内存,连接成功后把共享内存区对象映射到调用进程的地址空间,随后可像本地空间一样访问。
* 原型:void *shmat(int shmid, const void *shmaddr, int shmflg)
* shmaddr:指定共享内存出现在进程内存地址的什么位置
* shmflg:SHM_RDONLY:为只读模式,其他为读写模式
* 调用成功则返回:附加好的共享内存地址,失败返回-1*/
struct my_buffer *buffer;//实例化一个环形队列的指针
/*用shmat()函数连接共享内存,返回共享内存的起始地址*/
buffer = shmat(shm_id, 0, 0);
if (buffer == (void*)-1)
{
perror("add buffer to using process space failed!\n");
exit(1);
}
/*semget()函数创建一个新的信号量集,原型:
* int semget(key_t key, int nsems, int semflg);
* 返回值:如果成功则返回信号量集的IPC标识符。如果失败则返回-1。
* nsems表示一个新的信号量集中应该创建新的信号量的个数*/
int sem_id;
if ((sem_id = semget(IPC_PRIVATE, 3,0600 | IPC_CREAT)) < 0)
{ //用semget函数创建3个信号量SEM_EMPTY,SEM_FULL,MUTEX
perror("create semaphore failed! \n");
exit(1);
}
/*用semctl函数为信号量赋初值*/
/*semctl()函数:
* 原型:int semctl(int semid, int semnum, int cmd, union semun arg);
* semnum:信号量集数组上的下标,表示某一个信号量
* cmd:操作
* 操作需要的值来自共用体union semun中的参数*/
/*SETVAL操作初始化信号量的值*/
if (semctl(sem_id, SEM_FULL, SETVAL, 0) == -1)
{ /*将索引为0的信号量SEM_FULL赋初值为0*/
perror("sem set value error! \n");
exit(1);
}
if (semctl(sem_id, SEM_EMPTY, SETVAL, 3) == -1)
{ /*将索引为1的信号量SEM_EMPTY赋初值为3*/
perror("sem set value error! \n");
exit(1);
}
if (semctl(sem_id, MUTEX, SETVAL, 1) == -1)
{ /*将索引为3的信号量MUTEX赋初值为1*/
perror("sem set value error! \n");
exit(1);
}
/*缓冲区设置为空*/
buffer->head = 0;
buffer->tail = 0;
buffer->is_empty = 1;
buffer->num = 0;
/*创建生产者进程*/
for (i = 0; i < N_PRODUCER; i++)
{
pid_p = fork();
if (pid_p < 0)/*进程创建失败*/
{
perror("the fork failed");
exit(1);
}
else if (pid_p == 0)
{
/*用shmat()函数连接共享内存,返回共享内存的起始地址*/
buffer = shmat(shm_id, 0, 0);
if (buffer == (void*)-1)
{
perror("add buffer to using process space failed!\n");
exit(1);
}
for (j = 0; j < N_WORKTIME; j++)
{
P(sem_id, SEM_EMPTY);//空缓冲区-1
P(sem_id, MUTEX);//占用缓冲区
sleep(2);
produce(buffer,i);//生产内容
printf("生产者%d工作了%d次\n",i+1,j+1);
V(sem_id, MUTEX);//释放缓冲区
sleep(5);
V(sem_id, SEM_FULL);//满缓冲区+1
}
/*shmdt()函数说明:与shmat函数相反,是用来断开与共享内存附加点的地址,禁止本进程访问此片共享内存
函数原型:int shmdt(const void *shmaddr)*/
/*将共享内存与进程之间解除连接 */
shmdt(buffer);
exit(0);
}
}
for (i = 0; i < N_CONSUMER; i++)
{
pid_c = fork();
if (pid_c < 0)/*调用fork失败*/
{
perror("the fork failed");
exit(1);
}
else if (pid_c == 0)//创建子进程成功
{
/*用shmat()函数连接共享内存,返回共享内存的起始地址*/
buffer = shmat(shm_id, 0, 0);
if (buffer == (void*)-1)
{
perror("add buffer to using process space failed!\n");
exit(1);
}
for (j = 0; j < N_WORKTIME; j++)
{
P(sem_id, SEM_FULL);//满缓冲区-1
P(sem_id, MUTEX);//占用缓冲区
sleep(2);
consume(buffer,i);//消费者消费
printf("消费者%d工作了%d次\n",i+1,j+1);
V(sem_id, MUTEX);//释放缓冲区
V(sem_id, SEM_EMPTY);//空缓冲区+1
}
/*将共享内存与进程之间解除连接 */
shmdt(buffer);
exit(0);
}
}
/*主进程最后退出 */
while (wait(0) != -1);//子进程调用结束
shmdt(buffer); /*将共享内存与进程之间解除连接*/
shmctl(shm_id, IPC_RMID, 0);/*当cmd为IPC_RMID时,删除该共享段*/
semctl(sem_id, IPC_RMID, 0);/*删除信号量集*/
printf("主进程运行结束!\n");
fflush(stdout);
exit(0);
return 0;
}
四、运行结果
更多推荐
所有评论(0)