Linux系统编程:执行外部程序

Linux技术与实践这门课好久没去上了,而期末已然开始张牙舞抓,于是最近不得不开始预习。

昨夜睡前吃太饱,加之近来心事颇多,以及这不合常理的冬夜里的飞蚊侵扰,我最终没能安稳入睡,于是下床学习。打开老师的C语言源代码,看起来一半懂一半懵。关于fork和execvp这部分十分陌生,在各种博客上潜心学习之后,终于有所突破,在此做下记录。

首先关于fork()的知识来自于jason的专栏,关于执行外部程序的知识来自于Jollen’s Blog

fork函数通过系统调用创建一个与原进程几乎完全一样的子进程,并且父进程的值会被复制到子进程中。由于fork会把进程当前的情况拷贝一份,因此父进程中已经执行的部分不会在子进程中重复执行。

fork一次调用,二次返回(突然想起JAVA的一次编写,到处调试)。在父进程中,它会返回子进程的pid,而在子进程中则返回0,这也是判断当前进程是父是子的依据。用jason的话来说,这个过程相当于建立链表,父进程的fork返回值指向子进程的pid,而子进程由于没有子进程,因此fork返回值为0。

当然,如果fork出错,会返回负值。

execvp函数的原型是:

int execvp(const char *file, char * const argv[]);

它会从$PATH中搜索符合file的文件并执行,同时用argv的内容作为参数。

例如:

#include <iostream>

int main() {
    char *argv[] = {"ls", "-l", "/etc/passwd", 0};
    execvp("ls", argv);
}

就会执行

ls -l /etc/passwd

execvp在执行失败的时候会返回-1;如果执行成功,则不会返回,而是调用外部程序(例如“ls”)取代原进程的执行空间。因此如果想要通过execvp调用外部程序,最好不要在父进程里调用,而是通过fork一个子进程,让外部程序取代子进程而运行。

接下来贴上老师的代码(修改了一点):

/** 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>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

#define MAXARGS        20                    /* cmdline args    */
#define ARGLEN        100                   /* token length    */

void execute( char *arglist[] );
char *makestring( char *buf );

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;
}

void 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;

    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*/
}

这里需要说明一下,程序开头的注释说明这是版本2,所谓的版本1其实就是在execute函数里直接调用execvp系统调用,这样做的坏处,之前就说过了。

从main函数里我们看出这个程序的目的是不断地读取用户输入的外部命令并执行,因此父进程是绝对不能被系统调用取代的。