fork环境下fgets读取文件未终止,重复循环读取行问题
问题分析与解决
核心问题原因
文件描述符泄漏引发未定义行为
循环中每次重新打开临时文件Tmp前,未关闭之前打开的Tmp指针,导致文件描述符持续泄漏。当描述符数量达到系统上限后,fopen会失败,Tmp变为NULL。后续子进程调用write(fileno(Tmp), ...)时,fileno(NULL)属于未定义行为,可能意外写入到其他打开的文件描述符(比如输入文件Nombres),导致输入文件内容被篡改,父进程的fgets重复读取旧内容,循环无法终止。父子进程stdio缓冲区共享导致输出重复
最初用fprintf时,父子进程都持有Output文件的stdio用户态缓冲区,子进程退出时会自动刷新缓冲区,导致同一段内容被父进程和子进程各写入一次,出现重复行。改用write直接操作内核文件描述符则不会有这个问题,因为write不经过用户态缓冲区,内核会维护统一的文件偏移量。
解决步骤
- 关闭旧文件指针,避免泄漏
每次重新打开临时文件前,关闭之前的Tmp指针,释放文件描述符。 - 子进程关闭无关文件
子进程只需要操作临时文件,关闭其他无关的文件描述符,减少资源占用和意外操作的风险。 - 添加文件操作的错误检查
所有fopen、fork、write操作都要检查返回值,避免在出错后继续执行引发更多问题。
修改后的代码示例
#include <stdlib.h> #include <stdio.h> #include <unistd.h> #include <sys/wait.h> #include <string.h> #define MAXIMA_LONGITUD_LINEA 256 int main(int argc, char *argv[]) { // 参数检查 if (argc != 4) { fprintf(stderr, "Usage: %s <nombres> <apellidos> <output>\n", argv[0]); exit(EXIT_FAILURE); } // 打开输入输出文件并检查状态 FILE *Nombres = fopen(argv[1], "r"); if (!Nombres) { perror("Failed to open nombres file"); exit(EXIT_FAILURE); } FILE *Apellidos = fopen(argv[2], "r"); if (!Apellidos) { perror("Failed to open apellidos file"); fclose(Nombres); exit(EXIT_FAILURE); } FILE *Output = fopen(argv[3], "w"); if (!Output) { perror("Failed to open output file"); fclose(Nombres); fclose(Apellidos); exit(EXIT_FAILURE); } char *tempname = "aaa.txt"; FILE *Tmp = fopen(tempname, "w+"); if (!Tmp) { perror("Failed to open temp file"); fclose(Nombres); fclose(Apellidos); fclose(Output); exit(EXIT_FAILURE); } char linea[MAXIMA_LONGITUD_LINEA]; pid_t hijoid; while (fgets(linea, MAXIMA_LONGITUD_LINEA, Nombres) != NULL) { printf("%s", linea); // 避免额外换行,linea已包含原文件的换行符 // 关闭旧的临时文件指针,释放描述符 if (Tmp) { fclose(Tmp); } // 重新打开临时文件,清空内容 Tmp = fopen(tempname, "w+"); if (!Tmp) { perror("Failed to re-open temp file"); fclose(Nombres); fclose(Apellidos); fclose(Output); exit(EXIT_FAILURE); } hijoid = fork(); if (hijoid == -1) { perror("Fork failed"); fclose(Nombres); fclose(Apellidos); fclose(Output); fclose(Tmp); exit(EXIT_FAILURE); } if (hijoid == 0) { // 子进程关闭无关文件 fclose(Nombres); fclose(Apellidos); fclose(Output); // 写入临时文件 ssize_t written = write(fileno(Tmp), linea, strlen(linea)); if (written == -1) { perror("Write to temp file failed"); fclose(Tmp); exit(EXIT_FAILURE); } fclose(Tmp); exit(EXIT_SUCCESS); } else { // 父进程等待子进程完成 int status; waitpid(hijoid, &status, 0); if (!WIFEXITED(status) || WEXITSTATUS(status) != EXIT_SUCCESS) { fprintf(stderr, "Child process exited with error\n"); } rewind(Tmp); // 读取临时文件并写入输出 if (fgets(linea, MAXIMA_LONGITUD_LINEA, Tmp)) { ssize_t written = write(fileno(Output), linea, strlen(linea)); if (written == -1) { perror("Write to output file failed"); } } else { fprintf(stderr, "Failed to read line from temp file\n"); } } } // 清理所有资源 fclose(Nombres); fclose(Apellidos); fclose(Output); if (Tmp) { fclose(Tmp); } return EXIT_SUCCESS; }
内容的提问来源于stack exchange,提问作者Neon
相关产品推荐
相关产品推荐

