c - 在Linux中无法阻止从命名管道(FIFO)读取

标签 c linux pipe ipc named-pipes

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

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(管道中没有可用内容),则它应关闭文件描述符,然后再次打开它以继续尝试从管道中读取数据。这有点busy-waiting

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

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

但是,我得到的输出很奇怪。在按reader.c之前,屏幕上什么都没有(对于enter)。当我按Enter时,我得到以下信息:
Read: 1, first: 0, second: 0
Read: 1, first: 0, second: 0
Read: 1, first: 0, second: 0

当我按其他一些键然后按enter时,我得到了:
Read: 1, first: 0, second: 0
aa
Read: 3, first: 0, second: 0
aaa
Read: 4, first: 0, second: 0

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

最佳答案

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



好吧,不是真的。您会遇到奇怪的要求和易碎的断言。

The reader process has to be blocking.



不,你为什么要这样限制?考虑
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;
    }
}

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

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

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

如果写入器打开FIFO非阻塞式只写(O_WRONLY | O_NONBLOCKING),则如果没有读取器,则写入器将失败并返回errno = ENXIO;如果读取器正在运行但尚未准备就绪,则写入器将失败。有读者时,除非读者无法跟上,否则写入将成功。 (当读取器的缓冲区已满时,写入器将收到errno = EACCESerrno = EAGAIN的错误。)

编写者可以轻松地进行无阻塞写入,并具有可自定义的超时时间,直到可以写入为止。它与上面的errno = EWOULDBLOCK非常相似。

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.



也许在最佳条件下。

例如,如果恶意用户确实blocking_read()只是在编写者编写消息时,您失去了消息边界。读取器获取两个字节(echo 1 > your_pipe和换行符)和消息的初始部分,下一次读取获取该消息的最后两个字节和下一个消息的初始部分,只要有写者以如下方式写入套接字:比阅读器阅读速度快或快。

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

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.



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

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

这不仅仅是理论上的;在实践中很容易证明使用Bash和mknod。

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

就个人而言,我将使用绑定(bind)到路径名的Unix domain datagram socket,可能是errno = EWOULDBLOCK。这样可以保证消息的边界(两个不同的消息不会像管道或FIFO那样混合在一起)。读取者和写入者都可以使用ancillary data传递SCM_CREDENTIALS,这允许读取者验证写入者使用的用户ID和组ID。

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

(请注意,使用数据报协议(protocol),读取器无法验证发送消息的过程的详细信息,因为在读取器收到SCM_CREDENTIALS辅助消息时,原始发送者可能已经执行了另一个过程,或者在OS重用为了验证使用哪个可执行文件发送消息,需要一个面向连接的协议(protocol),例如Unix域流套接字,编写器发送两个或三个消息,所有消息都带有相同的SCM_CREDENTIALS辅助消息。 。正确地进行操作是非常棘手的,因此大多数程序员都认为这种验证很麻烦。

关于c - 在Linux中无法阻止从命名管道(FIFO)读取,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50958720/

相关文章:

c - 组装指令解读

c - 子进程返回的函数是否可以在父进程中捕获

linux - 如何阻止sed插入换行符?

c++ - 从 .exe 读取输入并写入 .exe 以读取国际象棋引擎命令

c++ - 生成 alpha 图片轮廓的算法?

c - 了解由于原型(prototype)不匹配而导致的意外结果 (C89)

linux - bash 无法识别两位数

c - 进程的 proc 条目

c - 通过超时或使用同一模块中的另一个函数写入其文件描述符来中断选择函数

Ubuntu 下的 C - 尝试解决 "Wheel game"时未出现预期结果