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

为何处理大文件时程序阻塞?附代码与文件描述符信息

管道处理导致大文件执行时父进程阻塞的问题排查

问题现象

执行cat ./small_size_file | grep oi时程序行为符合预期,但执行cat ./big_size_file | grep oi时,父进程会卡住并持续等待子进程。

相关代码

ft_fork函数

void    ft_fork(t_shell *shell, t_cmd *tmp_cmd)
{
    pid_t   cmd;
    int     status;

    status = 0;
    cmd = fork();
    if (cmd < 0)
        ft_clean(shell, 1);
    if (cmd == 0)
    {
        ft_execute_cmd(shell, tmp_cmd);
        close(STDIN_FILENO);
        close(STDOUT_FILENO);
        exit (EXIT_SUCCESS);
    }
    waitpid(cmd, &status, 0);
    if (!WTERMSIG(status))
        g_status = status >> 8;
}

ft_execute_cmd函数

void    ft_execute_cmd(t_shell *shell, t_cmd *tmp_cmd)
{
    char    *binpath;

    binpath = ft_search_cmd(shell, tmp_cmd);
    if (binpath)
    {
        if (execve(binpath, tmp_cmd->sim_cmd, shell->env) < 0)
        {
            ft_print_error(5);
            exit(127);
        }
    }
}

fork前的文件描述符

ls -l /proc/17912/fd
total 0
lrwx------ 1 joao joao 64 dez  4 18:30 0 -> /dev/pts/4
l-wx------ 1 joao joao 64 dez  4 18:34 1 -> 'pipe:[47741]'
lrwx------ 1 joao joao 64 dez  4 18:34 2 -> /dev/pts/4
lrwx------ 1 joao joao 64 dez  4 18:34 3 -> /dev/pts/4
lrwx------ 1 joao joao 64 dez  4 18:34 4 -> /dev/pts/4
lr-x------ 1 joao joao 64 dez  4 18:34 5 -> 'pipe:[47741]'

问题根源

核心问题是父进程没有关闭未使用的管道文件描述符,结合管道的工作机制形成死锁:

  1. 管道缓冲区存在大小限制,大文件写入时,cat子进程会填满缓冲区,随后阻塞等待数据被读取。
  2. 父进程仍持有管道的写端(fd1)和读端(fd5):
    • 即使cat子进程写完数据退出,管道写端并未完全关闭(父进程还持有),grep子进程读取完缓冲区数据后,会一直阻塞等待新数据,无法收到EOF信号。
    • cat子进程因管道缓冲区满无法继续写入,持续阻塞;父进程通过waitpid等待cat子进程结束,也随之卡住,形成循环阻塞。
  3. 小文件能正常执行是因为数据量小,可一次性写入管道缓冲区,cat子进程快速退出,未触发阻塞条件,属于巧合场景。

另外,子进程中的close(STDIN_FILENO)和close(STDOUT_FILENO)实际无效:execve成功后会替换进程地址空间,这些代码不会被执行;只有execve失败时才会运行,但正常执行命令时不会走到这一步。

修复方案

  1. 父进程fork后关闭未使用的管道描述符:
    父进程不需要操作管道的读写端,fork后立即关闭管道相关的fd(此处为fd1和fd5),确保管道的读写端仅由对应的子进程持有。
  2. 子进程execve前关闭不需要的管道端:
    比如cat子进程不需要管道读端(fd5),grep子进程不需要管道写端(fd1),提前关闭避免资源泄漏,同时保证管道状态正确。
  3. 完善命令找不到的错误处理:
    ft_execute_cmd中如果binpath为空,需明确处理错误并退出,避免子进程无意义执行后续代码。

修改后的代码示例

ft_fork函数

void    ft_fork(t_shell *shell, t_cmd *tmp_cmd, int pipe_read_fd, int pipe_write_fd)
{
    pid_t   cmd;
    int     status;

    status = 0;
    cmd = fork();
    if (cmd < 0)
        ft_clean(shell, 1);
    if (cmd == 0)
    {
        // 根据子进程角色关闭不需要的管道端:cat关闭读端,grep关闭写端
        if (/* 判断当前是cat命令 */) {
            close(pipe_read_fd); // 对应fd5
        } else if (/* 判断当前是grep命令 */) {
            close(pipe_write_fd); // 对应fd1
        }
        // 关闭其他无关的文件描述符(可选但推荐)
        close(3);
        close(4);
        ft_execute_cmd(shell, tmp_cmd);
        // 仅execve失败时执行以下代码
        ft_print_error(/* 命令执行失败错误码 */);
        close(STDIN_FILENO);
        close(STDOUT_FILENO);
        exit(EXIT_FAILURE);
    }
    // 父进程关闭管道的两端,不再持有管道描述符
    close(pipe_read_fd);
    close(pipe_write_fd);
    waitpid(cmd, &status, 0);
    if (!WTERMSIG(status))
        g_status = status >> 8;
}

ft_execute_cmd函数

void    ft_execute_cmd(t_shell *shell, t_cmd *tmp_cmd)
{
    char    *binpath;

    binpath = ft_search_cmd(shell, tmp_cmd);
    if (binpath)
    {
        if (execve(binpath, tmp_cmd->sim_cmd, shell->env) < 0)
        {
            ft_print_error(5);
            exit(127);
        }
    }
    else
    {
        ft_print_error(/* 命令未找到错误码 */);
        exit(127);
    }
}

内容的提问来源于stack exchange,提问作者42JQuina

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 23:08:15