Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

named pipe blocks on writing before its maximum size

Tags:

c

linux

pipe

I try to fill a named pipe (created by mkfifo /tmp/pipe) by writing to it 3 bytes at a time until the write() function blocks.

On my system, a pipe seems to be limited to 16 pages of 4096 bytes. Thus the pipe can contain 65536 bytes.

I do that with the following C code:

int main ()
{
  pid_t child;
  child = fork ();
  if (child == 0)
    {
      ssize_t ret;
      ssize_t total = 0;
      unsigned char *datat = malloc (65536);
      assert (datat != NULL);

      int fd = open ("/tmp/pipe", O_WRONLY);
      assert (fd != -1);

      while (1)
      {
        printf ("Trying writting\n");
        ret = write (fd, datat, 3);
        assert (ret != -1);
        total += ret;
        printf ("write : %ld.\n", total);
      }
    }
  else
    {
      int fd = open ("/tmp/pipe", O_RDONLY);
      assert (fd != -1);
      while (1);            //prevent closing the pipe.
    }
  return 0;
}

By this way, I succeed to fill the pipe until 65520 bytes. I don't understand why 65520 and not 65536 (or 65535 if we consider that 65536 is not a multiple of 3).

Then I tried to write 65520 bytes and, after, write 3 bytes:

int
main (int argc, char *argv[])
{
  pid_t child;
  child = fork ();
  if (child == 0)
    {
      ssize_t ret;
      ssize_t total = 0;
      unsigned char *datat = malloc (65536);
      assert (datat != NULL);

      int fd = open ("/tmp/pipe", O_WRONLY);
      assert (fd != -1);

      while(1)
      {
        printf ("Trying writting\n");
        ret = write (fd, datat, 65520);
        assert (ret != -1);
        total += ret;

        printf ("Trying writting\n");
        ret = write (fd, datat, 3);
        assert (ret != -1);
        total += ret;
        printf ("write : %ld.\n", total);
      }

    }
  else
    {
      int fd = open ("/tmp/pipe", O_RDONLY);
      assert (fd != -1);
      while (1);            //prevent closing the pipe.
    }
  return 0;
}

I expected the second write to block, however it was not the case and I wrote 65523 bytes.

The question is: why can't I write more than 65520 bytes on the first case whereas I can in the second?

EDIT:

More information :

  • My Operating system is Linux archlinux 4.16.5-1-ARCH

  • man 7 pipe give information about the size (which is equal to 65536 bytes) of the pipe and is confirmed by fcntl:


int
main (int argc, char *argv[])
{
  int fd = open ("/tmp/pipe", O_WRONLY);
  printf ("MAX : %d\n", fcntl (fd, F_GETPIPE_SZ));
  return 0;
}
like image 219
sebastien dontneedtoknowthat Avatar asked May 01 '18 13:05

sebastien dontneedtoknowthat


1 Answers

It's because of the way 4KB pages are filled with written data in the pipe implementation in the Linux kernel. More specifically, the kernel appends written data to a page only if the data fits entirely in the page, otherwise puts the data into another page with enough free bytes.

If you write 3 bytes at a time, the pipe pages won't be filled at their full capacity, because the page size (4096) is not a multiple of 3: the nearest multiple is 4095, so each page will end up with 1 "wasted" byte. Multiplying 4095 by 16, which is the total number of pages, you get 65520.

In your second use case, when you write 65520 bytes all at once, you are filling 15 pages entirely (61440 bytes), plus you are putting the remaining 4080 bytes in the last page, which will have 16 bytes still available for subsequent writes: that's why your second write() call with 3 bytes succeeds without blocking.

For full details on the Linux pipe implementation, see https://elixir.bootlin.com/linux/latest/source/fs/pipe.c.

like image 186
Francesco Lavra Avatar answered Oct 20 '22 01:10

Francesco Lavra