在Linux中无法阻止从命名管道(FIFO)读取 [英] Can't do blocking read from named pipe (FIFO) in Linux

查看:145
本文介绍了在Linux中无法阻止从命名管道(FIFO)读取的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我似乎无法完成这项工作,这很奇怪.这是我的体系结构:我有一个命名管道,该管道将在始终运行 root读取器进程与多个应用程序写入器进程之间进行通信.读取器进程必须为blocking,而写入器为nonblocking.因此,这就是我将在具有root特权的读取器进程中执行的操作.

It is very weird that I can't seem to make this work. This is my architecture: I have a named pipe which will communicate between a always-running root reader process and multiple application writer processes. The reader process has to be blocking while the writers are nonblocking. Therefore this is what I do in the reader process which will run with root privilege.

reader.c

#define PIPE_ID "/dev/shm/mypipe"
// This function configures named pipe
void configure_pipe() {
  // So that others can write
  umask(0000);
  if(mkfifo(PIPE_ID, S_IWUSR | S_IRUSR | S_IRGRP | S_IROTH | S_IWGRP
        | S_IWOTH) != 0) {
    perror("mkfifo error\n");
  }
}

在主要功能中:

int main (int argc, char **argv)
{
  int Process_Pipe_ID;
  configure_pipe();

  // main loop which never ends
  while(1) {
    if((Process_Pipe_ID = open(PIPE_ID, O_RDONLY | O_NONBLOCK)) < 0) {
      perror("pipe file open error\n");
      exit(-1);
    }
    int bytes_read = 0;
    Message_Struct_t msg;

    // loop to keep reading from the pipe if there are messages to be read
    while((bytes_read = read(Process_Pipe_ID, &msg, sizeof(Message_Struct_t))) != 0) {
      if(bytes_read < 0) {
        perror("Pipe reading error in Scheduling agent\n");
        exit(-1);
      }
      printf("Read: %d, first: %d, second: %d\n", bytes_read, msg.first, msg.second);
      fflush(stdout);
    }
    close(Process_Pipe_ID);
  }
}

我希望该阅读器不会在open上被阻塞,但是如果管道上有东西,它应该继续从命名管道中读取.然后,如果收到0表示EOF(管道中没有可用内容),则应关闭文件描述符,然后再次打开它以继续尝试从管道中读取数据.有点忙碌等待.

I expect this reader to be not blocked on open but it should keep reading from the named pipe if there's something on the pipe. Then, if it receives 0 which means EOF (nothing available in pipe) then it should close the file descriptor and open it again to keep trying reads from the pipe. It is kinda busy-waiting.

我希望bytes_read恰好是sizeof(Message_Struct_t)(24字节),因为我将我的编写器设置为atomic. 24个字节小于PIPE_BUF,因此 Linux保证只要我不超过管道的大小限制,它就是原子的.我绝不会超出大小限制.我的作家programs就像客户一样;他们来,执行并终止.因此,管道的书写端并不总是open.这是我非常简单的作家:

I expect the bytes_read to be exactly the sizeof(Message_Struct_t) (24 bytes) because I setup my writer as atomic. 24 bytes is less than PIPE_BUF, therefore Linux assures that it is atomic as long as I don't exceed the size-limit of a pipe. I am no way exceeding the size-limit. My writer programs are like clients; they come, execute and terminate. So the writing side of the pipe is not always open. This is my very simple writer:

writer.c

void writeInts(int first, int second) {
  Process_Pipe_ID = open(PIPE_ID, O_WRONLY | O_NONBLOCK);
  Message_Struct_t msg;
  msg.first = first;
  msg.second = second;
  int num;
  if((num = write(Process_Pipe_ID, &msg, sizeof(msg))) < 0) {
    perror("Error in writing\n");
    exit(-1);
  }  else
    printf("%d bytes wrote to pipe.\n", num);
  close(Process_Pipe_ID);
}

但是,我得到的输出很奇怪.在按enter之前,屏幕上没有任何内容(用于reader.c).当我按Enter时,我得到以下信息:

However, I am getting very weird output. I don't get anything on screen (for the reader.c) until I press enter. When I press enter I get the following:

Read: 1, first: 0, second: 0
Read: 1, first: 0, second: 0
Read: 1, first: 0, second: 0

当我按其他一些键然后按enter时,会得到以下提示:

When I press some other key and then enter, I get this:

Read: 1, first: 0, second: 0
aa
Read: 3, first: 0, second: 0
aaa
Read: 4, first: 0, second: 0

我不知道真正发生了什么以及如何处理.我想在编写者是非阻塞且原子的同时进行阻塞读取.我已经搜索了很多内容,然后编写了代码,但是很奇怪,我无法使它正常工作.

I have no idea what really is happening and how to go about this. I want to have a blocking read while the writers are non-blocking and atomic. I have searched a lot and then written the code but it is very weird that I can't get it to work.

推荐答案

我似乎无法完成这项工作,这很奇怪.

It is very weird that I can't seem to make this work.

嗯,不是真的.您会遇到奇怪的要求和脆弱的主张.

Well, not really. You have strange requirements and fragile assertions you try to go by.

阅读器进程必须被阻止.

The reader process has to be blocking.

Noooo ...为什么要设置这样的限制?考虑

Noooo... Why would you make such a limitation? Consider

ssize_t blocking_read(fd, void *buf, size_t len)
{
    struct timeval  timeout;
    fd_set          fds;
    int             r;
    ssize_t         n;

    /* First, do a speculative read, just in case
       there is data already available. */
    n = read(fd, buf, len);
    if (n >= 0)
        return n;
    else
    if (n != -1) {
        /* Paranoid check, will never happen .*/
        errno = EIO;
        return -1;
    } else
    if (errno != EAGAIN && errno != EWOULDBLOCK)
        return -1;

    /* Wait for data to become available. */
    FD_ZERO(&fds);
    while (1) {
        FD_SET(fd, &fds);
        timeout.tv_sec = 60; /* One minute */
        timeout.tv_usec = 0; /* and no millionths of seconds */

        r = select(fd + 1, &fds, NULL, NULL, NULL, &timeout);
        if (r < 0)
            return -1; /* errno set by select() */
        else
        if (!r)
            continue;  /* Timeout */

        n = read(fd, buf, len);
        if (n >= 0)
            return n;
        else
        if (n != -1) {
            /* Paranoid check, will never happen .*/
            errno = EIO;
            return -1;
        } else
        if (errno != EAGAIN && errno != EWOULDBLOCK)
            return -1;
    }
}

它就像对阻塞和非阻塞描述符的阻塞读取一样.如果什么也没发生,它确实每分钟醒来一次,但是您可以对其进行调整,直到它足够长而无所谓. (我会考虑一秒到86400秒之间的值,大约一天.再多不过是愚蠢的.记住这是超时,而不是普通的睡眠:任何信号传递或传入的数据都会立即将其唤醒.)

It acts like a blocking read on both blocking and nonblocking descriptors. It does wake up once per minute if nothing happens, but you can tune that until it is long enough to not matter. (I would consider values between one second and 86400 seconds, about one day. Any longer is just silliness. Remember it is a timeout, and not an ordinary sleep: any signal delivery or incoming data will immediately wake it up.)

这样,您可以首先在0400模式下创建FIFO(r--------),在阅读器中将其打开O_RDONLY | O_NONBLOCK,然后使用例如fchmod(fifofd, 0222)更改其模式(0222 = -w--w--w-)以允许编写器.这些都没有阻止.在读取器准备就绪之前,任何写入器尝试打开FIFO的尝试都不会成功.

With this, you can initially create the FIFO in 0400 mode (r--------), open it O_RDONLY | O_NONBLOCK in the reader, then use e.g. fchmod(fifofd, 0222) to change its mode (0222 = -w--w--w-) to allow writers. None of this blocks. None of the writer attempts to open the FIFO will succeed until the reader is ready.

读取器不打开和关闭FIFO;它只是不断调用blocking_read().

The reader does not open and close the FIFO; it just keeps calling blocking_read().

如果写入器打开FIFO非阻塞只写(O_WRONLY | O_NONBLOCKING),则如果没有读取器,则它们将失败,并显示errno = ENXIO;如果读取器正在运行但尚未就绪,则它们将失败,并显示errno = EACCES.当有读者时,除非读者无法跟上,否则写入将成功. (当读取器的缓冲区已满时,写入器将出现errno = EAGAINerrno = EWOULDBLOCK错误.)

If writers open the FIFO nonblocking write-only (O_WRONLY | O_NONBLOCKING), they will fail with errno = ENXIO if there is no reader, or with errno = EACCES if the reader is running but not ready yet. When there is a reader, the writes will succeed unless the reader cannot keep up. (When the reader has its buffer full, writers will get an error with errno = EAGAIN or errno = EWOULDBLOCK.)

编写者可以轻松地进行无阻塞写入,并具有可自定义的超时时间,以等待写入成为可能;它与上面的blocking_read()非常相似.

Writers can easily do a nonblocking write, with a customizable timeout to wait until writing becomes possible; it is a very similar function to blocking_read() above.

我希望bytes_read恰好是sizeof(Message_Struct_t)(24字节),因为我将我的writer设置为atomic. 24个字节小于PIPE_BUF,因此Linux确保只要我不超过管道的大小限制,它就是原子的.

I expect the bytes_read to be exactly the sizeof(Message_Struct_t) (24 bytes) because I setup my writer as atomic. 24 bytes is less than PIPE_BUF, therefore Linux assures that it is atomic as long as I don't exceed the size-limit of a pipe.

也许在最佳条件下.

例如,如果恶意用户确实echo 1 > your_pipe仅当编写者正在编写消息时,您才失去了消息边界.读取者获取两个字节(1和换行符)和消息的初始部分,下一次读取获取该消息的最后两个字节和下一个消息的初始部分,只要有写者写入该消息中即可.套接字的速度比读取器读取的速度快或快.

For example, if a nefarious user does e.g. echo 1 > your_pipe just when a writer is writing a message, you lose the message boundaries. The reader gets the two bytes (1 and newline) and the initial part of the message, the next read gets the last two bytes of that message and the initial part of the next message, as long as there are writers writing into the socket as fast or faster than the reader can read.

由于管道和FIFO永远不会保留消息边界,因此您的方法非常脆弱.更好的方法是使用确实保留消息边界的数据报套接字.

Because pipes and FIFOs never preserve message boundaries, your approach is extremely fragile. A better approach would be to use a datagram socket that does preserve message boundaries.

我绝不会超出大小限制.我的作家程序就像客户.他们来,执行并终止.因此,管道的书写面并不总是打开的.

I am no way exceeding the size-limit. My writer programs are like clients; they come, execute and terminate. So the writing side of the pipe is not always open.

您可以轻松超过大小限制.

You could easily exceed the size limit.

只有一个管道缓冲区,如果写入器的数量超出读取器的数量,则缓冲区可能已满(因此非阻塞写入将失败).很容易导致这种情况发生:例如,如果读取器对数据进行了任何操作,则使用两个并发写入器(如Bash for ((;;)) ; do printf 'message' > fifo ; done) 将填充缓冲区,并导致所有非阻塞写入器失败,并显示errno = EAGAINerrno = EWOULDBLOCK.

There is just the one pipe buffer, and it can become full (and therefore nonblocking writes fail), if there are more writers than the reader can keep up. It is easy to cause that to happen: for example, if the reader does anything with the data, using two concurrent writers (like a Bash for ((;;)) ; do printf 'message' > fifo ; done) will fill the buffer, and cause any nonblocking writers to fail with errno = EAGAIN or errno = EWOULDBLOCK.

这不仅是理论上的,而且是理论上的.在实践中很容易证明使用Bash和mknod.

This is not just theoretical; it is easy to prove using Bash and mknod in practice.

我感觉OP正在构建灾难,以等待他们当前的需求混合,尤其是使用管道(或FIFO)进行数据报传输.

I have a feeling OP is constructing a disaster waiting to happen with their current mix of requirements, especially using a pipe (or FIFO) for datagram transfer.

我个人会使用 Unix域数据报套接字绑定到路径名,可能是/var/run/yourservice.这样可以保证消息的边界(两个不同的消息不会像管道或FIFO那样混合在一起).读者和作家都可以使用辅助数据来传递 SCM_CREDENTIALS ,它使读者可以验证用户ID和编写者使用的组ID.

Personally, I would use an Unix domain datagram socket bound to a pathname, probably /var/run/yourservice. This would guarantee message boundaries (two different messages will not be mixed, like they can with pipes or FIFOs). Both the reader and writers could use ancillary data to pass an SCM_CREDENTIALS, which allows the reader to verify the user ID and group ID the writer used.

(作者可以在真实身份或有效身份之间进行选择.内核始终验证SCM_CREDENTIALS辅助消息字段,并且不允许发送不正确的数据.换句话说,SCM_CREDENTIALS辅助消息字段将始终是正确的在消息发送的那一刻.)

(The writer can choose between real or effective identities. The kernel always verifies the SCM_CREDENTIALS ancillary message fields, and will not allow incorrect data to be sent. In other words, the SCM_CREDENTIALS ancillary message fields will always be correct at the moment the message was sent.)

(请注意,使用数据报协议,阅读器无法验证发送消息的过程的详细信息,因为在阅读器收到SCM_CREDENTIALS辅助消息时,原始发送者可能已经执行了另一个过程,或者退出了该过程.操作系统将进程ID重用于其他新进程,要验证使用哪个可执行文件来发送消息,需要一个面向连接的协议,例如Unix域流套接字,编写器发送两个或三个消息,并且所有消息都相同SCM_CREDENTIALS辅助消息.正确执行此操作非常棘手,因此大多数程序员认为这样的验证很麻烦.)

(Note that using a datagram protocol, the reader cannot verify the details of the process that sent the message, because by the time the reader receives the SCM_CREDENTIALS ancillary message, the original sender could have executed another process, or exited with the OS reusing the process ID for some other new process. To verify which executable was used to send a message, one would need a connection-oriented protocol, like Unix domain stream socket, with the writer sending two or three messages, all with the same SCM_CREDENTIALS ancillary message. This is quite tricky to do correctly, so most programmers consider such verification icky.)

这篇关于在Linux中无法阻止从命名管道(FIFO)读取的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆