Search code examples
clinuxpipefifo

Why are my writes not blocking to this pipe?


I'm writing a little Linux fifo test program.

I created a pipe using mkfifo mpipe. The program should perform one write per argument sent to it. If no arguments are sent, then it performs one read from the pipe.

Here is my code

int main(int argc, char *argv[])
{
    if (argc > 1)
    {
       int fd = open("./mpipe", O_WRONLY);
       int i = 1;
       for (i; i < argc; i++)
       {
           int bytes = write(fd, argv[i], strlen(argv[i]) + 1);
           if (bytes <= 0)
           {
               printf("ERROR: write\n");
               close(fd);
               return 1;
           }
           printf("Wrote %d bytes: %s\n", bytes, argv[i]);
       }

       close(fd);
       return 0;
    }

    /* Else perform one read */
    char buf[64];
    int bytes = 0;

    int fd = open("./mpipe", O_RDONLY);
    bytes = read(fd, buf, 64);
    if (bytes <= 0)
    {
        printf("ERROR: read\n");
        close(fd);
        return 1;
    }
    else
    {
        printf("Read %d bytes: %s\n", bytes, buf);
    }

    close(fd);
    return 0;
}

I would expect the behavior to be something like this...

I call ./pt hello i am the deepest guy, and I would expect it to block for 6 reads. Instead one read seems enough to trigger multiple writes. My output looks like

# Term 1 - writer
$: ./pt hello i am the deepest guy # this call blocks until a read, but then cascades.
Just wrote hello
Just wrote i
Just wrote am
Just wrote the # output ends here

# Term 2 - reader
$: ./pt
Read 6 bytes: hello

Could someone help explain this weird behavior? I thought that every read would have to match with a write with regards to pipe communication.


Solution

  • What is happening there is that the kernel blocks the writer process in the open(2) system call until you have a reader opening it for reading. (A fifo requires both ends connected to processes to work) Once the reader does the first read(2) call (either the writer or the reader blocks, who gets first for the system call) The kernel passes all the data from the writer to the reader, and awakens both processes (that's the reason of receiving only the first command line parameter, and not the first 16 bytes from the writer, you get only the six characters {'h', 'e', 'l', 'l', 'o', '\0' } from the blocking writer)

    Finally, as the reader just closes the fifo, the writer gets killed with a SIGPIPE signal, as no more readers have the fifo open. If you install a signal handler on the writer process (or ignore the signal) you'll get an error from the write(2) syscall, telling you that no more readers were blocked on the fifo (EPIPE errno value) on the blocking write.

    Just notice that this is a feature, and not a bug, a means of knowing that writes will not reach any reader until you close and reopen the fifo.

    The kernel blocks the inode of the fifo for the whole read(2) or write(2) calls, so even another process doing another write(2) on the fifo will be blocked, and you'll not get the data on the reader from that second writer (should you have one). You can try if you like, to start two writers and see what happens.

    $ pru I am the best &
    [1] 271
    $ pru
    Read 2 bytes: I
    Wrote 2 bytes: I
    Wrote 3 bytes: am
    [1]+  Broken pipe             pru I am the best  <<< this is the kill to the writer process, announced by the shell.
    $ _