【问题标题】:Linux pipe(): Reading from a pipe doesn't always unblock writersLinux pipe():从管道读取并不总是解除对写入器的阻塞
【发布时间】:2015-03-24 13:01:01
【问题描述】:

我在 Linux 下使用管道时遇到问题。我想填充管道以进一步阻止 write 的调用。其他进程应该能够从管道中读取一些应该允许其他进程写入的字符。

示例代码:

#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>

int main()
{
int pipefd[2];
int size = 65535;
int total = 0;

// Create the pipe
if(pipe(pipefd) == -1)
{
   perror("pipe()");
   exit(EXIT_FAILURE);
}

// Fill in (almost full = 65535 (full - 1 byte))
while(total < size)
{
   write(pipefd[1], &total, 1);
   total++;
}

// Fork
switch(fork())
{

case -1:
        perror("fork()");
        exit(EXIT_FAILURE);
case 0:
    // Close unused read side
        close(pipefd[0]);
        while(1)
        {
       // Write only one byte, value not important (here -> total)
           int ret = write(pipefd[1], &total, 1);
       printf("Write %d bytes\n", ret);
        }
default:
    // Close unused write side
        close(pipefd[1]);
        while(1)
        {
       int nbread;
           scanf("%4i", &nbread);
           char buf[65535];
       // Read number byte asked
           int ret = read(pipefd[0], buf, nbread);
           printf("Read %d bytes\n", nbread);
        }
}

return 0;
}

我不明白下面的行为。这个过程最后写了一个,因为我没有完全填满管道,正常。但之后,写入被阻塞(管道已满),任何读取都应解除阻塞等待的写入调用。

test@pc:~$./pipe
Write 1 bytes
4095
Read 4095 bytes
1
Read 1 bytes
Write 1 bytes
Write 1 bytes
Write 1 bytes
Write 1 bytes
Write 1 bytes
Write 1 bytes
...

相反,写入调用只有在读取 4096 字节后才会解除阻塞...为什么????

通常,在read 成功 X 字节后,管道中应该有 X 字节可用空间,因此 write 应该最多可以写入 X 字节,不是吗?

如何让行为“读取 1 个字节,写入 1 个字节等”而不是“读取 1 个字节,读取 1,读取 10,读取 2000,...(直到读取 4096 个字节),写入 4096”?

【问题讨论】:

    标签: c linux pipe


    【解决方案1】:

    为什么它不像你想的那样工作

    所以基本上我的理解是您的管道与某种内核缓冲区链接列表相关联。只有当这些缓冲区之一被清空时,等待写入管道的进程才会被唤醒。碰巧在您的情况下,这些缓冲区的大小为 4K。

    见:http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/fs/pipe.c?id=HEAD

    具体行:281 完成缓冲区大小测试,行:287 决定唤醒其他进程。

    管道缓冲区的大小确实取决于内存页大小,见man fcntl

    F_SETPIPE_SZ(整数;从 Linux 2.6.35 开始)

    将 fd 引用的管道的容量更改为至少 arg 字节。 非特权进程可以将管道容量调整为之间的任何值 系统页面大小和 /proc/sys/fs/pipe-max-size 中定义的限制 (参见过程(5))。尝试将管道容量设置为低于页面大小是 静默四舍五入到页面大小。非特权进程尝试 将管道容量设置为高于 /proc/sys/fs/pipe-max-size yield 的限制 错误 EPERM;特权进程 (CAP_SYS_RESOURCE) 可以覆盖 限制。在为管道分配缓冲区时,内核可能会使用 容量大于 arg,如果方便实现的话。这 F_GETPIPE_SZ 操作返回实际使用的大小。试图设置 管道容量小于当前使用的缓冲空间量 存储数据会产生错误 EBUSY。

    如何让它发挥作用

    您尝试实现的模式是经典的。但它被用于周围的方式。人们从空管开始。等待事件的进程,read 空管道。想要发出事件信号的进程,将单个字节写入管道。

    我想我在 Boost.Asio 中看到过,但我懒得找到正确的参考。

    【讨论】:

      【解决方案2】:

      Pipe 使用 4kB 页面作为缓冲区,写入被阻塞,直到有一个空页面可供写入,然后在它再次满之前不阻塞。在fjardon'sanswer 中有很好的描述。如果您想使用管道发出信号,您正在寻找相反的场景。

      #include <stdio.h>
      #include <errno.h>
      #include <unistd.h>
      #include <stdlib.h>
      
      int main()
      {
          int pipefd[2];
      
          // Create the pipe
          if(pipe(pipefd) == -1)
          {
              perror("pipe()");
              exit(EXIT_FAILURE);
          }
      
          // Fork
          switch(fork())
          {
      
              case -1:
                  perror("fork()");
                  exit(EXIT_FAILURE);
              case 0:
                  // Close unused write side
                  close(pipefd[1]);
                  while(1)
                  {
                      char c;
                      // Read only one byte
                      int ret = read(pipefd[0], &c, 1);
                      printf("Woke up\n", ret);
                      fflush(stdout);
                  }
              default:
                  // Close unused read side
                  close(pipefd[0]);
                  size_t len = 0;;
                  char *str = NULL;
                  while(1)
                  {
                      int nbread;
                      char buf[65535];
                      while (getline(&str, &len, stdin)) {
                          if (sscanf(str, "%i", &nbread)) break;
                      };
                      // Write number byte asked
                      int ret = write(pipefd[1], buf, nbread);
                      printf("Written %d bytes\n", ret);
                      fflush(stdout);
                  }
          }
      
          return 0;
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2017-01-04
        • 1970-01-01
        • 1970-01-01
        • 2015-01-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多