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

C实现Minibash:execvp与父进程管道调用read命令卡顿问题

管道末尾的read内置函数阻塞问题

这是大学项目任务,无法分享完整代码,致歉。需实现read内置函数用于创建环境变量,逻辑本身不难,但当它作为管道最后一项时(例如ls | grep aux.txt | read a,预期环境变量a的值为aux.txt),程序会卡在fgets(value, sizeof(value),stdin);处,甚至终端无法恢复。

补充说明:若前序命令为内置命令(例如echo hi | echo hi2 | read a),$a可正常设为hi2,无异常。已尝试修改管道、替换fgets为read等方法,均未解决,这是代码中唯一未修复的部分。

简化主函数示例

char **argvv;
int fd[2][2];
int pid;

void fun_close(int **fd){
    close(fd[0][0]);
    close(fd[0][1]);
    close(fd[1][0]);
    close(fd[1][1]);
}

int main(int argc, char ***argvv) {
    argvv[0][0] = "echo";
    argvv[0][1] = "elpmaxe";
    argvv[1][0] = "rev";
    argvv[2][0] = "read";
    argvv[2][1] = "a";
    
    for (int i = 0; i < 2; i++) {
        pipe(fd[i]);
    }
    
    for(int i = 0; i< 3; i++){
        pid = fork();
        if(pid == 0){
            if(i ==0){
                dup2(fd[0][1], 1);
                fun_close((int**)fd);
                execvp(argvv[0][0], argvv[0]);
            }
            if(i == 1){
                dup2(fd[0][0], 0);
                dup2(fd[1][1], 1);
                fun_close((int**)fd);
                execvp(argvv[1][0], argvv[0]);
            }
        }else{
            if(i == 2){
                close(fd[0][1]);
                close(fd[0][0]);
                fun_read("read a", 3, fd[1]);
            }
        }
    }
    
    int corpse;
    int status;
    while ((corpse = wait(&status)) > 0)
        printf("Child %d exited with status 0x%.4X\n", corpse, status);

    return 0;
}

fun_read函数实现

int fun_read(char **command, int argc, int fd[]){
    char **env_varv;
    char value[1024];
    char last_var[1024];
    char *token;
    int status;
    char *delim = " \t\n";

    env_varv = malloc((argc-1) * sizeof(char *));

    for(int i = 1; i < argc; i++){
        env_varv[i-1] = strdup(command[i]);
        wait(status);
    }
    
    if (fd[0] != 0){
        printf("%d\n", fd[0]);
        dup2(fd[0], 0);
        close(fd[0]);
        close(fd[1]);
    }
    
    fgets(value, sizeof(value), stdin);
    
    int i = 0;
    token = strtok(value, delim);
    last_var[0] = '\0';
    while(token != NULL){
        if(i == argc-2){
            while (token != NULL){
                strcat(last_var,token);
                setenv(env_varv[i],last_var,1);
                token = strtok(NULL,delim);
                strcat(last_var," ");
            }
        }
        else if (env_varv[i] != NULL){
            setenv(env_varv[i],token,1);
            token = strtok(NULL,delim);
            i++;
        }
        else{
            break;
        }
    }
    return 0;
}

问题原因分析

  1. execvp参数错误:第二个子进程执行rev命令时,传入了argvv[0](echo的参数数组)而非argvv[1],导致rev命令执行异常,管道中无输出数据。
  2. 管道写端未关闭:父进程在调用fun_read前未关闭fd[1][1](管道的写端),此时管道写端仍有打开的文件描述符,读端(fd[1][0])会一直等待数据,不会触发EOF,导致fgets阻塞。
  3. wait调用错误:fun_read中的wait(status)参数应为指针(wait(&status)),且在此处等待子进程时机错误,会导致父进程提前阻塞,打乱管道数据流转。

修复方案

  1. 修正execvp参数:将第二个子进程的execvp(argvv[1][0], argvv[0]);改为execvp(argvv[1][0], argvv[1]);,确保rev命令拿到正确的参数。
  2. 关闭管道写端:父进程在调用fun_read前,关闭fd[1][1]:
    if(i == 2){
        close(fd[0][1]);
        close(fd[0][0]);
        close(fd[1][1]); // 新增:关闭管道写端
        fun_read("read a", 3, fd[1]);
    }
    
  3. 修复wait调用:移除fun_read循环中的wait(status),改为在父进程中等待所有子进程完成后再处理,或使用waitpid等待特定子进程,确保管道数据已全部写入。
  4. 确保管道描述符正确关闭:检查所有进程中不需要的管道描述符是否都已关闭,避免因残留的写端导致读阻塞。

内容的提问来源于stack exchange,提问作者Rodri R

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 17:55:28