Linux pipe():从管道读取并不总是解锁编写器

时间:2015-03-24 13:01:02

标签: c linux pipe

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

示例代码:

#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个字节后,才会解锁写入调用...为什么????

通常,在X字节成功read后,管道中应该有X个字节的空间,因此write应该能够写入多达X个字节,不是吗?

我怎么能有“读取1个字节,写1个字节等”的行为,而不是“读取1个字节,读取1,读取10,读取2000,......(直到4096个字节读取),写入4096”?

2 个答案:

答案 0 :(得分:3)

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

基本上我理解的是你的管道与某种内核缓冲区链表相关联。只有在清空其中一个缓冲区时,才会唤醒等待写入管道的进程。碰巧在你的情况下,这些缓冲区大小为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(int;自Linux 2.6.35起)

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

如何使其工作

您尝试实现的模式是经典。但它是用来解决的。人们从空管开始。处理等待事件,read空管道。想要发出事件信号的进程,将一个字节写入管道。

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

答案 1 :(得分:0)

管道使用4kB页面作为缓冲区并且写入被阻止,直到有一个空页面进行写入,然后在它再次满满之前不会阻塞。 fjardon's answer对此进行了详细介绍。如果您想使用管道进行信号传输,那么您正在寻找相反的情况。

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