在另一个进程中访问共享内存缓冲区

Posted

技术标签:

【中文标题】在另一个进程中访问共享内存缓冲区【英文标题】:Accessing a shared memory buffer in another process 【发布时间】:2018-02-07 19:34:01 【问题描述】:

我正在尝试使用互斥锁和共享缓冲区来解决生产者消费者问题,但在访问共享缓冲区结构中的值时遇到问题,特别是 char 数组。当我在一个终端中调用 producer.c 文件并使用

打印值(输入是字母表的 txt 文件)时
printf("%c", newBuff->bytes[newBuff->rear]); 

字符确实显示为正常,但是当我在 consumer.c 中做同样的事情时,但使用

printf("%c", newBuff->bytes[newBuff->front]);

这些值显示为空白。 newBuff->front 值为零,所以它应该打印字母 a。当我在 consumer.c 中访问我的结构中的其他值时,例如 front、count 或 back,它们是正确的。共享内存创建和附件也可以正常工作,所以我认为问题是我没有将 char 值正确存储在数组中,或者我试图错误地访问它们。在下面的代码中,我将 printf 放在 producer.c 的循环中,然后放在 consumer.c 的循环之外,因此我知道在消费者开始提取数据之前存在一个值。

Consumer.c

typedef struct buffer
    pthread_mutex_t lock;
    pthread_cond_t shout;
    int front;
    int rear;
    int count;
    int endOfFile;
    char bytes[1024];
 buffer;


int main(int argc, char const *argv[]) 
    int i=0;
    FILE *file = fopen(argv[1], "w");
    if (argc != 2)
        printf("You must enter in a file name\n");
    
    int shmid, swapCount=0;
    char swapBytes[] = "";
    char path[] = "~";
    key_t key = ftok(path, 7);
    buffer* newBuff;
    if ((shmid = shmget(key, SIZE, 0666 | IPC_CREAT | IPC_EXCL)) != -1) 
        newBuff = (buffer*) shmat(shmid, 0, 0);
        printf("successful creation\n");
        newBuff->front = 0;
        newBuff->count = 0;
        newBuff->endOfFile = 0;
        pthread_mutexattr_t attr;
        pthread_condattr_t condAttr;

        pthread_mutexattr_init(&attr);
        pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
        pthread_mutex_init(&newBuff->lock, &attr);

        pthread_condattr_init(&condAttr);
        pthread_condattr_setpshared(&condAttr, PTHREAD_PROCESS_SHARED);
        pthread_cond_init(&newBuff->shout, &condAttr);
     //shared memory creation

    else if ((shmid = shmget(key, 0, 0)) != -1)
        printf("%d\n", shmid);
        printf("successful attachment\n" );
        newBuff = (buffer*) shmat(shmid, 0, 0);
        printf("%c\n", newBuff->count);
    
    else
        printf("oops\n");
        exit(0);
    
    pthread_mutex_lock(&newBuff->lock);
    printf("%c\n", newBuff->bytes[newBuff->front]);
    while (newBuff->endOfFile != 1)
    
        while (newBuff->count == 0)
            pthread_cond_signal(&newBuff->shout);
            pthread_cond_wait(&newBuff->shout, &newBuff->lock);
        
        newBuff->front = ((newBuff->front + 1)%SIZE);
        newBuff->count--;
    
    pthread_mutex_unlock(&newBuff->lock);
    shmdt(&newBuff);

    //pthread_mutexattr_destroy(&attr);
    //pthread_condattr_destroy(&condAttr);*/

    return 0;

Producer.c

typedef struct buffer
    pthread_mutex_t lock;
    pthread_cond_t shout;
    int front;
    int rear;
    int count;
    int endOfFile;
    char bytes[1024];
 buffer;


int main(int argc, char const *argv[]) 
    FILE *file = fopen(argv[1], "r");
    if (argc != 2)
        printf("You must enter in a file dumbass\n");
    
    int shmid;
    char path[] = "~";
    key_t key = ftok(path, 7);
    buffer* newBuff;
    printf("dfasdfasdf\n");
    if ((shmid = shmget(key, SIZE, 0666 | IPC_CREAT | IPC_EXCL)) != -1) 
        newBuff = (buffer*) shmat(shmid, 0, 0);
        printf("successful creation\n");


        newBuff->front = 0;
        newBuff->count = 0;
        newBuff->endOfFile=0;
        pthread_mutexattr_t attr;
        pthread_condattr_t condAttr;

        pthread_mutexattr_init(&attr);
        pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
        pthread_mutex_init(&newBuff->lock, &attr);

        pthread_condattr_init(&condAttr);
        pthread_condattr_setpshared(&condAttr, PTHREAD_PROCESS_SHARED);
        pthread_cond_init(&newBuff->shout, &condAttr);
     //shared memory creation

    else if ((shmid = shmget(key, 0, 0)) != -1)
        printf("successful attachment\n" );
        newBuff = (buffer*) shmat(shmid, 0, 0);
    
    else
        printf("oops\n");
        exit(0);
    
    printf("%d\n", shmid);
    pthread_mutex_lock(&newBuff->lock);
    while (fscanf(file, "%c", &newBuff->bytes[newBuff->rear]) != EOF) //read in file
    
        printf("%c\n", newBuff->bytes[newBuff->rear]);
        while (newBuff->count >= SIZE) //buffer is full
            //("%c\n", newBuff->bytes[newBuff->rear]);
            pthread_cond_signal(&newBuff->shout);
            pthread_cond_wait(&newBuff->shout, &newBuff->lock);
        
        //printf("%c\n", newBuff->bytes[newBuff->rear]);
        newBuff->rear = ((newBuff->front + 1)%SIZE);
        newBuff->count++;
    
    newBuff->endOfFile = 1;
    pthread_cond_signal(&newBuff->shout);
    pthread_mutex_unlock(&newBuff->lock);

    shmdt(&newBuff);

    //pthread_mutexattr_destroy(&attr);
    //pthread_condattr_destroy(&condAttr);

    return 0;

【问题讨论】:

在某些时候,要么你的格式被破坏,要么你有一个函数之外的代码。请解决这个问题。另外,请删除重现问题所不需要的内容。我还建议您删除蛮力强制转换,它们通常仅用于隐藏问题,例如将 int 转换为指针时。 注意路径必须存在于对ftok的调用中,这是一个要求。并且使用 ~ 是错误的,因为它仅由 shell 扩展。所以使用您选择的真实路径名。这可能不是您的问题的原因,但它是一个要求... 调用shmget(key, 0, 0) 也是错误的,大小为0。您应该使用SIZE 常量(据我所知)。 除非你需要引用结构的 name,(据我所知,你不需要)然后你不需要它,一般来说,你应避免使用与 name 相同的符号创建 type,即您创建了一个带有符号 buffer 的新类型,以及具有相同符号的名称。用typedef struct ...buffer;替换你所拥有的 我认为这是一种风格考虑,@rykker。当然没有必要声明一个带有标签的结构类型,并声明相同的标识符作为该类型的别名,就像 OP 所做的那样,但它本质上并不是有害的,并且一些编码风格调用为它。 【参考方案1】:

您的代码存在一些困难,其中一些已在 cmets 中解决:

ftok() 需要传递给它的路径来指定现有文件,但您传递的路径不需要。

您请求的共享内存比实际需要的少:只有缓冲区内容的大小,而不是整个 struct buffer 的大小。因为实际分配的共享内存量将四舍五入到页面大小的倍数,这最终可能没问题,但您应该确保通过请求您实际需要的数量来确保它没问题.

System V 共享内存段具有内核持久性,因此一旦创建,它们将继续存在,直到它们被明确删除或系统重新启动。你永远不会删除你的。您也只在第一次创建它时才初始化它的内容。因此,除非您在两次运行之间手动删除它,否则您将在第二次和后续运行中使用旧数据(例如,设置了文件结束指示器)。我建议让消费者安排移除它。

消费者只打印缓冲区中的一个字节数据,并且在验证是否有要读取的内容之前打印。

在向缓冲区添加一个字节后,生产者直到 向消费者发出信号后才更新可用字节数。 充其量是浪费,因为消费者在下次唤醒(如果有的话)之前不会看到计数的变化。

生产者根据当前的 front 值而不是当前的 rear 值错误地更新了缓冲区的 rear 索引。因此,数据不会被写入缓冲区数组中的正确位置。

一旦生产者设置了 endOfFile 标志,消费者将忽略所有剩余的未读字节,但其中一个。

如果生产者在完成时将count 保留为零,则消费者将死锁。

我发现您的程序的修改版本解决了所有这些问题,通过共享内存成功并准确地传递数据。

更新:

还有,

消费者和/或生产者初始化互斥锁和条件变量的方式本身并不安全。无论哪个进程尝试shmget() 第二个(或第三个,或...)在第一个完成初始化它们之前访问这些对象都是可能的。更一般地说,一旦附加了共享内存段,写入它就不会涉及固有的内存屏障。为了解决这些问题,SysV 共享内存的自然伴侣是 SysV 信号量。

【讨论】:

所有优点。值得一提的是内存栅栏,这是很好的衡量标准 感谢您抽出宝贵时间帮助我解决我的问题。至于没有以粗体表示的问题,它们大多已得到解决和修复。然而,旧的观点确实是修复我的代码的问题。看来我的教授给了我错误的算法来更改后索引,这就是我在消费者中没有收到正确输出的原因。我将在解决交换每个其他字节的最终问题后发布完成的代码输出文件。

以上是关于在另一个进程中访问共享内存缓冲区的主要内容,如果未能解决你的问题,请参考以下文章

从共享内存打印垃圾值

在 Java 服务和本机应用程序之间共享缓冲区,访问开销最小

共享内存主/从进程访问单个串口

限制子进程访问共享内存和消息队列

访问共享内存段的进程返回不同的值

跨线程共享内存访问