首页 > 解决方案 > 命名管道块在其最大大小之前写入

问题描述

mkfifo /tmp/pipe我尝试通过一次写入 3 个字节来填充命名管道(由 创建),直到write()函数阻塞。

在我的系统上,管道似乎限制为 16 页 4096 字节。因此管道可以包含 65536 个字节。

我使用以下 C 代码执行此操作:

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

通过这种方式,我成功地将管道填充到 65520 字节。我不明白为什么 65520 而不是 65536(或者 65535,如果我们认为 65536 不是 3 的倍数)。

然后我尝试写入 65520 个字节,然后写入 3 个字节:

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

我预计第二次写入会阻塞,但事实并非如此,我写了 65523 个字节。

问题是:为什么我不能在第一种情况下写入超过 65520 字节,而在第二种情况下可以?

编辑:

更多信息 :


int
main (int argc, char *argv[])
{
  int fd = open ("/tmp/pipe", O_WRONLY);
  printf ("MAX : %d\n", fcntl (fd, F_GETPIPE_SZ));
  return 0;
}

标签: clinuxpipe

解决方案


这是因为 4KB 页面在 Linux 内核的管道实现中填充写入数据的方式。更具体地说,只有当数据完全适合页面时,内核才会将写入的数据附加到页面,否则将数据放入具有足够空闲字节的另一个页面中。

如果一次写入 3 个字节,管道页面将不会被填满,因为页面大小 (4096) 不是 3 的倍数:最接近的倍数是 4095,因此每个页面将以 1 结尾“浪费”字节。将 4095 乘以 16,即总页数,得到 65520。

在您的第二个用例中,当您一次写入 65520 个字节时,您将完全填充 15 个页面(61440 个字节),另外您将剩余的 4080 个字节放在最后一页中,其中仍有 16 个字节可用于后续写入:这就是为什么你的第二个 write() 调用 3 个字节成功而没有阻塞。

有关 Linux 管道实现的完整详细信息,请参阅https://elixir.bootlin.com/linux/latest/source/fs/pipe.c


推荐阅读