进程控制(2)

进程控制(1)中,介绍了创建子进程fork和vfork函数,其实在创建一个进程之后,子进程往往会调用一个exec函数去执行另一个程序。当调用一个exec函数之后,该进程执行的程序完全替换为新程序,而新程序从main函数开始执行。exec函数并不创建新的进程,前后进程ID不变,只是用磁盘上的一个新程序替换了当前进程的正文段、数据段、堆段和栈段。

exec函数

#include <unistd.h>
int execl(const char *pathname, const char *arg0, ... /* (char *)0 */);
int execv(const char *pathname, char *const argv[]);
int execle(const char *pathname, const char *arg0, ... /* (char *)0, char *const envp[] */);
int execve(const char *pathname, char *const argv[], char *const envp[]);
int execlp(const char *filename, const char *arg0, ... /* (char *)0 */);
int execvp(cosnt char *filename, char *const argv[]);
6个函数的返回值:若出错则返回-1,若成功则没有返回值,其中只有execve函数属于内核的系统调用,其他的只是库函数
  • 第一个区别

    前四个取路径名做为参数,后两个取文件名做为参数,当指定filename作为参数时:
    1、如果文件名中不包含“/”则从PATH环境变量中搜寻可执行文件。
    2、包含“/”就当做路径名。
    PATH 环境变量包含一张目录表(称为路径前缀),目录之间用冒号(:)分割。例如,
    PATH=/bin:/usr/bin:/usr/local/bin:.
    指定了四个目录项,最后一个路径前缀是当前目录。(零长前缀也表示当前目录,在 value 的开始处可用: 表示,在行中间则要用:: 表示,在行尾则以:表示。)
    如果execlp或execv使用路径前缀找到了一个可执行文件,但是该文件不是连接编辑程序产生的可执行代码文件,则当做shell脚本处理。

  • 第二个区别
    前两个和最后一个函数中都包括“ l ”这个字母,而另三个都包括“ v”, ” l “代表 list即表,而” v “代表 vector即矢量,也就是前三个函数的参数都是以list的形式给出的,要求将新程序的每个命令行参数都说明为一个单独的参数,但最后要加一个空指针,如果用常数0来表示空指针,则必须将它强行转换成字符指针,否则有可能出错。而后三个应该先构造一个指向各参数的指针数组,然后将该数组地址作为这几个函数的参数。

  • 第三个区别
    与向新程序传递环境表有关,以e结尾的函数,可以向函数传递一个指向环境字符串指针数组的指针。即自个定义各个环境变量,而其它四个则使用调用进程中的environ变脸为新程序复制现有的环境。

这六个函数中只有execve函数属于内核的系统调用,其他的只是库函数,这几个函数之间的关系可以表示为下图:
这里写图片描述

下面给出几个具体的例子来说明这几个函数的用法:
1、使用execl和execv来传递参数

#include "apue.h"
int main(int argc, char *argv[])
{
  int i;
  for (i = 1; i < argc; i++) {
    printf("arg%d: %s\n", i, argv[i]);
  }
  exit(0);
}

生成上面程序的可执行文件后,在demo中用使用execl和execv。


#include"apue.h"
#include <sys/wait.h>

char demoPath[] = "/home/xucheng/xc/test/print";
char *myargs[] = {"print", "xucheng", "ha", "ha", "ha" };

int
main(void)
{
  pid_t pid;

  if ((pid = fork()) < 0) {
    err_sys("fork error");
  } else if (pid == 0) {
    printf("Transmits by vector\n");
    if (execv(demoPath, myargs) < 0) {
      err_sys("execv error");
    }
  }
  if(waitpid(pid,NULL,0)<0)
      err_sys("wait error");

  if ((pid = fork()) < 0) {
    err_sys("fork error");
  } else if (pid == 0) {
    printf("Transmits by list\n");
    if (execl(demoPath, "another",myargs[0], myargs[1],
              myargs[2], myargs[3], myargs[4], (char *)0) < 0) {
      err_sys("execv error");
    }
  }
  if(waitpid(pid,NULL,0)<0)
      err_sys("wait error");

  exit(0);
}

运行的结果为:

xucheng@xucheng-Inspiron-5520:~/xc/test$ gcc demo.c -o demo
xucheng@xucheng-Inspiron-5520:~/xc/test$ ./demo 
Transmits by vector
arg1: xucheng
arg2: ha
arg3: ha
arg4: ha
Transmits by list
arg1: print
arg2: xucheng
arg3: ha
arg4: ha
arg5: ha

需要特别说明一点,在调用 execl、execle 和 execlp 函数传递参数表,在最后一个命令行参数之后跟着一个空指针。如果要用常量 0 来表示空指针,则必须要将它转换为一个字符指针。

2、使用带有环境变量的exec函数
以e结尾的3个函数可以传递一个指向环境字符串指针数组的指针。
下面这个函数会打印出所有的环境字符串。

#include"apue.h"
int main(void)
{
  extern char **environ;
  char **ptr;

  for (ptr = environ; *ptr != NULL; ptr++) {
    printf("%s\n", *ptr);
  }
  exit(0);
}

编译运行后,可以看到:

xucheng@xucheng-Inspiron-5520:~/xc/test$ ./print 
XDG_VTNR=7
LC_PAPER=zh_CN.UTF-8
LC_ADDRESS=zh_CN.UTF-8
XDG_SESSION_ID=c2
XDG_GREETER_DATA_DIR=/var/lib/lightdm-data/xucheng
LC_MONETARY=zh_CN.UTF-8
......

下面在demo中使用execv函数和execve函数,调用上面生成的可执行文件,比较打印的环境变量列表有何区别。

#include"apue.h"
#include<sys/wait.h>

char demoPath[] = "/home/xucheng/xc/test/print";
char *myEnvs[] = { "env1=hhhhhh", "env2=aaaaaaa" };

int main(void)
{
  pid_t pid;

  if (putenv("newenv=add in parent process") < 0) {
    err_sys("putenv error");
  }

  if ((pid = fork()) < 0) {
      err_sys("fork error");
  } else if (pid == 0) {
    printf("print_envir by execv\n");
    if (execv(demoPath, NULL) < 0) {
    err_sys("execv error");
    }
  }


  if(waitpid(pid,NULL,0)<0)
      err_sys("wait error");

  if ((pid = fork()) < 0) {
    err_sys("fork error");
  } else if (pid == 0) {
    printf("\n print_envir by execve\n");
    if (execve(demoPath, NULL, myEnvs) < 0) {
      err_sys("execv error");
    }
  }
  if(waitpid(pid,NULL,0)<0)
    err_sys("wait error");

  exit(0);
}

运行结果如下:

xucheng@xucheng-Inspiron-5520:~/xc/test$ ./demo 
print_envir by execv
XDG_VTNR=7
LC_PAPER=zh_CN.UTF-8
LC_ADDRESS=zh_CN.UTF-8
......
XAUTHORITY=/home/xucheng/.Xauthority
OLDPWD=/home/xucheng
_=./demo
newenv=add in parent process

 print_envir by execve
env1=hhhhhh
env2=aaaaaaa

从上面的运行结果,我们看到调用 execv 函数时父进程会将其设置的环境变量也传递给了子进程。而调用execve 函数时,子进程的环境变量列表只有 execve 函数传递的 列表。

3、解释器文件
exec函数也可以通过解释器文件来执行新的程序,解释器文件其实是一种文本文件,形式如下:

#! pathname[optional-argument]

内核使调用exec函数的进程实际执行的不是该解释器文件,而是在该解释器文件中第一行pathname中所指定的文件。如下:

#include "apue.h"
#include <sys/wait.h>

int
main(void)
{
    pid_t   pid;

    if ((pid = fork()) < 0) {
        err_sys("fork error");
    } else if (pid == 0) {          /* child */
        if (execl("/home/xucheng/xc/test/temp",
                  "temp", "myarg1", "MY ARG2", (char *)0) < 0)
            err_sys("execl error");
    }
    if (waitpid(pid, NULL, 0) < 0)  /* parent */
        err_sys("waitpid error");
    exit(0);
}

运行之前要更该解释器文件的权限,运行之后,结果为:

xucheng@xucheng-Inspiron-5520:~/xc/test$ cat temp 
#! /home/xucheng/xc/test/print foo
xucheng@xucheng-Inspiron-5520:~/xc/test$ ./demo 
arg0: /home/xucheng/xc/test/print
arg1: foo
arg2: /home/xucheng/xc/test/temp
arg3: myarg1
arg4: MY ARG2

其中argv[0]是解释器文件的pathname,argv[1]是解释器文件中的可选参数。

猜你喜欢

转载自blog.csdn.net/xc13212777631/article/details/81026747