Tiny-shell(三): 实现流水线处理

Tiny-shell(三): 实现流水线处理


概述

这一讲我们探讨如何在tsh中实现流水线处理,诸如cmd1 | cmd2 | cmd3这种命令的处理。建议您先阅读管道的实现以及tsh中重定向的处理

分析以及实现

对于cmd1 | cmd2 | cmd3这种命令来说,我们以|(管道)作为分隔符,调用make_argv函数将整条命令拆成命令数组,即拆分成cmd1,cmd2cmd3。可以看出,对于中间的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);
}
上一篇:【文献阅读】Scale Match for Tiny Person Detection-微小人物检测的尺度匹配


下一篇:Tiny Web服务器代码分析