You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

fork创建子进程导致fgets无限重复读取文件的问题排查

问题原因与解决方案:子进程继承stdin导致重复读取输入

你的程序出现无限重复读取命令的核心原因是子进程继承了父进程的stdin文件描述符,当子进程执行的命令会读取stdin(或意外读取)时,会干扰父进程的文件读取偏移,甚至导致父进程在文件读完后,因子进程的操作意外触发重复读取逻辑。

同时代码存在几个潜在问题会加剧该情况:

  • makearg函数未处理连续空格,会生成空参数,可能导致execvp失败,频繁的fork/exit会引发文件描述符的异常交互
  • fgets的缓冲区大小设置为128,小于command数组的1024,可能导致命令行被截断,剩余内容留在stdin中引发后续读取混乱
  • 子进程未关闭stdin,即使execvp成功,新进程仍会继承stdin,可能读取父进程的输入源

解决方案

1. 子进程中关闭stdin或重定向到/dev/null

在子进程分支中,执行execvp前关闭stdin,彻底避免子进程读取父进程的输入源:

case 0:
    // 关闭stdin,防止子进程读取父进程的输入文件
    close(STDIN_FILENO);
    // 可选:重定向到/dev/null,避免部分程序因stdin关闭报错
    // int dev_null = open("/dev/null", O_RDONLY);
    // if (dev_null != -1) {
    //     dup2(dev_null, STDIN_FILENO);
    //     close(dev_null);
    // }
    execvp(args[0], args);
    perror("execvp failed to run command");
    exit(EXIT_FAILURE);

2. 修复makearg函数,处理连续空格与空行

修改函数逻辑,正确计数有效命令参数,跳过连续空格和空行:

int makearg(char* s, char*** args) {
    // 计数有效token,跳过连续空格
    int tokencount = 0;
    int slength = 0;
    int in_token = 0;
    for(char* c = s; *c != '\0'; c++) {
        slength++;
        if(*c == ' ' || *c == '\n') {
            in_token = 0;
        } else if(!in_token) {
            in_token = 1;
            tokencount++;
        }
    }

    // 处理空行
    if(tokencount == 0) {
        *args = NULL;
        return 0;
    }

    // 分配内存
    char** tokens = malloc(tokencount * sizeof(char*));
    if(tokens == NULL) {
        return -1;
    }

    // 填充token数组,跳过空内容
    int itoken = 0;
    int ichar = 0;
    in_token = 0;
    for(char* c = s; *c != '\0'; c++) {
        if(*c == ' ' || *c == '\n') {
            if(in_token) {
                tokens[itoken][ichar] = '\0';
                itoken++;
                ichar = 0;
                in_token = 0;
            }
        } else {
            if(!in_token) {
                tokens[itoken] = malloc(slength * sizeof(char));
                if(tokens[itoken] == NULL) {
                    // 内存分配失败,回滚释放
                    for(int i = 0; i < itoken; i++) {
                        free(tokens[i]);
                    }
                    free(tokens);
                    return -1;
                }
                in_token = 1;
            }
            tokens[itoken][ichar] = *c;
            ichar++;
        }
    }
    // 处理最后一个token
    if(in_token) {
        tokens[itoken][ichar] = '\0';
    }

    *args = tokens;
    return tokencount;
}

3. 修正fgets的缓冲区大小与循环条件

将fgets的缓冲区大小改为command数组的实际大小,并使用更清晰的循环条件:

char command[BUFFERSIZE];
while(fgets(command, BUFFERSIZE, stdin) != NULL) {
    char** args;
    int token_count = makearg(command, &args);
    // 跳过空行或无效命令
    if(token_count <= 0) {
        continue;
    }

    // 剩余逻辑不变...
}

4. 释放makearg分配的内存

在父进程中,fork后及时释放args的内存,避免内存泄漏:

switch(fork()) {
    case 0:
        // 子进程逻辑...
    case -1:
        printf("failed to create child\n");
        // 释放内存
        for(int i = 0; i < token_count; i++) {
            free(args[i]);
        }
        free(args);
        break;
    default:
        pc++;
        // 释放内存
        for(int i = 0; i < token_count; i++) {
            free(args[i]);
        }
        free(args);
}

内容的提问来源于stack exchange,提问作者Sebastian Nuxoll

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 17:44:50