为何处理大文件时程序阻塞?附代码与文件描述符信息
管道处理导致大文件执行时父进程阻塞的问题排查
问题现象
执行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]'
问题根源
核心问题是父进程没有关闭未使用的管道文件描述符,结合管道的工作机制形成死锁:
- 管道缓冲区存在大小限制,大文件写入时,
cat子进程会填满缓冲区,随后阻塞等待数据被读取。 - 父进程仍持有管道的写端(fd1)和读端(fd5):
- 即使
cat子进程写完数据退出,管道写端并未完全关闭(父进程还持有),grep子进程读取完缓冲区数据后,会一直阻塞等待新数据,无法收到EOF信号。 cat子进程因管道缓冲区满无法继续写入,持续阻塞;父进程通过waitpid等待cat子进程结束,也随之卡住,形成循环阻塞。
- 即使
- 小文件能正常执行是因为数据量小,可一次性写入管道缓冲区,
cat子进程快速退出,未触发阻塞条件,属于巧合场景。
另外,子进程中的close(STDIN_FILENO)和close(STDOUT_FILENO)实际无效:execve成功后会替换进程地址空间,这些代码不会被执行;只有execve失败时才会运行,但正常执行命令时不会走到这一步。
修复方案
- 父进程fork后关闭未使用的管道描述符:
父进程不需要操作管道的读写端,fork后立即关闭管道相关的fd(此处为fd1和fd5),确保管道的读写端仅由对应的子进程持有。 - 子进程execve前关闭不需要的管道端:
比如cat子进程不需要管道读端(fd5),grep子进程不需要管道写端(fd1),提前关闭避免资源泄漏,同时保证管道状态正确。 - 完善命令找不到的错误处理:
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
相关产品推荐
相关产品推荐

