欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

EXEC函数族

程序员文章站 2022-07-15 09:26:26
...

fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支,通过if语句对i进行判断),子进程往往要调用一种exec函数以执行另一个程序(不再回来了,即剩下没有执行完的程序等数据都被清除了)。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程(其实启动例程才是真正的程序入口地址,启动例程是调用main函数的哪个函数,先于main函数执行,启动例程由C和汇编混合编写)开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变

将当前进程的.text、.data替换为所要加载的程序的.text、.data,然后让进程从新的.text第一条指令开始执行,但进程ID不变,换核不换壳

Linux使用exec函数族来执行新的程序,以新的子进程来代替原有的进程。exec函数族执行成功后不会返回(没有返回值),因为调用进程的实体,包括代码段,数据段和堆栈等都已经被新的内容所替换(因此返回值也没有意义),只是进程ID等一些表面上的信息仍然保持原样,看上去还是旧的躯壳,却已经注入了新的灵魂。只有当exec函数族执行失败了,才会返回-1,此时从exec函数族的调用点继续向下执行。所以通常我们直接在exec函数调用后直接调用perror()exit(),无需if判断。

综上,一个进程想要再不放弃原程序的情况下,同时去执行另一个程序,可以fork一个子进程,让子进程调用exec函数族去执行另一个程序,而自己仍保持源程序的执行。

 

其实有六种以exec开头的函数,统称exec函数族:

#include <unistd.h>

extern char **environ;  //要用到这个变量,需要申明  当然也可以自己重新定义

 

int execl(const char *path, const char *arg, ...);

int execlp(const char *file, const char *arg, ...); //const char * 指针所指的内容为常量

int execv(const char *path, char *const argv[]); //char *const 指针为常量,其内容可变化

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

int execle(const char *path, const char *arg, ..., char *const envp[]);

int execve(const char *path, char *const argv[], char *const envp[]);

 

l (list) 命令行参数列表   p (path)搜索file时使用path环境变量

v (vector)使用命令行参数数组,即argv   e (environment):使用环境变量数组,不使用进程原有的环境变量,设置新加载程序运行的环境变量,即environ,如果没有e(即不指定e这个参数),则不意味着不适用环境变量,而是把默认的环境变量(当前进程的环境变量environ)隐式传给被执行的应用程序;带上e,则用指定的环境变量去替代默认的那些环境变量,因此需要自己进行定义。

综上,l与v其实相同的参数,只是方式不一样。以上6个函数,由于有了lve参数,使得函数的形参个数不定,因此属于变参函数(形参个数或类型不确定的函数)。main函数也是一个变参函数。

使用excel函数族,既可以重新加载一个用户自定义的程序,也可以加载一个系统的可执行程序,如各种命令文件。

int execlp(const char *file, const char *arg, ...);

加载一个进程(新的程序),借助PATH环境变量(不用提供完整路径,只需要提供程序文件名即可,就会自动在PATH中去查找)。      

参数1:要加载的程序的名字。该函数需要配合PATH环境变量来使用,当PATH中所有目录搜索后没有参数1则出错返回-1。该函数通常用来调用系统程序。如:ls、date、cp、cat等命令。后面的参数均为命令行参数,argv[0]  argv[1]  注意,最后一个参数必须为NULL。没有e,则默认将进程的环境变量传入到第一个参数所指定的程序中。如果有e,则先定义好environ变量,然后直接将erviron作为最后一个参数传入即可。从而,加载出一个新的进程。其它函数功能都一样,只是使用方法有些不同。注意,excelp函数是使用的进程本身的环境变量。

 

int execl(const char *path, const char *arg, ...);

加载一个进程(新的程序),通过“路径+程序名”来加载。

对比execlp,如加载"ls"命令带有-l-F参数

execlp("ls", "ls", "-l", "-F", NULL);          使用程序名在PATH中搜索。

execl("/bin/ls", "ls", "-l", "-F", NULL);    使用参数1给出的绝对路径搜索。

上述两个函数被调用后,相当于进程重新被加载,等效执行了:ls -l -F 命令。ls为argv[0],以此类推。在所有函数中,argv[0]的信息出错(只要形式正确,为一个字符串)不会影响程序的正常执行,依然可以获得正确结果,因为实际要传入到程序内部的参数是argv[1]及其以后的所有参数,至于argv[0]其实相当于是执行程序本身,即函数第一个参数。

 

int execle(const char *path, const char *arg, ..., char *const envp[]);

加载一个进程,使用自定义环境变量env

//代码举例

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

int main(void)
{
    pid_t pid;
    pid = fork();
    if (pid == -1 ) {
        perror("fork");
        exit(1);
    } else if (pid > 0) {
            printf("I'm parent pid = %d, parentID = %d\n", getpid(), getppid());
    } else if (pid == 0) {
        sleep(3);   //子进程睡了3秒,因此会导致父进程先结束
        printf("i am child\n");
        execl("/bin/ls", "ls", "-l",  NULL);  //此时,子进程重新加载ls程序,后面代码不再执行(不再回来了)
        perror("exec");
        exit(1);
    }

    printf("-------finish...%d\n", getpid());

    return 0;
}

[[email protected] exec]# ./fork

I'm parent pid = 24375, parentID = 17448

-------finish...24375

[[email protected] exec]# i am child   //由于父进程先结束

total 89

-rwxrwxrwx. 1 root root  9901 Oct 13 09:37 execl

-rwxrwxrwx. 1 root root   285 Oct 13 09:37 execl.c

-rwxrwxrwx. 1 root root 10185 Oct 13 09:37 execlp

-rwxrwxrwx. 1 root root   474 Oct 13 09:37 execlp.c

-rwxrwxrwx. 1 root root  9805 Oct 13 09:37 exec_ps

 

//代码举例

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

extern char **envrion;

int main(void)
{
    char *argvv[] = {"ls", "-l", NULL};

    //execl("/bin/ls", "ls", "-l", NULL);   
    //execlp("ls", "ls", "-l", NULL);
    execv("/bin/ls", argvv);     //这三行的效果是一样的,后面程序不再执行

    perror("exec"); 
    exit(1);

    return 0;
}

//练习:将当前系统中的进程信息,打印到文件中。                                 

方法一: ps aux > out   //输出重定向到out文件中,且清空写入。out文件不存在,则其会自动创建。

方法二:利用exec函数族、dup2open函数,代码如下

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

int main(void)
{
        int fd;

        fd = open("ps.out", O_WRONLY|O_CREAT|O_TRUNC, 0664); //创建并打开一个ps.out文件,并将其截断为0。
        if(fd == -1){
                perror("open ps.out error");
                exit(1);
        }
        dup2(fd, STDOUT_FILENO);  //文件描述符重定向,将标准输出定向到ps.out文件中

        execlp("ps", "ps", "-aux", NULL);  //直接执行命令 ps -aux ,此时会输出到文件中
        perror(“execlp”);
        exit(1);
 close(fd);  //若exec成功,后面的程序不再执行了,因此关闭该文件是没有意义的。

        return 0;
}

[[email protected] exec]# ./exec_ps

[[email protected] exec]# ls

execl    execlp    exec_ps    fork    makefile  output.c  test    upper    wrapper

execl.c  execlp.c  exec_ps.c  fork.c  output    ps.out    test.c  upper.c  wrapper.c

[[email protected] exec]# ls -l ps.out

-rwxrwxrwx. 1 root root 38914 Mar 26 00:28 ps.out

分析:即使exec成功,没有通过close关闭打开的文件,但是当该进程结束后,会隐式关闭打开的文件,隐式回收各种资源。