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



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

阅读器.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 errorn");
}
}

主要功能:

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 errorn");
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 agentn");
exit(-1);
}
printf("Read: %d, first: %d, second: %dn", bytes_read, msg.first, msg.second);
fflush(stdout);
}
close(Process_Pipe_ID);
}
}

我希望这个读取器不会在open上被阻塞,但如果管道上有东西,它应该继续从命名管道读取。然后,如果它接收到0,这意味着EOF(管道中没有可用的内容),那么它应该关闭文件描述符并再次打开它,以继续尝试从管道读取。等待有点忙。

我希望bytes_read恰好是sizeof(Message_Struct_t)(24字节),因为我将编写器设置为原子。24字节小于PIPE_BUF,因此Linux确保它是原子的,只要我不超过管道的大小限制。我决不会超过尺寸限制。我的作家programs就像客户;他们来了,执行和终止。因此,管道的写入端并不总是open。这是我非常简单的作者:

写入程序.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 writingn");
exit(-1);
}  else
printf("%d bytes wrote to pipe.n", num);
close(Process_Pipe_ID);
}

然而,我得到了非常奇怪的输出。在按下enter之前,我不会在屏幕上看到任何内容(对于reader.c)。当我按下回车键时,我得到以下信息:

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

我不知道到底发生了什么,也不知道该怎么做。我想在编写器是非阻塞和原子的时候进行阻塞读取。我搜索了很多,然后写了代码,但很奇怪,我不能让它工作。

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

嗯,不是真的。你有奇怪的需求和脆弱的断言,你试图通过。

读取器进程必须被阻塞。

不。。。你为什么要做出这样的限制?考虑

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模式(r--------)下创建FIFO,在读卡器中打开它O_RDONLY | O_NONBLOCK,然后使用例如fchmod(fifofd, 0222)更改其模式(0222=-w--w--w-)以允许写入程序。这些都没有阻止。在读卡器准备就绪之前,写入程序打开FIFO的任何尝试都不会成功。

读卡器未打开和关闭FIFO;它只是不断地呼叫CCD_ 20。

如果写入程序仅打开FIFO非阻塞写入(O_WRONLY | O_NONBLOCKING),则如果没有读卡器,则它们将以errno = ENXIO失败;如果读卡器正在运行但尚未就绪,则以errno = EACCES失败。如果有读者,除非读者跟不上,否则写作就会成功。(当读卡器的缓冲区已满时,写入程序将收到errno = EAGAINerrno = EWOULDBLOCK错误。)

写入程序可以很容易地进行非阻塞写入,并具有可自定义的超时等待,直到写入成为可能;它是一个与上面的CCD_ 26非常相似的函数。

我希望bytes_read的大小正好是(Message_Struct_t)(24字节),因为我将编写器设置为原子。24字节小于PIPE_BUF,因此Linux确保它是原子的,只要我不超过管道的大小限制。

也许在最佳条件下。

例如,如果一个邪恶的用户在编写消息时执行了例如echo 1 > your_pipe,那么您就失去了消息边界。读卡器获取两个字节(1和换行符)和消息的初始部分,下一次读取获取该消息的最后两个字节和下一条消息的起始部分,只要有写入程序以读卡器读取的速度或速度写入套接字即可。

因为管道和FIFO从不保留消息边界,所以您的方法非常脆弱。一个更好的方法是使用一个保留消息边界的数据报套接字。

我决不会超过大小限制。我的写作程序就像客户;他们来了,执行和终止。因此,管道的书写端并不总是打开的。

您很容易超过大小限制。

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

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


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

就我个人而言,我会使用一个Unix域数据报套接字绑定到一个路径名,可能是/var/run/yourservice。这将保证消息边界(两个不同的消息不会像管道或FIFO那样混合)。读取器和写入器都可以使用辅助数据来传递SCM_CREDENTIALS,这允许读取器验证写入器使用的用户ID和组ID。

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

(注意,使用数据报协议,读取器无法验证发送消息的进程的详细信息,因为当读取器接收到SCM_CREDENTIALS辅助消息时,原始发送器可能已经执行了另一个进程,或者退出时操作系统将进程ID重新用于其他新进程。要验证哪个可执行文件用于发送消息,需要连接面向区段的协议,如Unix域流套接字,编写器发送两到三条消息,所有消息都带有相同的SCM_CREDENTIALS辅助消息。这很难正确进行,所以大多数程序员认为这样的验证很恶心。)

最新更新