zoukankan      html  css  js  c++  java
  • 第7章 进程关系(5)_贯穿案例2:mini shell(2)

    5. 贯穿案例2:mini shell(2)

    (1)己经完成的功能:pwd、cd、exit命令

    (2)阶段性目标:

      ①env、export、echo及其他命令

      ②标准输入、输出重定向">"、"<"、">>"

      ③设置后台进程

    (3)存在问题:当mshell(后台进程)要读写终端时(如执行date时),进程会被暂停。解决方案见下一章的《信号》

    【编程实验】mini  shell

    //job.h

    #ifndef __JOB_H__
    #define __JOB_H__
    #include <sys/types.h>
    
    //重定向类型(这里只支持3种,<、>和>>)
    enum RedirectType{RedirectRead, RedirectWrite, RedirectAppend};
    typedef struct
    {
        enum RedirectType redirect; //重定向的类型
        int  fd;    //将标准输入、输出重定向到fd这个目标文件
    }Redirection;
    
    //接收命令行参数
    typedef struct
    {
        pid_t pid;    //进程pid
        char** args;  //对应于主函数中的char* argv[]参数
    
        //每个命令允许使用多个重定向符号,放在以下数组中
        //如:echo aaa>s.txt bbb>>s.txt
        Redirection*  redirects;  //堆上申请的数组
        int           redirect_num;
    }Program;
    
    //命令行中可以包含多个程序,如
    //#date;ls -l,单个或多个命令通过cmd传入Job结构体中
    typedef struct
    {
        char*     cmd;       //单条命令或多条命令(用分号隔开)
        int       progs_num; //作业中包含程序的数量
        Program*  progs;     //各个程序的命令行参数
        pid_t     pgid;      //进程组ID,设置前(后)台进程
    }Job;
    
    //创建作业
    extern Job* create_job(char* cmd);
    //销毁作业
    extern void destroy_job(Job* job);
    //创建进程(命令)
    extern Program* create_program(char** arg);
    //销毁进程(命令)
    extern void destroy_program(Program* prog);
    //将命令加入作业中
    extern int add_program(Job* job, Program* prog);
    
    extern Redirection* create_redirect(int fd, enum RedirectType type);
    extern void destroy_redirect(Redirection* r);
    extern void add_redirection(Program* prog, Redirection* r);
    
    #endif

    //job.c

    #include "job.h"
    #include <malloc.h>
    #include <assert.h>
    #include <string.h>
    
    //创建作业
    Job* create_job(char* cmd)
    {
        Job* job = (Job*)malloc(sizeof(Job));
        assert( job != NULL);
        
        job->cmd = (char*)malloc(sizeof(char) * strlen(cmd));
        assert(job->cmd != NULL);
        strcpy(job->cmd, cmd);
    
        job->progs_num = 0;
        job->progs = NULL;
    
        return job;
    }
    
    //销毁作业
    void destroy_job(Job* job)
    {
        assert(job != NULL);
        free(job->progs);
        free(job->cmd);
        free(job);
    }
    
    //arg格式:command  arg0 arg1 ==> 返回3
    static int arg_num(char** arg)
    {
        int ret = 0;
        char* start = arg[0];
       
        while(start != NULL){
            start = arg[++ret];
        }
    
        return ret;
    }
    
    //创建进程(命令)
    Program* create_program(char** arg)
    {
        Program* prog = (Program*)malloc(sizeof(Program));
        assert(prog != NULL);
        
        prog->redirect_num = 0;
        prog->redirects = NULL;
    
        int counter = arg_num(arg);
        prog->args = (char**)calloc(counter + 1, sizeof(char*)); //以NULL结尾
    
        int i = 0;
        for(i=0; i< counter; i++){
           int len = strlen(arg[i]);
           prog->args[i] = (char*)malloc(len);
           assert(prog->args[i] != NULL);
    
           strcpy(prog->args[i], arg[i]);
        }
    
        prog->args[i] = NULL;  //指针数组,以NULL结尾
    
        return prog;
    }
    
    //销毁进程(命令)
    void destroy_program(Program* prog)
    {
        assert(prog != NULL);
    
        int i = 0;    
        while(prog->args[i] != NULL)
        {
            free(prog->args[i++]);        
        }
        
        free(prog->redirects);
        free(prog->args);
        free(prog);
    }
    
    //将命令加入作业中
    int add_program(Job* job, Program* prog)
    {
        //重新申请一片空间以增加一条命令进来,放入job->progs中
        Program* ps = (Program*)malloc(sizeof(Program) * (job->progs_num + 1));
        memcpy(ps, job->progs, job->progs_num * sizeof(Program));
        
        ps[job->progs_num++] = *prog;//将新的进程(命令)加入进来
        
        free(job->progs); //释放旧的程序(命令)组
        job->progs = ps;
    
        return job->progs_num - 1; //返回新命令的索引号
    }
    
    Redirection* create_redirect(int fd, enum RedirectType type)
    {
        Redirection* r = (Redirection*)calloc(1, sizeof(Redirection));
        assert( r!= NULL);
    
        r->fd = fd;
        r->redirect = type;
    
        return r;
    }
    
    void destroy_redirect(Redirection* r)
    {
        assert( r!= NULL);
        free(r);
    }
    
    void add_redirection(Program* prog, Redirection* r)
    {
        Redirection* rs = (Redirection*)calloc(prog->redirect_num + 1, 
                                               sizeof(Redirection));
        assert(rs != NULL);
        //复制原有数据
        if(prog->redirects != NULL){
            memcpy(rs, prog->redirects, prog->redirect_num* sizeof(Redirection));
            free(prog->redirects); //释放原有的redirects
        }
    
        prog->redirects = rs;
    
        //将新的redirection加入数组中
        memcpy(&prog->redirects[prog->redirect_num], r, sizeof(Redirection));
    
        prog->redirect_num += 1;
    }

    //mshell.c

    #include "job.h"
    #include <unistd.h>
    #include <fcntl.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <malloc.h>
    #include <string.h>
    #include <assert.h>
    #include <sys/wait.h>
    
    char* prompt = "mshell> "; //命令行的提示符
    #define MAX_COMMAND_LEN 256 //命令行最多的字符数
    extern char** environ;      //环境表指针
    
    //前台和后台进程组标志
    #define FOREGROUND   0      //前台进程组
    #define BACKGROUND   1      //后台进程组
    
    //env命令
    void env_fun(void)
    {
        int i = 0;
        char* env = NULL;
        while((env = environ[i++]) != NULL){
            printf("%s
    ", env);
        }
    }
    
    //export命令
    void export_fun(Program* prog)
    {
        //export格式:export CITY=ShangHai
        if(prog->args[1] == NULL){
            fprintf(stderr, "export: invalid argument
    ");
            return;
        }
    
        putenv(prog->args[1]);
    }
    
    //echo命令
    void echo_fun(Program* prog)
    {
        char* s = prog->args[1];
        if(s == NULL){
            fprintf(stderr, "echo: invalid argument
    ");
            return;
        }
        //echo的格式:echo $PATH
        //1. echo $PATH 从环境变量中读取
        //2. echo PATH  直接输出echo后面的字符,如“PATH”
        if(s[0] == '$'){
            char* v = getenv(s + 1);
            printf("%s
    ", v);
        }else{
            printf("%s
    ", s);
        }
    }
    
    //cd命令
    void cd_fun(Program* prog)
    {
        if(chdir(prog->args[1]) < 0){
            perror("cd error");
        }
    }
    
    //pwd命令
    void pwd_fun(Program* prog)
    {
        char buffer[256];
        memset(buffer, 0, sizeof(buffer));
    
        if(getcwd(buffer, sizeof(buffer)) == NULL){
            perror("pwd error");
        }
    
        printf("%s
    ", buffer);
    }
    
    //分析命令所带的参数(含进程名本身)
    void split_cmd(Job* job, char* arguments, int* bg)
    {
        char** args = (char**)calloc(MAX_COMMAND_LEN, sizeof(char*));
        assert( args != NULL);
    
        char* cmd = strtok(arguments, " "); //1. 先取出命令名本身
    
        args[0] = (char*)calloc(strlen(cmd) + 1,  sizeof(char)); //命令本身
        strcpy(args[0], cmd);
    
        Redirection* rs[5]; //一条命令中重定向的符号不会太多。为简单起见,假设为5个。
        int redirect_num = 0;
    
        int i = 1;
        char* s = NULL;
        //2. 剩余的为参数部分
        *bg = FOREGROUND;
        while((s = strtok(NULL, " ")) != NULL){  //将参数分隔出来
            if(!strcmp(s, "&")){
                //设置后台进程标志
                *bg = BACKGROUND;            
                continue;
            }
    
            if(!strcmp(s, "<")){ //如果参数<
                //格式:cat < s.txt
                char* file = strtok(NULL, " "); //重定向“<”后面的为文件名
                if(file == NULL){
                    continue;
                }else{
                    //打开要重定向到的目标文件,因为后面要用dup2来完成重定向
                    int fd = open(file, O_RDONLY); //输入重定向,只需以只读打开
                    rs[redirect_num++] =  create_redirect(fd, RedirectRead);
                }
    
                continue;
            };
    
            if(!strcmp(s, ">")){
                //格式:cat > s.txt
                char* file = strtok(NULL, " "); //重定向“>”后面的为文件名
                if(file == NULL){
                    continue;
                }else{
                    //打开要重定向到的目标文件,因为后面要用dup2来完成重定向
                    //输出重定向,需以可写方式打开
                    int fd = open(file, O_WRONLY | O_CREAT | O_TRUNC, 0777); 
                    rs[redirect_num++] =  create_redirect(fd, RedirectWrite);
                }
    
                continue;
            }
    
            if(!strcmp(s, ">>")){
                //格式:cat >> s.txt
                char* file = strtok(NULL, " "); //重定向“>>”后面的为文件名
                if(file == NULL){
                    continue;
                }else{
                    int fd = open(file, O_WRONLY | O_CREAT | O_APPEND, 0777); 
                    rs[redirect_num++] =  create_redirect(fd, RedirectAppend);
                }
                continue;
            }
    
            args[i] = (char*)calloc(strlen(s)+1, sizeof(char));
            strcpy(args[i++], s);
        }
    
        //根据args创建一个Program
        Program* prog = create_program(args);
        
        int k = 0;
        for(; k < redirect_num; k++){
            add_redirection(prog, rs[k]);//将所有重定向信息放入prog中
            destroy_redirect(rs[k]);
        }
        
        add_program(job, prog);
    
        int j = 0;
        for(j=0; j < i; j++){
            free(args[j]);
        }
    
        free(args);
    }
    
    //多条命令的解析
    void parse_cmd(Job* job, char* line, int* bg)
    {
        char buff [MAX_COMMAND_LEN]={0};
    
        //以“;”号分隔多条命令
        char*  pos = line; 
        char*  start = line;
        int count = 0;
        while( start < (line + strlen(line)) ){  //将参数分隔出来
            memset(buff, 0, sizeof(buff));
            if((pos = strchr(pos, ';')) == NULL)
            {
                pos = line + strlen(line);      
            }
    
            count = pos-start;
            if(count > 0 ){
                 memcpy(buff, start, count);
                 split_cmd(job, buff, bg);
            }
            start = ++pos;
        }
    }
    
    //执行命令
    void execute_cmd(Job* job, int bg)
    {
        int i = 0;
        for(i=0; i<job->progs_num; i++)
        {
            if(!strcmp(job->progs[i].args[0], "cd")){             //cd命令
                 cd_fun(&job->progs[i]);
            }else if(!strcmp(job->progs[i].args[0], "pwd")){      //pwd命令
                 pwd_fun(&job->progs[i]);
            }else if(!strcmp(job->progs[i].args[0], "exit")){     //exit命令
                 exit(0);
            }else if(!strcmp(job->progs[i].args[0], "env")){      //env命令
                 env_fun();
            }else if(!strcmp(job->progs[i].args[0], "export")){   //export命令
                 export_fun(&job->progs[i]);
            }else if(!strcmp(job->progs[i].args[0], "echo")){     //echo命令
                 echo_fun(&job->progs[i]);
            }else{                                     //其他命令用exec函数完成
                 //创建子进程来执行其它命令
                 pid_t pid;
                 if((pid = fork()) < 0 ){
                     perror("fork error");
                 }else if(pid == 0){ //child process
                     //第1个子进程创建新的进程组,以后的子进程加入到该组当中
                     if(i==0){
                         if(setpgid(getpid(), getpid()) < 0){ //组长进程
                             perror("setpgid error!");
                         }
                         job->pgid = getpgid(getpid()); //保存组长进程ID
                     }else{
                         //其余子进程加入到新的进程组中
                         if(setpgid(getpid(), job->pgid) < 0){
                             perror("setpgid error");
                         }
                     }
    
                     //设置前台进程组
                     if(bg == FOREGROUND){
                         tcsetpgrp(0, getpgid(getpid()));
                     }
    
                     //对标准输入、标准输出和追加输出进行重定向
                     int k = 0;
                     job->progs[i].pid = getpid(); //每条命令会启动一个子进程,记录其pid
                     for(; k<job->progs[i].redirect_num; k++){
                         if(job->progs[i].redirects[k].redirect == RedirectRead){
                             //将标准输入重定向到指定的文件中
                             if(dup2(job->progs[i].redirects[k].fd, STDIN_FILENO) != STDIN_FILENO){
                                 perror("dup2 error");
                             }
                         }
                         if((job->progs[i].redirects[k].redirect == RedirectWrite) || 
                           (job->progs[i].redirects[k].redirect == RedirectAppend)){
                             //将标准输出重定向到指定的文件中
                             if(dup2(job->progs[i].redirects[k].fd, STDOUT_FILENO) != STDOUT_FILENO){
                                 perror("dup2 error");
                             }
                         }
                     } //end for2
    
                     //调用exec函数执行系统中的其它命令
                     if(execvp(job->progs[i].args[0], job->progs[i].args) < 0){
                         perror("execvp error");
                         exit(1); //子进程退出
                     }
                 }else{ //parent process
                     if(i == 0){ //与子进程执行相同的逻辑,以确保不会因进程调度而出现错误
                         //由minishell启动的所有子进程默认放到一个新的进程组,组长进程为
                         //第1个子进程
                         if ((setpgid(pid, pid)) < 0) {
                             perror("setpgid error");
                         }
                         job->pgid = pid;
                     }else{
                         //其余子进程加入到新的进程组中去
                         if((setpgid(pid, job->pgid)) < 0){
                             perror("setpgid error");
                         }
                     }
                     
                     if(bg == FOREGROUND){
                         tcsetpgrp(0, job->pgid);
                         //等待子进程组结束,含曾被暂停过的(阻塞)
                         waitpid(-job->pgid, NULL, WUNTRACED);
                     }
    
                     //后台进程
                     if(bg == BACKGROUND){
                         waitpid(-job->pgid, NULL, WNOHANG); //非阻塞
                     }
                 } //end fork
            }
        }  //end for1
    }
    
    int main(int argc, char* argv[])
    {
        //将mshell本身设置成一个进程组
        setpgid(getpid(), getpid());
        char buffer[MAX_COMMAND_LEN];
        memset(buffer, 0, MAX_COMMAND_LEN);
    
        ssize_t size = strlen(prompt) * sizeof(char);
        write(STDOUT_FILENO, prompt, size);
    
        ssize_t len = 0;
        int bg;   //设置前台和后台进程组标志
    
        while(1){
            len = read(STDIN_FILENO, buffer, MAX_COMMAND_LEN);
            buffer[len -1] = 0;  //以NULL结尾
    
            if(strlen(buffer) > 0){
                Job* job = create_job(buffer);
                
                //解析命令
                parse_cmd(job, buffer, &bg);
                //执行命令
                execute_cmd(job, bg);
    
                destroy_job(job);
            }
    
            write(STDOUT_FILENO, prompt, size);
            memset(buffer, 0, MAX_COMMAND_LEN);
        }
    
        return 0;
    }
  • 相关阅读:
    mysql auto_increment自增初始值与步长
    mysql配置的一些问题以及命令的说明
    数据分析-数据透视表
    线程进程
    tkinter学习-- 九、三种事件绑定方式总结
    tkinter学习-- 八、事件event
    tkinter学习-- 六、Radiobutton(单选按钮)与checkbutton(复选按钮)
    tkinter学习-- 四、控件Text
    在spring Boot中使用swagger-bootstrap-ui
    js 数据比较
  • 原文地址:https://www.cnblogs.com/5iedu/p/6359657.html
Copyright © 2011-2022 走看看