我正在尝试开发经典的生产者-消费者循环缓冲区示例,分别具有一个生产者和一个消费者。
但是我遇到了一些问题,我不知道它们来自哪里。 特别是生产者似乎在工作,但消费者总是选择 0。 我正在考虑共享内存和/或缓冲的一些问题,但它们似乎实现正确
代码解释:
-最初我创建了上面定义的STRUCT_BUFFER大小的共享内存区域。此时它实际上仅用于提供尺寸。
-我创建了 3 个信号量:互斥信号量、满信号量、空信号量。
-fork():在子进程(消费者)中,我调用缓冲区上的数据获取,如果找到的数字为 -1(终止标记),则将其放回并退出循环,终止。在父级(生产者)中,我执行一个 for ,将每个索引分别放在缓冲区上,最后放置一个 -1 (终止标记)。
-然后等到消费者吃完,消除红绿灯。 get 和 put 函数是生产者-消费者的经典代码。
ps。 SYSC() 宏仅用于错误管理。
#include <sys/wait.h>
#include <sys/mman.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>
#include <semaphore.h>
#include "macros.h"
#define SHM_NAME "/shm8"
#define MUTEX "/mutex"
#define FULL "/full"
#define EMPTY "/empty"
#define N 10
typedef struct{
int B[N];
int *head;
int *tail;
} STRUCT_BUFFER;
void put(int, int*, int*, int*, sem_t*, sem_t*, sem_t*);
int get(int*, int*, int*, sem_t*, sem_t*, sem_t*);
int main(){
int ret_value, mem_fd;
void * ptr;
pid_t pid;
sem_t * mutex;
sem_t * full;
sem_t * empty;
int * head;
int * tail;
// Creating shared memory area
SYSC(mem_fd, shm_open(SHM_NAME, O_CREAT | O_RDWR, 0666), "in shm_open");
SYSC(ret_value, ftruncate(mem_fd, sizeof(STRUCT_BUFFER)), "in ftruncate");
// Memory mapping
SYSCN(ptr, mmap(NULL, sizeof(STRUCT_BUFFER), PROT_READ | PROT_WRITE, MAP_SHARED, mem_fd, 0), "in mmap");
head = tail = ptr;
// Closing file descriptor
SYSC(ret_value, close(mem_fd), "in close");
// Creating semaphores
mutex = sem_open(MUTEX, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR, 1);
if(mutex == SEM_FAILED) {
perror("In sem_open");
exit(EXIT_FAILURE);
}
full = sem_open(FULL, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR, N);
if(full == SEM_FAILED) {
perror("In sem_open");
exit(EXIT_FAILURE);
}
empty = sem_open(EMPTY, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR, 0);
if(empty == SEM_FAILED) {
perror("In sem_open");
exit(EXIT_FAILURE);
}
// Creating a new process
SYSC(pid, fork(), "in fork");
if(!pid) {
// Consumer code
printf("Consumer %d starts\n", getpid());
int toGet;
while(1) {
toGet = get(ptr, head, tail, mutex, full, empty);
printf("Consumer %d consumed %d\n", getpid(), toGet);
// If termination token
if(toGet == -1) {
put(toGet, ptr, head, tail, mutex, full, empty);
break;
}
}
sleep(1);
printf("Consumer %d finishes\n",getpid());
exit(EXIT_SUCCESS);
} else {
// Producer code
printf("Producer %d starts\n",getpid());
int toPut;
int exitToken = -1;
// Production
for(int i = 0 ; i < N ; i++) {
toPut = i + 1;
put(toPut, ptr, head, tail, mutex, full, empty);
printf("Producer %d produced %d. Cycle:%d\n", getpid(), toPut, i);
sleep(1);
}
// Termination token
put(exitToken, ptr, head, tail, mutex, full, empty);
printf("Producer %d produced termination token\n", getpid());
sleep(1);
// Wait
SYSC(ret_value, wait(NULL), "in wait");
// Closing semaphores
sem_close(mutex);
sem_close(empty);
sem_close(full);
sem_unlink(MUTEX);
sem_unlink(FULL);
sem_unlink(EMPTY);
// Closing shared memory
munmap(ptr, sizeof(STRUCT_BUFFER));
printf("Producer %d finishes\n",getpid());
exit(EXIT_SUCCESS);
}
}
void put(int toPut, int * ptr, int * head, int * tail, sem_t * mutex, sem_t * full, sem_t * empty) {
sem_wait(full);
sem_wait(mutex);
// Critical section
ptr[*tail] = toPut;
*tail = ((*tail) + 1) % N;
sem_post(mutex);
sem_post(empty);
}
int get(int * ptr, int * head, int * tail, sem_t * mutex, sem_t * full, sem_t * empty) {
sem_wait(empty);
sem_wait(mutex);
// Critical section
int toGet;
toGet = ptr[*head];
*head = ((*head) + 1) % N;
sem_post(mutex);
sem_post(full);
return toGet;
}
在为您的
SYSC
和 SYSCN
宏推断出合适的定义后,我能够构建您的程序并确认您观察到的不当行为。
主要错误与
head
和 tail
指针有关。当您在 main
中将它们声明为指针并像这样初始化它们时:
head = tail = ptr;
,您将它们视为指向缓冲区本身的头元素和尾元素的指针。在这种情况下,头指针和尾指针最初相互别名以及基指针是可以的。
但是,在
get()
和 put()
函数中,您将 head
和 tail
视为指向存储头元素和尾元素的 indices 的变量的指针。例如:
ptr[*tail] = toPut; *tail = ((*tail) + 1) % N;
对于这种用途,这些指针相互或数据别名是不可以的。然后他们需要指向自己的、单独的对象。
其次,
head
和tail
索引需要在进程之间共享,因为消费者进程访问这两个索引。在原始代码中,使用的 head
和 tail
指针都是 main
的局部变量,因此不在进程之间共享(每个进程都有自己独立的副本)。看起来您可能计划通过共享 STRUCT_BUFFER
对象共享指针,但最终没有这样做。这是放置您需要共享的头部和尾部索引的合适位置。
另外,我会推荐
使用匿名内存映射而不是创建和映射 POSIX 共享内存段。除了更简单之外,这还可以消除由于共享内存段的寿命比您预期/需要的时间长或被同时运行的程序的不同实例共享而可能引起的问题。
使用存储在共享内存区域中的未命名(进程共享)信号量而不是命名信号量。这稍微简单一些,并且消除了信号量比您预期的寿命长或被同时运行的程序的不同实例共享可能引起的问题。
实现了上述所有内容后,您的
STRUCT_BUFFER
对象将包含 get()
和 put()
函数需要使用的所有信息,除了 put()
应存储的特定值之外。我强烈建议修改这些函数以接受指向 STRUCT_BUFFER
的指针,而不是所有这些单独的参数。