FIFO Linux - write()函数突然终止程序

4
英译中:

我正在用C语言实现一个管道,其中多个生产者程序(在我的例子中有9个)将数据写入一个单一的消费者程序。

问题是,当调用write()函数时,一些生产者(有时一个或两个)突然退出程序。

代码很简单,以下是生产者代码:

#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <poll.h>

#define MSG_SIZE_BYTES 4

void send(unsigned int * msg){

    int fd, msg_size;
    int r;
    char buffer [5];
    char myfifo[50] = "/tmp/myfifo";

    fd = open(myfifo, O_WRONLY);

    if(fd == -1){
        perror("error open SEND to fifo");
    }

    r = write(fd, msg, MSG_SIZE_BYTES);

    if(r == -1){
        perror("error writing to fifo");
     }

    close(fd);
    printf("Message send\n");
}

int main(int argc, char *argv[]){
    int cluster_id = atoi(argv[1]);
    unsigned int msg[1];
    msg[0] = cluster_id;

    while(1){
        printf("Press a key to continue...\n");
        getchar();
        send(msg);
    }
}


这是消费者代码。
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <poll.h>

#define MSG_SIZE_BYTES 4

int receive(unsigned int * received_msg){
    int fd, msg_size;
    int ret_code;
    char buffer [5];
    char myfifo[50] = "/tmp/myfifo";

    fd = open(myfifo, O_RDONLY);

    if(fd == -1) 
       perror("error open RECV to fifo");

    ret_code = read(fd, received_msg, MSG_SIZE_BYTES);

    close(fd);

    if (ret_code == -1){
        printf("\nERROR\n");    
        return 0;
    }

    return 1;
}

void main(){

    mkfifo("/tmp/myfifo", 0666);

    unsigned int msg[1];
    while(1){
       receive(msg);
       printf("receive msg from id %d\n", msg[0]);

    }
}


我正在使用以下命令编译生产者和消费者:gcc -o my_program my_program.c
要重现问题,您需要打开9个终端来运行每个生产者和1个终端来运行消费者。 执行消费者:./consumer 同时在所有终端中执行生产者,将相关联的ID通过命令行传递给每个执行文件。例如:./producer 0,./producer 1。
在生产者发送消息若干次(平均10次)之后,任意一个生产者将突然停止执行,显示问题。
以下图像描述了执行情况: 准备执行的终端 以下图像描述了生产者ID 3的错误 生产者3的错误 提前致谢。

5
请提供一个最小化可复现示例。通过“突然”我理解为它崩溃了,但是根据您提供的内容很难判断原因。一个猜测是您正在违反内存读取msg缓冲区。 - pbn
1
即使“打开”失败,您仍然可以继续编写/读取。 - kiran Biradar
你可以获取失败程序的退出状态吗? - Ian Abbott
你能否包含 [mcve] 以便我们可以自行测试? - kiran Biradar
好的,我会尝试在一个可验证的示例中隔离问题。 - mcro
显示剩余8条评论
2个回答

4

看起来消费者程序在读取数据后关闭了管道的读取端口:

fd = open(myfifo, O_RDONLY);

if(fd == -1){
     perror("error open RECV to fifo");
}
ret_code = read(fd, received_msg, MSG_SIZE_BYTES);

close(fd);

所有当前尝试write()数据的其他编写器(即在write()系统调用中被阻塞的编写器)现在会收到一个SIGPIPE信号,这会导致程序终止(如果没有指定其他信号处理)。

当生产者正在写入数据时,您的消费者程序可能不关闭文件描述符。只需读取下一个数据而不关闭即可。


我注释掉了消费者的close(fd)。它可以工作更长时间,但是在消费者接收到一长串消息后,会显示错误:error open RECV to fifo: Too many open files,这很有道理,因为消费者在每个循环中都打开fifo。我尝试在while循环之前调用消费者的open()函数,但不起作用,因为在每个交换的消息之间需要打开以进行生产者和消费者之间的握手。 - mcro
1
如果你不关闭它,那么第一次打开时也不需要再次打开。 - Ctx
如果我把open()放在开头(在消费者循环之外),消费者程序就会停滞不前,永远无法进入循环。 - mcro
如果你把用于读取FIFO的open()放在循环外面,那么消费者将不会得到任何返回值,直到第一个生产者运行自己的open()来写入FIFO。之后,当没有生产者打开FIFO时,消费者将定期收到EOF。此时,它必须重新打开FIFO进行读取,这将再次导致它等待下一个生产者打开FIFO。另请参见无法正确使用Unix FIFOLinux上FIFO的奇怪行为等其他问题。 - Jonathan Leffler

1

问题已解决:

问题是我在每个消息中打开和关闭FIFO,导致某些写入尝试中出现了Broken pipe。将close()移除,并在代码开头插入open()函数,同时针对生产者和消费者进行修改,而不是在循环内部进行修改,解决了这个问题。

以下是修复了错误的生产者代码:

#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <poll.h>

#define MSG_SIZE_BYTES 4

int my_fd;

void send(unsigned int * msg){

    int fd, msg_size;
    int r;
    char buffer [5];
    char myfifo[50] = "/tmp/myfifo"

    if(fd == -1){
        perror("error open SEND to fifo");
    }

    r = write(my_fd, msg, MSG_SIZE_BYTES);

    if(r == -1){
        perror("error writing to fifo");
     }

    //close(fd);
    printf("Message send\n");
}

int main(int argc, char *argv[]){
    int cluster_id = atoi(argv[1]);
    unsigned int msg[1];
    msg[0] = cluster_id;

    my_fd = open("/tmp/myfifo", O_WRONLY);

    while(1){
        printf("Press a key to continue...\n");
        getchar();
        send(msg);
    }
}

这里是消费者代码:

#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <poll.h>

#define MSG_SIZE_BYTES 4

int my_fd;

int receive(unsigned int * received_msg){
    int fd, msg_size;
    int ret_code;
    char buffer [5];
    char myfifo[50] = "/tmp/myfifo";

    if(fd == -1) 
       perror("error open RECV to fifo");

    ret_code = read(my_fd, received_msg, MSG_SIZE_BYTES);

    //close(fd);

    if (ret_code == -1){
        printf("\nERROR\n");    
        return 0;
    }

    return 1;
}

void main(){

    mkfifo("/tmp/myfifo", 0666);
    my_fd = open("/tmp/myfifo", O_RDONLY);

    unsigned int msg[1];

    while(1){
       receive(msg);
       printf("receive msg from id %d\n", msg[0]);

    }
}

谢谢大家!!

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接