模拟实现Unix/Linux外壳

97 篇文章 7 订阅

模拟实现Unix/Linux外壳

注:本文中的程序代码来自《深入理解计算机系统》一书,非本人原创。

Unix/Linux中一般工作在文本界面,也就是一个外壳之下,外壳也是一个程序,那么它是怎么实现的呢?本文带你实现一个简单的shell程序,Let’s go.

所谓外壳,实际上就是执行一系列的读/求值(read/evaluate)步骤,然后终止。读步骤取来自用户的一个命令行。求职步骤解析命令行,并代表用户运行程序。

不多说别的,先上代码,大体流程见代码之下的说明。


main.c

#include "csapp.h"
#define MAXARGS 128

/* Function prototypes */
void eval(char *cmdline);
int parseline(char *buf, char **argv);
int builtin_command(char **argc);

int main(){

    char cmdline[MAXLINE];

    while(1){
        /* Read */
        printf("> ");
        fgets(cmdline,MAXLINE,stdin);
        if(feof(stdin)){
            exit(0);
        }

        /* Evaluate */
        eval(cmdline);
    }
    exit(0);
}

/* eval - Evaluate a command line */
void eval(char *cmdline){

    char *argv[MAXARGS];    /* Argument list execve() */
    char buf[MAXLINE];
    int bg;
    pid_t pid;

    strcpy(buf,cmdline);
    bg = parseline(buf,argv);
    /* Ignore empty lines */
    if(argv[0] == NULL){
        return;
    }

    if(!builtin_command(argv)){
        /* Child runs user jobs */
        if((pid = Fork()) == 0){
            if(execve(argv[0], argv, environ) < 0){
                printf("%s: Command not found.\n",argv[0]);
                exit(0);
            }
        }

        /* Parent waits for foreground job to terminate */
        if(!bg){
            int status;
            if(waitpid(pid, &status,0) < 0){
                unix_error("waitfg: waitpid error");
            }
        }else{
            printf("%d %s",pid,cmdline);
        }
    }

    return;
}

/* If first arg is a builtin command ,run it and return true */
int builtin_command(char **argv){
    /* quit command */
    if(!strcmp(argv[0],"quit")){
        exit(0);
    }
    /* Ignore singleton */
    if(!strcmp(argv[0],"&")){
        return 1;
    }
    /* Not a builtin command */
    return 0;
}

/* parseline - Parse the command line and build the argv array */
int parseline(char *buf, char **argv){

    char *delim;                /* Points to first space delimiter */
    int argc;                       /* Number of args */
    int bg;                         /* Background jobs? */

    buf[strlen(buf) - 1] = ' ';     /* Replace trailing '\n' with space */
    /*Ignore leading spaces */
    while(*buf && (*buf == ' ')){   
        buf ++;
    }

    /* Build the argv list */

    argc = 0;
    while((delim = strchr(buf,' '))){
        argv[argc++] = buf;
        *delim = '\0';
        buf = delim + 1;
        /* Ignore spaces */
        while(*buf && (*buf == ' ')){
            buf ++;
        }
    }

    argv[argc] = NULL;

    /* Ignore blank line */
    if(argc == 0){
        return 1;
    } 

    /* Should the job run in the background? */
    if((bg = (*argv[argc - 1] == '&')) != 0){
        argv[--argc] = NULL;
    }

    return bg;
}

说明:

1.外壳打印一个命令行提示符,等待用户在stdin上输入命令行,然后对这个命令行求值。

2.eval函数的首要任务是调用parseline函数,这个函数解析了以空格分隔的命令行参数,并构造最终会传递给execve的argv向量。第一个参数被假设为要么是一个内置的外壳命令名,马上就会解释这个命令,要么是一个可执行目标文件,会在一个新的子进程的上下文中加载并运行这个文件。

3.如果最后一个参数是“&”字符,那么parseline返回1,表示应该在后台执行该程序(外壳不会等待他完成),否则他返回0,表示应该在前台执行这个程序(外壳会等待他完成)。

4.在解析了命令行之后,eval函数调用builtin_command函数,该函数检查第一个命令行参数是否是一个内置的外壳命令。如果是,它就立即解释这个命令,并返回1,否则返回0.

5.简单的外壳只有一个内置命令——quit命令,该命令会终止外壳。实际使用的外壳有大量的命令,如pwd,jobs,fg等。

6.如果builtin_command返回0,那么外壳创建一个子进程,并在子进程中执行所请求的程序。如果用户要求在后台运行该程序,那么外壳返回到循环的顶部,等待下一个命令行。否则,外壳使用waitpid函数等待作业终止。当作业终止时,外壳就开始下一轮迭代。

7.注意,这个简单的shell是有缺陷的,因为它并不回收它的后台子进程。

需要注意的是,在上面的程序中有csapp.h这个头文件,下面给两个地址可以找到csapp.h以及它的实现csapp.c:

csapp.h : http://csapp.cs.cmu.edu/2e/ics2/code/include/csapp.h

caspp.c : http://csapp.cs.cmu.edu/2e/ics2/code/src/csapp.c

如果某天不幸地资源不在了,可以到我个人的资源页面去下载:

http://download.csdn.net/detail/test1280/9629512

使用头文件的时候需要注意,因为csapp.c中有关于线程的头文件,在用gcc的时候最后要加上-lpthread 如 # gcc -o Ex Ex.c -lpthread,才能顺利通过。

下面给出两个测试的小代码:

code one:


hello.c

#include <stdio.h>
#include <stdlib.h>

int main(int argc,char **argv,char **envp){

    int i;
    for(i = 0 ; i < argc ; i++){
        printf("argv[%2d] is %s\n",i,argv[i]);
    }

    int n = 0;
    while(NULL != envp[n]){
        printf("envp[%2d] is %s\n",n,envp[n]);
        n++;
    }

    printf("this is hello.c , hello.c just a simple test!\n");

    return 0;
}

hello.c主要的功能是输出参数列表argv以及环境变量数组envp的内容。

这里写图片描述

code two:


longJob.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc,char **argv,char **envp){

    int i;

    i = atoi(argv[1]);

    while(i>0){
        printf("now i is %d\n",i);
        sleep(1);
        i--;
    }

    return 0;
}

longJob.c就是单纯的耗费时间,模拟一个运算量很大的程序。

这里写图片描述

很明显地看出,在第二次执行 ./longJob 6 &的时候,shell没有等待子进程,而是直接到了主进程中,通过第二个红箭头处指的”>”可以辨别。

第三个红箭头处,并不是没有了提示符,而是提示符早就输出在了stdout中,而子进程与父进程的文件描述符表一样,当他们都向stdout输出时,会混在一起,但这个时候父进程确实已经输出了“>”,只是是在子进程输出之前,看起来不是那么明显。

以上模拟shell的那部分代码以及相关解释来自《深入理解计算机系统》一书,下面的两端代码是本人所写,存在很多不足以及潜在的bug。

上述内容如有错误,敬请指出,谢谢!

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值