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

shell由下面的循环组成:

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

我们可以实际用一下shell:

jiange@jiange-Inspiron:~/cpp$ ls
override  override.cpp  test  test1.cpp  test.cpp  test.o  test.sh
jiange@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就能在子进程运行的时候屏蔽中断信号。

Logo

更多推荐