在最大尺寸之前写入命名管道块

seb*_*hat 8 c linux pipe

我尝试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;
}
Run Code Online (Sandbox Code Playgroud)

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

然后我尝试写入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;
}
Run Code Online (Sandbox Code Playgroud)

我期望第二次写入阻止,但事实并非如此,我写了65523字节.

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

编辑:

更多信息 :

  • 我的操作系统是Linux archlinux 4.16.5-1-ARCH

  • man 7 pipe 提供有关管道大小(等于65536字节)的信息,并由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;
}
Run Code Online (Sandbox Code Playgroud)

Fra*_*vra 6

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

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

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

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