我似乎无法完成这项工作,这很奇怪。这是我的体系结构:我有一个命名管道,该管道将在始终运行 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字节),因为我将编写器设置为原子。 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
我不知道真正发生了什么以及如何处理。我想在编写者是非阻塞且原子的同时进行阻塞读取。我已经搜索了很多内容,然后编写了代码,但是很奇怪我无法使它正常工作。
答案 0 :(得分:3)
我似乎无法完成这项工作,这很奇怪。
嗯,不是真的。您有奇怪的要求和易碎的断言。
阅读器进程必须被阻止。
不,...您为什么要进行这样的限制?考虑
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秒之间的值,大约一天。再多不过是愚蠢的。记住这是超时,而不是普通的睡眠:任何信号传递或传入的数据都会立即将其唤醒。)
这样,您可以首先以0400模式(r--------
)创建FIFO,在阅读器中将其打开O_RDONLY | O_NONBLOCK
,然后使用例如fchmod(fifofd, 0222)
更改其模式(0222 = -w--w--w-
)以允许作者。这些都没有阻止。在读取器准备就绪之前,任何写入器尝试打开FIFO的尝试都不会成功。
读取器不会打开和关闭FIFO;它只是不断调用blocking_read()
。
如果写入者打开FIFO非阻塞式只写(O_WRONLY | O_NONBLOCKING
),则如果没有读取器,它们将以errno = ENXIO
失败,如果读取器正在运行但尚未就绪,则它们将以errno = EACCES
失败。然而。有读者时,除非读者无法跟上,否则写入将成功。 (当读取器的缓冲区已满时,写入器将显示errno = EAGAIN
或errno = EWOULDBLOCK
的错误。)
编写者可以轻松地进行无阻塞写入,并具有可自定义的超时时间,以等待写入成为可能;它与上面的blocking_read()
非常相似。
我希望bytes_read恰好是sizeof(Message_Struct_t)(24字节),因为我将我的writer设置为atomic。 24个字节小于PIPE_BUF,因此Linux确保只要我不超过管道的大小限制,它就是原子的。
也许在最佳条件下。
例如,如果恶意用户确实echo 1 > your_pipe
只是在编写者编写消息时,您失去了消息边界。读取者获取两个字节(1
和换行符)和消息的初始部分,下一次读取获取该消息的最后两个字节和下一则消息的初始部分,只要有写者在写以比阅读器更快或更快的速度插入插座。
由于管道和FIFO永远不会保留消息边界,因此您的方法非常脆弱。更好的方法是使用确实保留消息边界的数据报套接字。
我绝不会超出大小限制。我的作家程序就像客户。他们来,执行并终止。因此,管道的书写面并不总是打开的。
您可以轻松超过大小限制。
只有一个管道缓冲区,如果写入器的数量超过读取器的数量,则缓冲区可能已满(因此非阻塞写入失败)。容易发生这种情况:例如,如果读取器对数据进行了任何操作,则使用两个并发写入器(例如Bash for ((;;)) ; do printf 'message' > fifo ; done
)将填充缓冲区,并导致所有非阻塞写入器以errno = EAGAIN
或errno = EWOULDBLOCK
失败。
这不仅仅是理论上的;在实践中很容易证明使用Bash和mknod。
我感觉OP正在构建灾难,以等待他们当前的需求混合,尤其是使用管道(或FIFO)进行数据报传输。
我个人会使用绑定到路径名(可能是/var/run/yourservice
)的Unix domain datagram socket。这样可以保证消息边界(两个不同的消息不会像管道或FIFO那样混合在一起)。读取者和写入者都可以使用ancillary data来传递SCM_CREDENTIALS,这使读取者可以验证写入者使用的用户ID和组ID。
(作者可以在真实身份或有效身份之间进行选择。内核始终验证SCM_CREDENTIALS辅助消息字段,并且不允许发送不正确的数据。换句话说,SCM_CREDENTIALS辅助消息字段将始终是正确的在消息发送的那一刻。)
(请注意,使用数据报协议,阅读器无法验证发送消息的过程的详细信息,因为在阅读器收到SCM_CREDENTIALS辅助消息时,原始发送者可能已经执行了另一个过程,或者退出了该过程。操作系统将进程ID重用于其他新进程,要验证使用哪个可执行文件来发送消息,需要一个面向连接的协议,例如Unix域流套接字,编写器发送两个或三个消息,并且所有消息都相同SCM_CREDENTIALS辅助消息。要正确执行此操作非常棘手,因此大多数程序员都认为这种验证很麻烦。)