欢迎来到进程控制系列第三篇!在前两篇中我们学习了进程的创建、终止、等待以及程序替换等基础。今天我们将把这些知识整合起来,亲手打造一个简易但功能完整的自定义Shell(命令行解释器),并深入探讨多命令管道、重定向等进程协作实践。通过本文,你将彻底理解Shell背后的工作原理,并为后续学习更复杂的系统编程打下坚实基础。
在动手之前,我们必须重温几个关键的进程控制API:fork()创建子进程,exec族函数替换进程映像,wait()/waitpid()回收子进程资源,以及用于进程协作的管道pipe()和重定向dup2()。这些系统调用是我们构建自定义Shell的砖瓦。
一个最基本的命令行解释器需要完成:读取用户输入、解析命令、执行命令(包括内置命令和外部程序)。我们还要扩展支持进程协作功能,例如管道(|)和输入输出重定向(<、>)。本文的进程控制实践将围绕如何创建多个子进程并通过管道连接它们。
#include #include #include #include #include #define MAX_CMD_LEN 1024#define MAX_ARG_NUM 64int main() { char cmd[MAX_CMD_LEN]; char *args[MAX_ARG_NUM]; while (1) { printf("myshell> "); fflush(stdout); if (fgets(cmd, sizeof(cmd), stdin) == NULL) break; cmd[strcspn(cmd, "")] = 0; // 去除换行符 // 解析命令到args数组... // 执行命令... } return 0;} 解析命令时,我们需要处理引号、转义、分割参数。为了简化,这里只做空格分割。注意检查内置命令如cd和exit,它们必须由Shell自身执行而不创建子进程。
这是最精彩的进程协作部分!当检测到命令中包含|时,我们需要创建多个子进程,每个子进程通过pipe()连接,并利用dup2()重定向标准输入/输出。下面是一个处理单管道的核心片段:
void execute_pipeline(char *cmd1, char *cmd2) { int pipefd[2]; pipe(pipefd); pid_t pid1 = fork(); if (pid1 == 0) { // 第一个子进程:输出重定向到管道写端 dup2(pipefd[1], STDOUT_FILENO); close(pipefd[0]); close(pipefd[1]); execlp("/bin/sh", "sh", "-c", cmd1, NULL); perror("execlp"); exit(1); } pid_t pid2 = fork(); if (pid2 == 0) { // 第二个子进程:输入重定向到管道读端 dup2(pipefd[0], STDIN_FILENO); close(pipefd[0]); close(pipefd[1]); execlp("/bin/sh", "sh", "-c", cmd2, NULL); perror("execlp"); exit(1); } close(pipefd[0]); close(pipefd[1]); waitpid(pid1, NULL, 0); waitpid(pid2, NULL, 0);} 重定向符号>和<的处理类似:在子进程中打开文件并用dup2()重定向到相应文件描述符。通过这样的进程控制,我们实现了命令间的协作。
将上述模块整合,我们就得到了一个能够处理简单命令、管道、重定向和内置命令的自定义Shell。注意错误处理和资源回收,避免僵尸进程。完整的代码较长,这里给出核心逻辑:
cd用chdir())。 编译运行我们的命令行解释器,输入ls -l | grep .c | wc -l,应该能正确统计当前目录下C文件的数量。再试试echo "hello" > test.txt,然后cat test.txt,验证重定向是否成功。
通过本文的实践,我们不仅掌握了一个自定义Shell的实现,更深入理解了进程控制和进程协作的核心机制。你可以继续扩展它,比如添加作业控制、环境变量、通配符匹配、脚本执行等功能。这将成为你Linux系统编程路上的一个坚实里程碑。
—— 进程控制(三) 完 ——
本文由主机测评网于2026-03-15发表在主机测评网_免费VPS_免费云服务器_免费独立服务器,如有疑问,请联系我们。
本文链接:http://www.vpshk.cn/20260331271.html