gpt4 book ai didi

c - Linux x86-64 fork syscall 针对 C 标准 libc FILE I/O 的奇怪行为(关键字 : fork, fclose,linux)

转载 作者:行者123 更新时间:2023-12-03 09:45:22 25 4
gpt4 key购买 nike

这个问题在这里已经有了答案:





Why does forking my process cause the file to be read infinitely

(4 个回答)


1年前关闭。




故事
我试图诊断在 Linux 上用 C 编写的应用程序中的错误。原来这个bug是由引起的忘记 fclose在子进程时FILE *句柄在父进程中仍然打开。
文件操作只有read .没有写操作。
情况1
应用程序在 Linux 5.4.0-58-generic 上运行.在这种情况下,错误发生了。
案例2
应用程序在 Linux 5.10.0-051000-generic 上运行.在这种情况下,有 没有错误 ,这正是我所期望的。
什么是错误?
父进程做fork的随机数系统调用,如果没有 fclose在子进程中。
案例2肯定
我完全清楚 忘记 fclose会导致内存泄漏,但是:

  • 我想,就在这种情况下 , 不是绝对必要的,因为子进程要尽快退出,而我使用的退出是exit(3)不是 _exit(2) .
  • 奇怪的是,怎么会忘记 fclose子进程影响父进程?

  • 我目前的猜测:
    这是 5.4 之后的版本中已修复的 Linux 内核错误。 .然而我没有证据,但我的测试告诉我。

    问题
    我已经能够通过调用 fclose 来修复这个应用程序错误。在子进程退出之前。但是,我想知道在这种情况下实际发生了什么。所以我的问题是 怎么会忘记fclose子进程影响父进程?

    重现问题的非常简单的代码(附加了 3 个文件)。
    注意:test1.c 和 test2.c 的区别仅在 fclose在子进程中。 test2.c 不调用 fclose在子进程中。
    文件 test.txt
    123123123
    123123123
    123123123
    123123123
    123123123
    123123123

    文件 test1.c
    #include <stdio.h>
    #include <errno.h>
    #include <stdlib.h>
    #include <stdint.h>
    #include <unistd.h>
    #include <string.h>
    #include <sys/wait.h>
    #define TICK do { putchar('.'); fflush(stdout); } while(0)
    int main() {
    char buff[1024] = {0};
    FILE *handle = fopen("test.txt", "r");

    uint32_t num_of_forks = 0;

    while (fgets(buff, 1024, handle) != NULL) {

    TICK;
    num_of_forks++;

    pid_t pid = fork();
    if (pid == -1) {
    printf("Fork error: %s\n", strerror(errno));
    continue;
    }

    if (pid == 0) {
    fclose(handle);
    exit(0);
    }
    }

    fclose(handle);
    putchar('\n');
    printf("Number of forks: %d\n", num_of_forks);
    wait(NULL);
    }

    文件 test2.c
    #include <stdio.h>
    #include <errno.h>
    #include <stdlib.h>
    #include <stdint.h>
    #include <unistd.h>
    #include <string.h>
    #include <sys/wait.h>
    #define TICK do { putchar('.'); fflush(stdout); } while(0)
    int main() {
    char buff[1024] = {0};
    FILE *handle = fopen("test.txt", "r");

    uint32_t num_of_forks = 0;

    while (fgets(buff, 1024, handle) != NULL) {

    TICK;
    num_of_forks++;

    pid_t pid = fork();
    if (pid == -1) {
    printf("Fork error: %s\n", strerror(errno));
    continue;
    }

    if (pid == 0) {
    // fclose(handle);
    exit(0);
    }
    }

    fclose(handle);
    putchar('\n');
    printf("Number of forks: %d\n", num_of_forks);
    wait(NULL);
    }


    运行程序

    在 Linux 5.4.0-58-generic 上运行(发生错误的地方)
    查看 test2 执行(错误),它导致 fork 系统调用的随机数。
    ammarfaizi2@integral:/tmp$ uname -r
    5.4.0-58-generic
    ammarfaizi2@integral:/tmp$ gcc --version
    gcc (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0
    Copyright (C) 2019 Free Software Foundation, Inc.
    This is free software; see the source for copying conditions. There is NO
    warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

    ammarfaizi2@integral:/tmp$ ldd --version
    ldd (Ubuntu GLIBC 2.31-0ubuntu9.1) 2.31
    Copyright (C) 2020 Free Software Foundation, Inc.
    This is free software; see the source for copying conditions. There is NO
    warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
    Written by Roland McGrath and Ulrich Drepper.
    ammarfaizi2@integral:/tmp$ cat test.txt
    123123123
    123123123
    123123123
    123123123
    123123123
    123123123
    ammarfaizi2@integral:/tmp$ diff test1.c test2.c
    27c27
    < fclose(handle);
    ---
    > // fclose(handle);
    ammarfaizi2@integral:/tmp$ gcc test1.c -o test1 && gcc test2.c -o test2
    ammarfaizi2@integral:/tmp$ ./test1
    ......
    Number of forks: 6
    ammarfaizi2@integral:/tmp$ ./test1
    ......
    Number of forks: 6
    ammarfaizi2@integral:/tmp$ ./test1
    ......
    Number of forks: 6
    ammarfaizi2@integral:/tmp$ ./test2
    ..................................................................................................................................................................................
    Number of forks: 178
    ammarfaizi2@integral:/tmp$ ./test2
    ............................................................................................................................................................................................................................................................................................................................................................
    Number of forks: 348
    ammarfaizi2@integral:/tmp$ ./test2
    ...........................................................................................................................................................................................................................................................................................................................................................................................................................................................................................
    Number of forks: 475
    ammarfaizi2@integral:/tmp$ md5sum test1 test2
    c32d03916b9b72546b966223837fd115 test1
    f314d2135092362288a66f53b37ffa4d test2
    在 Linux 5.10.0-051000-generic 上运行(相同的代码,完全没有错误)
    root@esteh:/tmp# uname -r
    5.10.0-051000-generic
    root@esteh:/tmp# gcc --version
    gcc (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0
    Copyright (C) 2019 Free Software Foundation, Inc.
    This is free software; see the source for copying conditions. There is NO
    warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

    root@esteh:/tmp# ldd --version
    ldd (Ubuntu GLIBC 2.31-0ubuntu9.1) 2.31
    Copyright (C) 2020 Free Software Foundation, Inc.
    This is free software; see the source for copying conditions. There is NO
    warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
    Written by Roland McGrath and Ulrich Drepper.
    root@esteh:/tmp# cat test.txt
    123123123
    123123123
    123123123
    123123123
    123123123
    123123123
    root@esteh:/tmp# diff test1.c test2.c
    27c27
    < fclose(handle);
    ---
    > // fclose(handle);
    root@esteh:/tmp# gcc test1.c -o test1 && gcc test2.c -o test2
    root@esteh:/tmp# ./test1
    ......
    Number of forks: 6
    root@esteh:/tmp# ./test1
    ......
    Number of forks: 6
    root@esteh:/tmp# ./test1
    ......
    Number of forks: 6
    root@esteh:/tmp# ./test2
    ......
    Number of forks: 6
    root@esteh:/tmp# ./test2
    ......
    Number of forks: 6
    root@esteh:/tmp# ./test2
    ......
    Number of forks: 6
    root@esteh:/tmp# md5sum test1 test2 # Make sure the files are identical with case 1
    c32d03916b9b72546b966223837fd115 test1
    f314d2135092362288a66f53b37ffa4d test2

    概括
  • 遗忘fcloseLinux 5.4.0-58-generic 上的子进程中导致父进程中的 fork 系统调用很奇怪。
  • Linux 5.10.0-051000-generic 上似乎不存在该错误.
  • 最佳答案

    感谢@Jonathan Leffler!
    此问题与 Why does forking my process cause the file to be read infinitely 重复。
    缺少的知识,为什么Linux 5.10.0-051000-generic上没有出现bug原来它与内核无关。

    原来,父进程与子进程竞争(与内核无关)。

  • 注意:从 更改文件句柄的偏移量子进程 还将更改 中的偏移量父进程如果 句柄由父 创建.
  • 如果没有fclose(3)在子进程中,子进程将调用 lseek(2)只要他们调用exit(3) .这将导致父级重新读取相同的偏移量,因为子级调用 lseek(2)负偏移 + SEEK_CUR .

  • (我不知道为什么要在退出前调用 lseek(2),这可能在@Jonathan Leffler 的回答中已经解释过了,我没有仔细阅读整个答案)。
  • 如果 parent 在 child 调用 lseek(2) 之前完成读取整个文件.那么就完全没有问题了。

  • 此外,正如@iBug 所提到的,但请记住,进程调度可能会使结果不可预测,除非您实现某种“同步”。 Linux 5.10.0-051000-generic 上的父进程我用的机器只是一个 幸运过程在 child 调用 lseek(2) 之前总是先读取整个文件。 .
    我尝试在文件中添加更多行(为 150 行),因此父级通常会比读取 6 行慢,并且会发生未定义的行为。
    测试结果: https://gist.githubusercontent.com/ammarfaizi2/b72bd03fcc13779f96b8bbeef9253e66/raw/da1eff4ed5434aa51929e5c810d54de8ffe15548/test2_fix.txt

    关于c - Linux x86-64 fork syscall 针对 C 标准 libc FILE I/O 的奇怪行为(关键字 : fork, fclose,linux),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65456297/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com