【Unix/Linux编程实践】shell如何运行程序—编写命令解析器sh

来源:互联网 发布:淘宝pc端 编辑:程序博客网 时间:2024/06/04 18:59

1.shell是如何运行程序的?

shell由下面的循环组成:

while (!end_of_input)    getcommand    execute command    wait for command to finish

我们可以实际用一下shell:

jiange@jiange-Inspiron:~/cpp$ lsoverride  override.cpp  test  test1.cpp  test.cpp  test.o  test.shjiange@jiange-Inspiron:~/cpp$ ps  PID TTY          TIME CMD 2457 pts/12   00:00:00 bash 3780 pts/12   00:00:00 ps

上述过程为:

sh读入ls,新建一个进程ls并执行,此时sh则等待ls的退出,ls退出后回到sh,sh继续读入ps……

因此,要编写sh,我们需要知道如何运行一个程序,创建,终止一个进程,以及一个进程如何等待另一个进程的结束。

2.如何执行一个程序

答案:

execvp(progname, arglist)

它的运作流程:

  1. 程序调用execvp;
  2. 内核从磁盘将程序载入;
  3. 内核将arglist复制到进程;
  4. 内核调用main(argc, argv)。

运行ls的例子:

/* exec1.c - shows how easy it is for a program to run a program */main(){    char    *arglist[3];    arglist[0] = "ls";    arglist[1] = "-l";    arglist[2] = 0 ;    printf("* * * About to exec ls -l\n");    execvp( "ls" , arglist );    printf("* * * ls is done. bye\n");}

执行结果:

$ ./exec1 * * * About to exec ls -l总用量 40-rwxrwxr-x 1 jiange jiange 7370 127 22:59 exec1-rw-rw-r-- 1 jiange jiange  262 1014  2007 exec1.c-rw-rw-r-- 1 jiange jiange  406 1014  2007 forkdemo1.c-rw-rw-r-- 1 jiange jiange  315 1014  2007 forkdemo2.c-rw-rw-r-- 1 jiange jiange  503 1014  2007 forkdemo3.c-rw-rw-r-- 1 jiange jiange 1395 1014  2007 psh1.c-rw-rw-r-- 1 jiange jiange 1766 1014  2007 psh2.c-rw-rw-r-- 1 jiange jiange  784 1014  2007 waitdemo1.c-rw-rw-r-- 1 jiange jiange 1077 1014  2007 waitdemo2.c

一切好像正常运行,不过,为什么printf(“* * * ls is done. bye\n”);这一句没有执行呢?!

这就要理解下execvp的作用了:execvp将程序从磁盘载入当前进程中,替代当前进程的代码和数据,所以执行完execvp之后,进程exec1完全被替换掉了,自然不会执行后续的打印工作。

2.带提示符的shell

这个版本提示用户输入程序名和参数,然后运行。

/*  prompting shell version 1 *      Prompts for the command and its arguments. *      Builds the argument vector for the call to execvp. *      Uses execvp(), and never returns. */#include    <stdio.h>#include    <signal.h>#include    <string.h>#define MAXARGS     20              /* cmdline args */#define ARGLEN      100             /* token length */int main(){    char    *arglist[MAXARGS+1];        /* an array of ptrs */    int numargs;            /* index into array */    char    argbuf[ARGLEN];         /* read stuff here  */    char    *makestring();          /* malloc etc       */    numargs = 0;    while ( numargs < MAXARGS )    {                           printf("Arg[%d]? ", numargs);        if ( fgets(argbuf, ARGLEN, stdin) && *argbuf != '\n' )            arglist[numargs++] = makestring(argbuf);        else        {            if ( numargs > 0 ){     /* any args?    */                arglist[numargs]=NULL;  /* close list   */                execute( arglist ); /* do it    */                numargs = 0;        /* and reset    */            }        }    }    return 0;}int execute( char *arglist[] )/* *  use execvp to do it */{    execvp(arglist[0], arglist);        /* do it */    perror("execvp failed");    exit(1);}char * makestring( char *buf )/* * trim off newline and create storage for the string */{    char    *cp, *malloc();    buf[strlen(buf)-1] = '\0';      /* trim newline */    cp = malloc( strlen(buf)+1 );       /* get memory   */    if ( cp == NULL ){          /* or die   */        fprintf(stderr,"no memory\n");        exit(1);    }    strcpy(cp, buf);        /* copy chars   */    return cp;          /* return ptr   */}

上面使用makestring函数来动态分配空间给字符串参数。

运行上面程序,我们就可以传递参数进去了。

不过一个比较严重的缺陷:运行完ls之后,整个程序就退出了,而我们期待的是,运行完ls后返回到我们的shell中,shell可以继续接收命令输入并执行。

在开头我们讲到,sh读入ls,新建一个进程ls并执行,此时sh则等待ls的退出,ls退出后回到sh。

也就是说,sh并不直接执行ls命令,而是新建了一个进程来执行它,那么问题来了:如何建立新的进程?

答案:使用fork();

我们可以使用fork来创建子进程,然后新的进程调用execvp来执行用户命令。

那么,父进程sh如何等待子进程ls的结束呢?

答案:使用 pid=wait(&status);

wait阻塞调用它的进程直到子进程结束,之后wait取得子进程结束时传给exit的值(通过status),并返回子进程的PID。

3.可以持续接收用户命令的shell

经过以上分析,我们可以设计新的shell:

获取命令-> 用fork创建新进程 ->父进程wait ->子进程exec运行新程序->在新程序的main中运行->新程序exit -> 父进程得到子进程状态->获取命令

/**  prompting shell version 2 ** **     Solves the `one-shot' problem of version 1 **         Uses execvp(), but fork()s first so that the **         shell waits around to perform another command **     New problem: shell catches signals.  Run vi, press ^c. **/#include    <stdio.h>#include    <signal.h>#define MAXARGS     20              /* cmdline args */#define ARGLEN      100             /* token length */main(){    char    *arglist[MAXARGS+1];        /* an array of ptrs */    int numargs;            /* index into array */    char    argbuf[ARGLEN];         /* read stuff here  */    char    *makestring();          /* malloc etc       */    numargs = 0;    while ( numargs < MAXARGS )    {                           printf("Arg[%d]? ", numargs);        if ( fgets(argbuf, ARGLEN, stdin) && *argbuf != '\n' )            arglist[numargs++] = makestring(argbuf);        else        {            if ( numargs > 0 ){     /* any args?    */                arglist[numargs]=NULL;  /* close list   */                execute( arglist ); /* do it    */                numargs = 0;        /* and reset    */            }        }    }    return 0;}execute( char *arglist[] )/* *  use fork and execvp and wait to do it */{    int pid,exitstatus;             /* of child */    pid = fork();                   /* make new process */    switch( pid ){        case -1:                perror("fork failed");            exit(1);        case 0:            execvp(arglist[0], arglist);        /* do it */            perror("execvp failed");            exit(1);        default:            while( wait(&exitstatus) != pid )                ;            printf("child exited with status %d,%d\n",                    exitstatus>>8, exitstatus&0377);    }}char *makestring( char *buf )/* * trim off newline and create storage for the string */{    char    *cp, *malloc();    buf[strlen(buf)-1] = '\0';      /* trim newline */    cp = malloc( strlen(buf)+1 );       /* get memory   */    if ( cp == NULL ){          /* or die   */        fprintf(stderr,"no memory\n");        exit(1);    }    strcpy(cp, buf);        /* copy chars   */    return cp;          /* return ptr   */}

关于格式控制方面,比如如何在一行中输入参数,如何用exit退出shell等等,在这里我们不做讨论。

在运行的时候,我们发现一个严重的错误:

假如我们正在运行一个子进程,这时我们键入Ctrl-C键,我们预期的应该是子进程终止,而shell仍继续运行,但事实是shell也终止了,因为键盘信号SIGINT发给了所有连接的进程!

为使得有子进程在运行时,shell不会因为中断信号而跟着终止,我觉得可以这样:只要有子进程运行,shell就将对SIGINT信号的响应设为ignore,子进程结束之后,则设置为默认方式。这样子shell就能在子进程运行的时候屏蔽中断信号。

0 0