Parent打开了一个要读取的文件,我派了两个孩子从文件中读取并写入不同的文件。
子1读取第一行,而子2读取任何内容。当我做一个ftell,它到达终点。
有人可以解释一下这种行为吗?
f[0] = fopen("input", "r");
for ( i = 1; i <= 2; i++ ){
if ((pid = fork()) != 0){
waitpid(pid);
}
else
{
snprintf ( buffer, 10, "output%d", i );
printf("opening file %s \n",buffer);
f[i] = fopen( buffer, "w");
fgets(buff2, 10, f[0]);
fprintf(f[i], "%s", buff2);
fclose(f[i]);
_exit(0);
}
}
fclose(f[0]);
答案 0 :(得分:2)
你的问题是缓冲。默认情况下,stdio在完全缓冲模式下读取文件,这意味着对fgets(3)
的调用实际上将从文件中读取大量字符,缓冲所有内容,然后返回第一行,同时将其余部分保留在缓冲区中,从未来再次调用的角度来看(记住stdio努力减少read(2)
和write(2)
系统调用的数量)。请注意,stdio缓冲是用户空间的事情;所有内核看到的是一个进程读取该文件上的一个巨大块,因此光标会相应更新。
通用块大小为4096和8192;你的输入文件可能比那个小,因此调用fgets(3)
的第一个进程最终会读取整个文件,最后将光标留在最后。缓冲很棘手。
你能做什么?我能想到的一个解决方案是禁用缓冲(因为这是我们正在谈论的输入流,我们不能使用行缓冲模式,因为行缓冲对于输入流来说没有意义)。因此,如果在分叉之前禁用输入流上的缓冲,一切都会起作用。这是通过setvbuf(3)
完成的。
这是一个有效的例子:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
static FILE *f[3];
static char buffer[128];
static char buff2[128];
int main(void) {
pid_t pid;
int i;
if ((f[0] = fopen("input", "r")) == NULL) {
perror("Error opening input file");
exit(EXIT_FAILURE);
}
if (setvbuf(f[0], NULL, _IONBF, 0) < 0) {
perror("setvbuf(3) failed");
exit(EXIT_FAILURE);
}
for (i = 1; i <= 2; i++) {
if ((pid = fork()) < 0) {
perror("fork(2) failed");
exit(EXIT_FAILURE);
}
if (pid != 0) {
if (waitpid(pid, NULL, 0) < 0) {
perror("waitpid(2) failed");
exit(EXIT_FAILURE);
}
} else {
snprintf(buffer, sizeof(buffer), "output%d", i);
printf("opening file %s\n", buffer);
if ((f[i] = fopen(buffer, "w")) == NULL) {
perror("fopen(2) failed");
exit(EXIT_FAILURE);
}
errno = 0;
if (fgets(buff2, sizeof(buff2), f[0]) == NULL) {
if (errno != 0) {
perror("fgets(3) error");
exit(EXIT_FAILURE);
}
}
fprintf(f[i], "%s", buff2);
fclose(f[i]);
exit(EXIT_SUCCESS);
}
}
fclose(f[0]);
return 0;
}
请注意,这可能会对性能造成重大影响。你的代码将会进行更多的系统调用,对于大文件来说它可能太昂贵了,但它似乎不是一个问题,因为显然你处理相对较小的输入文件。
答案 1 :(得分:1)
这是我的fork()
手册页的摘录:
子进程有自己的父进程描述符副本。这些描述符引用相同的底层对象,因此,例如,文件对象中的文件指针在子进程和父进程之间共享,因此子进程中描述符上的lseek(2)可以影响后续的读取或写入父母。 shell还使用此描述符复制为新创建的进程建立标准输入和输出,以及设置管道。
所以你的行为完全正常。如果您希望您的孩子拥有自己的文件描述符,它应该打开自己的文件。
例如,您可以执行以下操作:
for ( i = 1; i <= 2; i++ )
{
if ((pid = fork()) != 0)
{
waitpid(pid);
}
else
{
f[0] = fopen("input", "r"); // New
snprintf ( buffer, 10, "output%d", i );
printf("opening file %s \n",buffer);
f[i] = fopen( buffer, "w");
fgets(buff2, 10, f[0]);
fprintf(f[i], "%s", buff2);
fclose(f[i]);
fclose(f[0]); //New
_exit(0);
}
}
此外,您应该检查错误(else
中的几乎所有功能都可能因错误而失败)。