EXEC函数族

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

[root@localhost exec]# ./fork

I'm parent pid = 24375, parentID = 17448

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

[root@localhost 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;
}

[root@localhost exec]# ./exec_ps

[root@localhost 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

[root@localhost exec]# ls -l ps.out

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

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值