Linux pipe():从管道读取并不总是解除对写入器的阻塞

Posted

技术标签:

【中文标题】Linux pipe():从管道读取并不总是解除对写入器的阻塞【英文标题】:Linux pipe(): Reading from a pipe doesn't always unblock writers 【发布时间】: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”?

【问题讨论】:

【参考方案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;

【讨论】:

以上是关于Linux pipe():从管道读取并不总是解除对写入器的阻塞的主要内容,如果未能解决你的问题,请参考以下文章

5.管道 Pipe

Linux 进程间通信之管道(pipe),(fifo)

简述Linux进程间通信之管道pipe(下)

线程和分叉:从 popen()-pipe 读取时 fgetc() 阻塞

linux管道pipe详解

11NIO--管道Pipe