【问题标题】:Writing array of structures through pipe通过管道写入结构数组
【发布时间】:2012-10-17 17:49:31
【问题描述】:

我正在学习 Linux 中的流程管理,我需要让孩子和父母通过管道进行交流。我已经声明了两个结构:

typedef struct
{
    double min, max, avg;   /*Number stats*/
} Stats;

typedef struct {
    pid_t pid;      /*Process ID*/
    int first;      /*First number to process*/
    int last;       /*Last number to process*/
    int fd[2];      /*Pipe descriptor*/
    Stats stats;    /*Stats computed by process*/
}Process_list;

我需要 M 个子进程从一组数字中计算出一些统计数据(划分工作)。然后,子进程将读取 Process_list 结构,从头到尾处理数组中的数字(在此处指定)并将计算的统计信息保存到 stats 结构中。

与我的代码问题最相关的部分如下(我正在添加 cmets 而不是其他代码来解释什么可以完成):

int main(int argc, char *argv[])
{
    pList=(Process_list *)malloc((M+1)*sizeof(Process_list));
    /*Correct allocation is checked*/

    int i;
    pid_t pid;
    for (i=0 ; i<M ; i++)       /*M clones*/
    {
        /*Fork and pipe creation*/
        pid = fork ();
        pipe(pList[i].fd);

        /*If it's the child*/
        if ( pid == 0 ) 
        {
            pList[i].pid=pid; 
            printf("CHILD %d: %d a %d\n",i, pList[i].first,pList[i].last);

            /*A function here computes stats and saves them OK in the struct*/
            /*(e.g. min is saved in pList[i].stats.max, when printed it's a reasonable value)*/

            /*Now I need to send the info to the parent*/
            ret1=close(pList[i].fd[0]);
            /*ret1=0 => OK */

            ret2=write(pList[i].fd[1], &(pList[i].stats), sizeof(Stats));
            printf("return write: %d\n",ret2);
            /*TROUBLE HERE! This isn't even printed. sizeof(Stats)=24 which I think is OK*/

            exit(EXIT_SUCCESS);
        }

        /*Parent*/
        else if ( pid > 0 )
        {
            wait(NULL); /*Is this really neccesary?*/

            ret1=close(pList[i].fd[1]);
            ret2=read(pList[i].fd[0], &(pList[i].stats), sizeof(Stats));
            /*Both ret1 and ret2 = 0*/

            printf("[p]Mín: %lf\n Max: %lf\nAverage: %lf\n",pList[i].stats.min,pList[i].stats.max,pList[i].stats.avg);
            /*Everything printed here  = 0.0000000000*/


        }

        else /*fork error*/
                return -1;
    }

所以我的问题是孩子们完美地计算了他们的统计数据,但父母没有收到他们。 write() 函数什么也不做。 M 的每个值都会发生这种情况(包括 M=1 - 只有一个进程)。

我也不知道 wait(NULL) 是否是必要的,因为我看到了一些不使用它的工作示例。但是,如果我不在那里写,父母的 printfs 会出现在孩子之前,所以我认为它只是不等待孩子在管道中写。无论如何,没有它就行不通。

或者也许结构方法不是一个好方法?

非常感谢您!

【问题讨论】:

标签: c process struct pipe


【解决方案1】:

你必须在 fork 之前创建管道。否则,您将创建两个管道。一个在您的父母身上,一个在您的孩子身上,它们未连接。

在你获得所有数据后,wait 为孩子,否则如果孩子在write 上阻塞以清除管道缓冲区,你可能最终会永远等待。 wait 释放与死去的孩子相关的资源。如果您的孩子没有死,这将等到它死了。

另一件事:使用fwrite 将数据块(结构数组)写入文件描述符。 fread 供阅读。您可以使用fdopen 将文件描述符转换为FILE*

int main(int argc, char *argv[])
{
pList=(Process_list *)malloc((M+1)*sizeof(Process_list));
/*Correct allocation is checked*/

int i;
pid_t pid;
for (i=0 ; i<M ; i++)       /*M clones*/
{
    /*Fork and pipe creation*/
    if(pipe(pList[i].fd)) {
        perror("pipe");
        return -1;
    }
    pid = fork ();

    /*If it's the child*/
    if ( pid == 0 ) 
    {
        pList[i].pid=pid; 
        printf("CHILD %d: %d a %d\n",i, pList[i].first,pList[i].last);

        /*A function here computes stats and saves them OK in the struct*/
        /*(e.g. min is saved in pList[i].stats.max, when printed it's a reasonable value)*/

        /*this just closes your read end of the pipe*/
        close(pList[i].fd[0]);
        /*ret1=0 => OK */

        FILE* fp = fdopen(pList[i].fd[1], "w");
        while (!fwrite(&(pList[i].stats), sizeof(Stats), 1, fp) && !feof(fp));
        if (feof(fp)) {
            fclose(fp);
            fprintf(stderr, "reader closed his end of the pipe\n");
            return -1;
        }

        // sends eof to reader
        fclose(fp);

        printf("%d bytes written successfully",sizeof(Stats));
        /*TROUBLE HERE! This isn't even printed. sizeof(Stats)=24 which I think is OK*/

        exit(EXIT_SUCCESS);
    }

    /*Parent*/
    else if ( pid > 0 )
    {

        // this is actually nessesary to ensure that the pipe properly closes
        close(pList[i].fd[1]);
        FILE* fp = fdopen(pList[i].fd[0], "r");

        if (!fread(&(pList[i].stats), sizeof(Stats), 1, fp)) {
            fprintf(stderr, "writer sent eof, but not enough data\n");
            return -1;
        }

        fclose(fp);

        printf("[p]Mín: %lf\n Max: %lf\nAverage: %lf\n",pList[i].stats.min,pList[i].stats.max,pList[i].stats.avg);
        /*Everything printed here  = 0.0000000000*/

        wait(0);

    }

    else /*fork error*/
            return -1;
}

【讨论】:

  • 这真是太棒了。非常感谢您的回复。我从中学到了很多!如果您不介意,只是一个小问题,为什么在这种情况下fwritefreadwriteread 更好?如果进程需要写入多个数据块,它们似乎是必要的。但是,考虑到它只会写一个块,为什么它是一个更好的选择呢?是更一致还是更高效?
  • fwrite 将确保您的数据以一定大小的卡盘离开缓冲区(在这种情况下为sizeof(Stats))。不会写入结构的任何部分,只会写入整个结构。 write 将以一个字节的块写入数据,这意味着如果写入缓冲区已满,您最终可能只写入半个结构,然后读者关闭他的管道末端,您将永远不知道如何处理第二个一半(或者读者正在用半个结构做什么)。 fread 在读取端做同样的事情。
  • 另一件事是freadfwrite 是对缓冲流进行操作的库调用,从而通过用户空间中的额外缓冲区为您提供性能。 readwrite 是系统调用,它们将总是调用内核。做一个练习:编写一个程序,用read/write 一次复制一个文件,用fread/fwrite 复制一个文件,看看 20MB+ 文件的区别。
【解决方案2】:

在终止子的情况下,执行wait() 允许系统释放与子关联的资源;如果没有执行wait(),则终止的子进程保持在“僵尸”状态。这是一种安全的做法,而且总是很好用。参考:man -s2 wait

wait(NULL) 系统调用意味着,父进程将等待任何子进程(在您的情况下,它将等待刚刚分叉的子进程)并且不会存储您的子进程返回的状态。如果您想对等待进行更多控制,请查看:man -s2 waitpid。基本上,wait()waitpid() 的普通形式。

现在,管道部分。您正在子进程内创建一个管道。父进程如何访问它?实际上它是这样工作的:if a pipe is created in a parent process, the pipe fd's will be copied to the child process. 不是相反。 参考:man -s2 pipe

所以,在父进程中创建管道,然后发出fork(),然后在子进程中使用管道fd发送给父进程。子进程现在可以write() 将数据写入管道的写入 fd,父代码现在可以read() 子进程写入的内容。

您编写了从子级到父级的单向 IPC 通信。因此,您正在关闭子级中的读取 fd,并且您也正在关闭父级中的写入 fd。所以,这样你只能在孩子中写作,而只能在父母中阅读。逻辑正确执行。没有错误。

一旦解决了 pipe() 的逻辑问题(在父级中创建管道然后分叉),您的代码应该可以按预期工作。管道没有理由不适用于您编写的任何数据类型或结构。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-05-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多