在孩子

时间:2015-07-24 15:07:32

标签: c fork

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

2 个答案:

答案 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中的几乎所有功能都可能因错误而失败)。