首先,当一个文件描述符在分叉后共享时,父子节点都指向相同的打开文件描述,这意味着它们共享相同的文件位置。这在fork() 手册页中有说明。
因此,每当父级写入时,位置都会更新到文件末尾,因此子级始终尝试在没有数据的文件末尾读取。这就是为什么read() 返回 0 的原因,就像您点击文件末尾时一样。
(发生这种情况时,您不应尝试对缓冲区中的数据做任何事情。这并不是说您在“阅读垃圾”,而是您没有阅读 任何内容,而是然后假装缓冲区中的任何垃圾都是您刚刚阅读的内容。特别是您的代码完全无视来自read()的返回值,这就是您应该告诉您实际阅读的内容的方式。)
如果你想让孩子有一个独立的文件位置,那么孩子需要为自己单独open()该文件并获得一个指向新文件描述的新fd。
但是,当孩子读取了当前文件中的所有数据后,read() 将再次返回 0;它不会等待父母再写一些。其他进程有一个文件打开以供写入这一事实不会影响常规文件上read() 的语义。
所以你需要做的是,当read() 返回 0 时,你手动休眠一段时间,然后重试。当文件中有更多数据时,read() 将返回一个正数,然后您可以处理您读取的数据。或者,有更优雅但更复杂的方法使用特定于系统的 API,如 Linux 的inotify,它可以休眠直到文件的内容发生变化。您可能熟悉tail -f,它在不同的系统上使用了这些方法的某种组合。
另一个危险的错误是,如果其他人将文本写入预期不包含空字节的文件,您的孩子将读取超出缓冲区容量的数据,从而超出缓冲区。这可能是一个可利用的安全漏洞。
这是修复这些错误并为我工作的代码版本:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
void readLine (int fd, char *str, size_t max) {
size_t pos = 0;
while (pos < max) {
ssize_t n = read(fd, str + pos, 1);
if (n == 0) {
sleep(1);
} else if (n == 1) {
if (str[pos] == '\0') {
return;
}
pos++;
} else {
perror("read() failure");
exit(2);
}
}
fprintf(stderr, "Didn't receive null terminator in time\n");
exit(2);
}
int main(int argc, char ** argv){
int fd=open("sharedFile", O_CREAT|O_RDWR|O_TRUNC, 0600);
if (fd < 0) {
perror("parent opening sharedFile");
exit(2);
}
pid_t pid = fork();
if (pid == 0){
int newfd = open("sharedFile", O_RDONLY);
if (newfd < 0) {
perror("child opening sharedFile");
exit(2);
}
char buf[1000];
while (1) {
readLine(newfd, buf, 1000);
printf("%s\n",buf);
}
} else if (pid > 0) {
while (1){
sleep(1);
write(fd,"abcd",strlen("abcd")+1);
}
} else {
perror("fork");
exit(2);
}
return 0;
}