2

I am learning process managing in Linux and I need to make child and parent communicate though a pipe. I have declared two structures:

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;

I need the M child processes to compute some stats out of an array of numbers (dividing the work). Then, the child process would read the Process_list structure, process the numbers in the array from first to last (specified there) and save the computed stats into the stats structure.

The most relevant parts to my trouble of the code are the following (I am adding comments instead of other pieces of code to explain what is done OK):

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;
    }

So my problem is that childs compute their stats perfectly, but the parent doesn't receive them. The write() function does nothing. This happens for every value of M (including M=1 -just one process).

Also I don't know if wait(NULL) is necessary, as I've seen some working examples that doesn't use it. However, if I don't write it there, the parent's printfs will appear before the child's, so I think it just doesn't wait for the child to write in the pipe. It doesn't work without it, anyway.

Or maybe the structure approach is just not a good one?

Thank you very much in advance!

Serge
  • 191
  • 2
  • 11
  • 2
    [Do not cast the return value of `malloc()`.](http://stackoverflow.com/questions/605845/do-i-cast-the-result-of-malloc) –  Oct 17 '12 at 17:51

2 Answers2

3

You have to create the pipe before you fork it. Otherwise you are creating two pipes. One on your parent and one on your child which are not connected.

And wait for the child after you got all the data, else you may end up waiting forever if the child is blocking on write for the pipe buffer to clear. wait frees up resources associated with a dead child. If your child is not dead this will wait until it is.

Another thing: use fwrite for writing blocks of data (arrays of structs) to a file descriptor. fread for reading. You can convert a file descriptor to a FILE* with fdopen.

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;
}
Sergey L.
  • 21,822
  • 5
  • 49
  • 75
  • This is incredibly awesome. Thank you very very much for your response. I have learned a lot from it! Just one little question if you don't mind, why are `fwrite` and `fread` better than `write` and `read` in this case? They seem necessary in case the process needs to write several blocks of data. But, considering that it will write just a single block, why is it a better option? Is it more consistent or more efficient? – Serge Oct 17 '12 at 18:39
  • `fwrite` will ensure that your data leaves the buffer in chucks of a certain size (`sizeof(Stats)` in this case). No part of a struct will be written, only whole ones. `write` will write data in chunks of one byte, means that if the write buffer is full you could end up writing only half a struct, then the reader closing his end of the pipe and you will never know what to do with the second half (or what the reader is doing with half a struct for that matter). `fread` does the same on the read end. – Sergey L. Oct 18 '12 at 16:01
  • Another thing is that `fread` and `fwrite` are library calls that operate on a buffered stream, thus giving you performance through an additional buffer in user space. `read` and `write` are system calls that will **always** invoke the kernel. Do an exercise: write a program that copies a file one byte at a time with `read`/`write` and one with `fread`/`fwrite` and see the difference on something with 20MB+. – Sergey L. Oct 18 '12 at 16:08
  • http://stackoverflow.com/questions/11414191/what-are-the-main-differences-between-fwrite-and-write – askmish Oct 18 '12 at 17:25
0

In the case of a terminated child, performing a wait() allows the system to release the resources associated with the child; if a wait() is not performed, then the terminated child remains in a "zombie" state. Its kind of a safe practice and always good to use it. Reference: man -s2 wait

The wait(NULL) system call implies that, the parent process will wait for any child process(and in your case it will wait for the child that been just forked) and will not store the status returned by your child process. If you want more control on the wait, check out: man -s2 waitpid. Basically, wait() is a vanilla form of waitpid().

Now, the pipe part. You are creating a pipe inside the child process. How will this be accessible to the parent process? In reality it works this way: if a pipe is created in a parent process, the pipe fd's will be copied to the child process. Not the other way around. Refer: man -s2 pipe

So, create the pipe in the parent process, then issue a fork() and then use the pipe fd in the child process to send to the parent. The child can now write() the data into the pipe's write fd and the parent code can now read() what the child process wrote.

You wrote a uni-directional IPC communication from child to parent. So, you're closing the read fd in the child and you are also closing the write fd in the parent. So, that way you only write in child and only read in parent. The logic is properly implemented.No fault there.

Once you fix the logical problem with the pipe()(creating the pipe in parent and then forking), your code should work as you intended. There's no reason why the pipe shouldn't work with any datatype or the structure you wrote.

askmish
  • 6,464
  • 23
  • 42