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():从管道读取并不总是解除对写入器的阻塞的主要内容,如果未能解决你的问题,请参考以下文章