Tiny-shell(三): 实现流水线处理
概述
这一讲我们探讨如何在tsh中实现流水线处理,诸如cmd1 | cmd2 | cmd3
这种命令的处理。建议您先阅读管道的实现以及tsh中重定向的处理。
分析以及实现
对于cmd1 | cmd2 | cmd3
这种命令来说,我们以|
(管道)作为分隔符,调用make_argv
函数将整条命令拆成命令数组,即拆分成cmd1
,cmd2
和cmd3
。可以看出,对于中间的cmd2
命令,它接受cmd1
的输入,同时将输出传递给cmd3
,这也就是我们处理的重点。所以对于每个|
,我们都创建一个管道和子进程,并利用管道来进行进程间的通信,这样就将命令间的输入输出串了起来。对于每一条命令(除了最后一条外),execute_cmd
都创建了一个管道和一个子进程,将每条命令的标准输出重定向到下一个命令的标准输入。父进程将其标准输出重定向到管道中,并调用execute_redirect
函数执行命令。子进程将其标准输入重定向为来自管道,并回到for循环中创建一个子进程来处理流水线中的下一条命令,这里可以类比为一个进程链。对于列表的最后一条命令来说,execute_cmd
没有创建一个子进程或者管道(因为后面没有命令了),而是直接调用execute_cmd
函数。
关于利用管道来进行父子输入输出通信,可以参考我的这篇博客,里面有详细的图文讲解。
编译运行
$ gcc -o pipeline tsh1.c execute_redirect.c parse_redirect.c execute_cmd_pipe.c make_argv.c
$ ./pipeline
tsh1>> ls -l | sort -n | grep execute
-rw-rw-r-- 1 chris chris 1365 3月 10 21:22 execute_cmd_pipe.c
-rw-rw-r-- 1 chris chris 1513 3月 10 21:18 execute_redirect.c
-rw-rw-r-- 1 chris chris 448 2月 27 20:27 execute_cmd_simple.c
-rw-rw-r-- 1 chris chris 587 3月 1 22:11 execute_cmd_redirect.c
代码实现
execute_cmd_pipe.c
/**
* @Filename: execute_cmd_pipe.c
* @Description: 处理流水线的execute_cmd函数,为了直观,省略了错误检查。
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
void execute_redirect(char *s, int in, int out);
int make_argv(const char *, const char *delimiters, char ***argvp);
void execute_cmd(char *cmds) {
int child;
int count;
int fds[2];
int i;
char **pipelist;
count = make_argv(cmds, "|", &pipelist);
if (count <= 0) {
fprintf(stderr, "Failed to find any commands\n");
exit(1);
}
/* 处理流水线, 比如"cmd1 | cmd 2 | cmd3" */
for (i = 0; i < count - 1; i++) {
pipe(fds);
child = fork();
if (child) {
dup2(fds[1], STDOUT_FILENO);
close(fds[0]);
close(fds[1]);
/* 只有第一条命令才可能有输入重定向 */
execute_redirect(pipelist[i], i == 0, 0);
exit(1);
}
dup2(fds[0], STDIN_FILENO);
close(fds[0]);
close(fds[1]);
}
/**
* 处理最后一条命令, 只有最后一条命令才可能有输出的重定向
* 当只有一条命令时且有输入输出重定向时, 上面的for循环不执行, 且in,out都为1
*/
execute_redirect(pipelist[i], i == 0, 1);
exit(1);
}
execute_redirect.c
/**
* @Filename: execute_redirect.c
* @Description: 对可能带有重定向的单个代码进行处理
*/
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int make_argv(const char *s, const char *delimiters, char ***argvp);
int parse_redirect_in(char *s);
int parse_redirect_out(char *s);
/**
* in为1, 表示需要处理输入重定向
* out为1, 表示需要处理输出重定向
*/
void execute_redirect(char *s, int in, int out) {
char **chargv;
char *pin;
char *pout;
/**
* 实现了对于"sort > f2 < f1"这种小于符号在后的命令处理
* 之前只能处理小于符号在大于符号之前.
*/
if (in && ((pin = strchr(s, '<')) != NULL) &&
out && ((pout = strchr(s, '>')) != NULL) && (pin > pout)) {
/* 先处理输入重定向, 处理完之后就把in置0 */
if (parse_redirect_in(s) == -1) {
perror("Failed to redirect input");
return ;
}
in = 0;
}
/* 一般情况按先处理输出重定向再处理输入重定向 */
if (out && (parse_redirect_out(s) == -1))
perror("Failed to redirect output");
else if (in && (parse_redirect_in(s) == -1))
perror("Failed to redirect input");
else if (make_argv(s, " \t", &chargv) <= 0)
fprintf(stderr, "Failed to parse command line\n");
else {
execvp(chargv[0], chargv);
perror("Failed to execute command");
}
exit(1);
}