进程控制 -- 进程创建(fork),进程终止(exit, _exit),进程等待(wait, waitpid),进程程序替换(exec*系列函数)

目录

1. 进程创建

1.1 fork函数

1.2 写时拷贝

1.3 fork常规用法

2. 进程终止

2.1 进程退出场景

2.2 进程常见退出方法

2.2.1 退出码

2.2.2 exit函数

2.2.3 _exit函数

2.2.4 return退出 

3. 进程等待

3.1 进程等待的必要性

3.2 进程等待的方法

3.2.1 wait方法

3.2.2 waitpid方法

3.2.3 参数status

3.2.4 阻塞与非阻塞等待

4.进程程序替换

4.1 替换原理

4.2  替换函数(exec*函数)

4.3 替换函数介绍

4.3.1 execl函数

4.3.2  execlp函数

4.3.3  execv函数

4.3.4  execvp函数

4.3.5 execvpe函数

4.3.6 execle函数

4.3.7 execve函数


1. 进程创建

1.1 fork函数

        在Linux系统中fork函数用来从一个已存在的进程中创建一个新进程,新进程为子进程,而原进程为父进程。

        使用fork函数需要包含头文件 <unistd.h> 和 <sys/types.h>。原型如下:

#include <sys/types.h>
#include <unistd.h>

pid_t fork(void);

         返回值:子进程中返回0,父进程返回子进程的pid,出错返回-1.

        进程调用fork,当控制转移到内核中的fork代码后,内核要做以下几个工作:

                (1) 分配新的内存块和内核数据结构(task_struct)给子进程。

                (2) 将父进程部分数据结构内容(代码,数据以及页表等)拷贝到子进程。

                (3) 添加子进程到系统进程列表中。

                (4) fork返回,开始调度器调度。

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main()
{
        pid_t pid;
        printf("Before: pid is %d\n", getpid());
        pid = fork();
        if (pid == -1)
        {
                perror("fork()");
                exit(1);
        }
        printf("After: pid is %d, fork return %d\n", getpid(), pid);
        sleep(1);
        return 0;
}

         fork之前只有父进程,pid为678777。fork之后有父子进程共享代码和数据,第一行是父进程执行的结果,pid为678777,获取的fork函数返回值为子进程pid:678778。第二行是子进程执行的结果,pid为678778,获取的fork函数返回值为0。

1.2 写时拷贝

        通常情况下,父子进程代码共享,父子进程不写入时,数据也是共享的,当任意一方试图写入,便会以写时拷贝的方式各自有一份副本。如下图:

        在创建子进程之后,数据段的数据就会变成只读的,在对数据段进行修改之后,操作系统发现对只读的字段进行修改,就会产生错误,这种错误导致了写时拷贝。

知识点1:
        为什么要写时拷贝?

                (1) 减少创建子进程时,变量的拷贝,从而减少创建子进程的时间。

               (2) 写时拷贝只拷贝发生修改的变量,不会拷贝全部变量,减少内存浪费。写时拷贝时以各种延时申请技术,可以提高整机内存的使用率。

1.3 fork常规用法

        (1)  一个父进程创建一个子进程,希望父子进程同时执行不同的代码段。例如:父进程等待客户端请求,生成子进程来处理请求。

        (2) 一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数。

2. 进程终止

        进程终止的本质是释放系统资源,就是释放进程申请的相关内核数据结构和对应的数据和代码。

2.1 进程退出场景

        (1) 代码运行完毕,结果正确。

        (2) 代码运行完毕,结果不正确。比如代码中要打开一个文件,打开失败直接return.

        (3)代码异常终止。比如再代码中进行除0操作或者遇到野指针导致程序崩溃。

2.2 进程常见退出方法

        main函数的返回值,通常表明程序的执行情况,反映上述两种情况,结果正确通常返回0,结果不正确通常返回非0。不同的返回值表明不同的出错原因。

        一般main函数的返回值是返回给父进程bash的。可以通过 echo $? 查看返回值。如下例子:

#include <stdio.h>

int main()
{
        FILE* fp = fopen("log.txt", "r");
        if (fp == NULL)
                return 1;
        return 0;
}

        上述函数以只读的方式打开一个不存在的文件,正常情况下肯定会出错,返回 1。通过 echo $? 查看上述程序的返回值。 

        main函数的返回值称为进程退出码。echo $? 的作用是打印最近一个程序(进程)退出时的退出码。

        常见的退出方式有以下几种:

                (1) main函数中return返回退出码退出。

                (2) 调用exit函数。

                (3)_exit

知识点1:
        函数中的return语句,当一个a函数调用一个b函数时,b函数执行完通过return返回一个整数,但是该整数是一个局部变量,是如何返回给a函数的?

                return语句返回一个整数,是先把局部变量拷贝到寄存器中,然后再把寄存器中的值返回给a函数。

知识点2:
        子进程退出时,会把进程退出码写到自己的task_struct中,父进程在子进程僵尸状态的时候读取子进程的退出码。

2.2.1 退出码

        退出码(退出状态)可以告诉我们最后一次执行的命令的状态。在命令结束后,我们可以知道命令是成功完成还是以错误结束的。退出代码为0表示执行成功。 0 以外的任何代码都被视为不成功。代码异常退出(进程收到信号),退出码无意义了。

#include <stdio.h>
#include <string.h>

int main()
{
        for (int i = 0; i < 150; i++)
        {
                printf("%d->%s\n", i, strerror(i));
        }

        return 0;
}

        通过上述代码可以查看标准C语言中常见的错误码对应的错误信息,一共134个。 

         ls -l hello.txt,查看一共不存在的hello.txt 文件,退出码就为2.

2.2.2 exit函数

        任何地方调用exit函数,表示进程结束。并返回给父进程bash子进程的退出码。

#include <unistd.h>
void exit(int status);
//参数:status 定义了进程的终⽌状态,⽗进程通过wait来获取该值

        下面代码进行演示: 

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

void fun()
{
        printf("fun begin!\n");
        exit(40);
        printf("fun end!\n");
}

int main()
{
        fun();
        printf("main!\n");

        return 0;
}

2.2.3 _exit函数

        exit是C语言提供的接口,_exit是系统调用。

        _exit和exit函数的效果一样,exit中封装了_exit,exit最后也会调用_exit,但在调用_exit之前,还做了其他工作。

        _eixt和exit的区别是:调用exit()退出进程时,会进行缓冲区的刷新。调用_exit退出进程时,不会进行缓冲区的刷新。

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

int main()
{
        printf("hello world!");
        exit(40);

        return 0;
}

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

int main()
{
        printf("hello world!");
        _exit(40);

        return 0;
}

         可以看到,调用exit()会把结果刷新到显示器上,调用_exit()不会把结果刷新到显示器上。

2.2.4 return退出 

        main函数中执行return n,等同于执行exit(n),因为调用main的运行时函数会将main的返回值当作exit的参数。

3. 进程等待

3.1 进程等待的必要性

        子进程退出时,如果父进程不管,就会成为僵尸进程,进而造成内存泄漏。

        创建子进程是为了完成父进程的一部分任务。所以父进程要获取子进程的退出信息来获取任务的完成情况。

        综上,进程等待是为了完成以下两件事,一是回收子进程的资源,二是获取子进程的退出信息。

3.2 进程等待的方法

3.2.1 wait方法

#include<sys/types.h>
#include<sys/wait.h>

pid_t wait(int* status);

//返回值:
//    成功返回被等待进程pid,失败返回-1。
//参数:
//    输出型参数,获取⼦进程退出状态,不关⼼则可以设置成为NULL

         wait函数和scanf函数一样,如果子进程没有退出,则父进程会阻塞在wait调用处。wait函数等待任一子进程退出,如果有多个子进程,其中一个子进程退出则调用wait函数。

        下列代码中使用wait函数来回收处于僵尸进程的子进程,

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
        pid_t id = fork();
        if (id == 0)
        {
                int cnt = 5;
                while(cnt)
                {
                        printf("我是子进程,pid: %d, ppid: %d\n", getpid(), getppid());
                        sleep(1);
                        cnt--;
                }
                exit(0);
        }
        sleep(10);
        pid_t rid = wait(NULL);
        if (rid > 0)
        {
                printf("wait success, rid: %d\n", rid); //rid为成功等待的子进程的pid
        }
        sleep(10);
        return 0;
}

        上述代码表示,程序启动之后子进程进行5s的循环,此时父进程处于休眠,5s循环完之后,子进程退出,但是父进程还在休眠,没有进行回收,此时子进程处于僵尸状态,父进程休眠10s后,调用wait回收子进程,父进程运行完后面的代码之后退出程序。 

3.2.2 waitpid方法

#include <sys/types.h>
#include <sys/wait.h>

pid_ t waitpid(pid_t pid, int *status, int options);
//返回值:
//    当正常返回的时候waitpid返回收集到的⼦进程的进程ID;
//    如果设置了选项WNOHANG,⽽调⽤中waitpid发现没有已退出的⼦进程可收集,则返回0;
//    如果调⽤中出错,则返回-1,这时errno会被设置成相应的值以指⽰错误所在;
//参数:
//    pid:
//    pid=-1,等待任⼀个⼦进程。与wait等效。
//    pid>0.等待其进程ID与pid相等的⼦进程。

//    status: 输出型参数 -- 获取子进程退出的退出信息
//    WIFEXITED(status): 若为正常终⽌⼦进程返回的状态,则为真。(查看进程是
//    否是正常退出)
//    WEXITSTATUS(status): 若WIFEXITED⾮零,提取⼦进程退出码。(查看进程的
//    退出码)

//    options:默认为0,表⽰阻塞等待
//    WNOHANG: 若pid指定的⼦进程没有结束,则waitpid()函数返回0,不予以等
//    待。若正常结束,则返回该⼦进程的ID。

        两个方法可以总结为如下情况:

                (1) 如果子进程已经运行完退出,处于僵尸状态,则父进程中调用wait/waitpid时,会立即返回,并释放资源,获得子进程退出信息。

                (2) 如果子进程存在且正常运行还没退出时,调用wait/waitpid函数,则父进程可能阻塞,等到子进程运行完毕。

                (3) 如果不存在等待的子进程,则立即出错返回-1。 

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
int main()
{
        pid_t id = fork();
        if (id == 0)
        {
                int cnt = 5;
                while(cnt)
                {
                        printf("我是子进程,pid: %d, ppid: %d\n", getpid(), getppid());
                        sleep(1);
                        cnt--;
                }
                exit(40);
        }
        int status = 0;
        pid_t rid = waitpid(id, &status, 0);
        if (rid > 0)
        {
                printf("wait success, rid: %d, status: %d\n", rid, status);
        else
        {
                printf("wait failed: %d: %s\n", errno, strerror(errno));
        }
        return 0;
}

        上述代码将子进程退出码设为40,但是父进程中接收到的子进程退出状态不为40,可以推测出,退出状态中不只是退出码。 

3.2.3 参数status

        wait和waitpid都有一个status参数,该参数是一个输出型参数。如果传递NULL,表示不关心子进程的退出状态信息。否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。

        status不能简单的当作整型来看待,可以当作位图来看待,具体如下(只研究status低16位)

        正常终止就是进程终止中前两种退出场景,被信号所杀是异常退出场景中的一种。

      上述第8-15位,表示子进程退出的退出码。所以将上述返回的status右移8位,然后再&0xFF,只提取该八位的值。

        将上述代码改为如下代码,则就为获取子进程的退出码。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
int main()
{
        pid_t id = fork();
        if (id == 0)
        {
                int cnt = 5;
                while(cnt)
                {
                        printf("我是子进程,pid: %d, ppid: %d\n", getpid(), getppid());
                        sleep(1);
                        cnt--;
                }
                exit(40);
        }
        int status = 0;
        pid_t rid = waitpid(id, &status, 0);
        if (rid > 0)
        {
                printf("wait success, rid: %d, exit code: %d\n", rid, (status>>8)&0xFF);        
        }
        else
        {
                printf("wait failed: %d: %s\n", errno, strerror(errno));
        }
        return 0;
}

         进程异常退出时,一般都是因为进程收到了信号,status中的低7位用于保存异常时对应的信号编号。

        没有异常,则低7位为0,一旦低7位不为0,就是异常退出,则8-15位的退出码无意义。

        Linux系统中,使用 kill -l 命令查看所有信号。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
int main()
{
        pid_t id = fork();
        if (id == 0)
        {
                //int cnt = 5;
                while(1)
                {
                        printf("我是子进程,pid: %d, ppid: %d\n", getpid(), getppid());
                        sleep(1);
                        //cnt--;
                }
                exit(40);
        }
        int status = 0;
        pid_t rid = waitpid(id, &status, 0);
        if (rid > 0)
        {
                printf("wait success, rid: %d, exit code: %d, exit signal: %d\n", rid, (status>>8)&0xFF, status&0x7F);  //rid为成功等待的子进程的pid
        }
        else
        {
                printf("wait failed: %d: %s\n", errno, strerror(errno));
        }
        return 0;
}

        用另一个终端输入 kill -9 子进程pid ,则会得到下面的结果。 

知识点1:

        僵尸进程的PCB中存储着该进程的退出码和退出信号,父进程通过waitpid()这种系统调用,让OS去处于僵尸状态的子进程的PCB中获得退出码和退出信号,然后返回给父进程中创建的status。

知识点2:

        上述代码中(status>>8)&0xFF操作就等同于WEXITSTATUS(status)这个宏运算。WIFEXITED(status)宏运算是判断子进程退出是否异常,正常终止返回真,异常返回假。

3.2.4 阻塞与非阻塞等待

        上述的代码例子都是阻塞等待,再调用wait()和waitpid()时,需要等待子进程完成才进行调用,不然父进程就会一直阻塞在wait()和waitpid()的调用处。

        非阻塞等待就是当子进程正常运行时,父进程中的waitpid()去检测子进程的状态,一般非阻塞等待都会搭配循环使用,对子进程构成轮询的形式。waitpid()中给option形参传入WNOHANG就能将waitpid()的等待方式换为非阻塞等待方式。

        下面给出一个非阻塞等待的例子:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>

typedef void (*func_t) ();

#define NUM 5
func_t handlers[NUM + 1];

//如下是各个功能函数
void Download()
{
        printf("我是一个下载任务...\n");
}
void Flush()
{
        printf("我是一个刷新任务...\n");
}
void Log()
{
        printf("我是一个记录日志的任务...\n");
}

//注册函数,将需要各种功能函数写入到上述函数指针数组中
void registerHandler(func_t h[], func_t f)
{
        int i = 0;
        for (; i < NUM; i++)
        {
                if (h[i] == NULL) break;
        }
        //指针数组满了
        if (i == NUM) return;
        h[i] = f;
        h[++i] = NULL;
}
int main()
{
        registerHandler(handlers, Download);
        registerHandler(handlers, Flush);
        registerHandler(handlers, Log);
        pid_t id = fork();
        if (id == 0)
        {
                while(1)
                {
                        printf("我是子进程,pid: %d, ppid: %d\n", getpid(), getppid());
                        sleep(3);
                }
                exit(10);
        }

        //父进程
        while(1)
        {
                int status = 0;
                pid_t rid = waitpid(id, &status, WNOHANG);
                if (rid > 0)
                {
                        printf("wait success, rid: %d, exit code: %d, exit signal: %d\n", rid, (status>>8)&0xFF, status&0x7F);
                        break;
                }
                else if (rid == 0)
                {
                        //函数指针进行回调
                        int i = 0;
                        for (; handlers[i]; i++)
                        {
                                handlers[i]();
                        }
                        printf("本轮调用结束,子进程没有退出\n");
                        sleep(1);
                }
                else
                {
                        printf("等待失败\n");
                        break;
                }
        }
        return 0;
}

        上述代码首先给出一个函数指针func_t的定义,然后创建一个全局的函数指针数组handlers[NUM + 1]。在main函数中通过rigisterHandler函数将各个功能函数指针放入函数指针数组中。父进程通过轮询的方式监测子进程的运行,子进程没有结束,父进程同时也做着自己的任务。当子进程正常结束或者异常结束时,父进程等待成功或者等待失败,结束进程。 

        上述程序的结束是通过在另一个终端中输入 kill -9 852802 进行结束的。 

4.进程程序替换

        程序替换是通过特定的接口,加载磁盘上的一个全新的程序(代码和数据),加载到调用进程的地址空间中。

4.1 替换原理

        用fork创建子进程后,子进程执行和父进程相同的程序(但是可能执行不同的代码分支),子进程往往要调用exec*系列的函数来执行另一个程序。

        当进程调用一种exec*系列函数时,该进程的用户空间代码和数据完全被新程序的代码和数据替换,从新程序的启动例程开始执行。调用exec*系列函数并不创建新进程,所以调用exec*系列函数前后,该进程的id并未改变。

        程序替换的过程中没有创建新的进程,只是把当前的代码和数据用新的程序的代码和数据覆盖式的进行替换。一旦程序替换成功,就去执行新代码,原始代码的后半部分已经不存在了。

        下图中表示,程序替换只是把物理内存中的代码和数据以及页表的对应关系进行了替换。

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

int main()
{
        printf("我的程序要运行了!\n");
        execl("/usr/bin/ls", "ls", "-l", "-a", NULL);
        printf("我的程序运行完毕!\n");
        return 0;
}

        上述代码表示,替换之后,后续代码不执行。 

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

int main()
{
        printf("我的程序要运行了!\n");
        int i = execl("/usr/b/ls", "ls", "-l", "-a", NULL);
        printf("我的程序运行完毕!,返回值为:%d\n", i);
        return 0;
}

        上述代码测试参数传递错误之后,exec*函数的返回值。 

4.2  替换函数(exec*函数)

#include <unistd.h>
//前6个都是C语言提供的调用接口,最后一个是Linux提供的系统调用

//path: 路径+程序名 -- 执行哪个程序
//file:不用带路径(带路径也没错),只用给程序名,函数会从环境变量PATH中查找指定的命令
//arg: 命令行参数,...表示可变参数列表。命令怎么写,这里就怎么写,最后一个参数以NULL结尾,
//表示参数传递完毕。 -- 怎么执行函数
//argv[]:将命令行参数以指针数组的形式传递
//envp[]:环境变量的指针数组
int execl(const char *path, const char *arg, ...);

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

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

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

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

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

//系统调用
int execve(const char *path, char *const argv[], char *const envp[]);

        这些函数如果调用成功则加载新的程序从启动代码开始执行,没有返回值。如果调用出错,则返回-1,所以exec*函数只有失败的返回值,没有成功的返回值。 

        这些函数原型看起来很容易混淆,但是有规律。

        l(list):表示参数使用列表传递命令行参数。

        v(vector):表示参数使用数组传递命令行参数。

        p(path):带有p的函数名,表示会从环境变量PATH中查找执行的程序。

        e(env):带有e的函数名,表示用户需传递一个自己维护的环境变量指针数组。

        注:上述表中的“是否带路径”,若为“是”,表示程序会从PATH中的路径查找,若为“不是”,表示需要用户自己传入程序路径。 

        下列代码演示,通过程序替换的方式,用C语言调用其他语言写的程序。

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
int main()
{
        printf("我的程序要运行了!\n");
        if (fork() == 0)
        {
                sleep(1);
                //脚本语言执行的程序是解释器
                //第一个参数是脚本解释器
                //execl("/usr/bin/python3", "python", "other.py", NULL);
                execl("/usr/bin/bash", "bash", "other.sh", NULL);
                exit(1);
        }
        waitpid(-1, NULL, 0);
        printf("我的程序运行完毕!\n");
        return 0;
}

知识点1:

        exec*函数为什么没有影响父进程?

                1. 进程具有独立性。2. 使用exec*函数进行进程替换时,数据和代码都发生了写时拷贝。 

4.3 替换函数介绍

4.3.1 execl函数

        execl函数,需要传递程序的路径,并且使用列表形式传递命令行参数。

#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

int main()
{
        printf("我的程序要运行了!\n");
        if (fork() == 0)
        {
                sleep(1);
                execl("/usr/bin/ls", "ls", "-al", NULL);
                exit(1);
        }

        waitpid(-1, NULL, 0);
        printf("我的程序运行完毕!\n");
        return 0;
}

4.3.2  execlp函数

        execlp函数,只需要传递程序文件名,并且以列表形式传递命令行参数。

#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

int main()
{
        printf("我的程序要运行了!\n");
        if (fork() == 0)
        {
                sleep(1);
                execlp("ls", "ls", "-al", NULL);
                exit(1);
        }

        waitpid(-1, NULL, 0);
        printf("我的程序运行完毕!\n");
        return 0;
}

4.3.3  execv函数

        execv函数,需要传递程序路径,以数组的形式传递命令行参数。

#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

char *const argv[] = {
        (char*const)"ls",
        (char*const)"-a",
        (char*const)"-l",
        NULL
};

int main()
{
        printf("我的程序要运行了!\n");
        if (fork() == 0)
        {
                sleep(1);
                execv("/usr/bin/ls", argv);
                exit(1);
        }

        waitpid(-1, NULL, 0);
        printf("我的程序运行完毕!\n");
        return 0;
}

4.3.4  execvp函数

        execvp函数,只需要传递程序文件名,以数组的形式传递命令行参数。

#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

char *const argv[] = {
        (char*const)"ls",
        (char*const)"-a",
        (char*const)"-l",
        NULL
};

int main()
{
        printf("我的程序要运行了!\n");
        if (fork() == 0)
        {
                sleep(1);
                execvp("ls", argv);
                exit(1);
        }

        waitpid(-1, NULL, 0);
        printf("我的程序运行完毕!\n");
        return 0;
}

4.3.5 execvpe函数

        execvpe函数,只需要传递程序文件名,以数组的形式传递命令行参数,不使用当前环境变量,需自己组装环境变量。

         这里先给出一个C++源代码other.cc,用于打印命令行参数以及环境变量,并编译为可执行的二进制程序other。

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

int main(int argc, char *argv[], char *env[])
{
        std::cout << "hello C++" << std::endl;
        for (int i = 0; i < argc; i++)
        {
                printf("argv[%d]: %s\n", i, argv[i]);
        }
        printf("\n");
        for (int i = 0; env[i]; i++)
        {
                printf("env[%d]: %s\n", i, env[i]);
        }
        return 0;
}

         下列给出程序execvpe.c,调用execvpe函数执行other程序,并给other程序传递自己组装的命令行参数和环境变量表。

        注:这里函数名带”p“,传递的时候传递程序名即可,但是other程序的路径没有放入PATH环境变量中,所以这里传递的程序名直接传递程序路径./other。

#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

char *const argv[] = {
        (char*const)"ls",
        (char*const)"-a",
        (char*const)"-l",
        NULL
};

char*const env[] = {
        "MYVAL1=1234567",
        "MYVAL2=6666666",
        NULL
};

int main()
{
        printf("我的程序要运行了!\n");
        if (fork() == 0)
        {
                sleep(1);
                execvpe("./other", argv, env);
                exit(1);
        }

        waitpid(-1, NULL, 0);
        printf("我的程序运行完毕!\n");
        return 0;
}

知识点1:        

        如果exec*系列函数中,函数名没有带”e“,则表明环境变量继承自父进程。

       将上述程序中的 execvpe("./other", argv, env); 替换为 execvp("./other", argv); 则会得到下列结果,打印的是继承自父进程的环境变量。

        若需求不是使用全新的env列表,而是给继承下来的env列表新增几个环境变量,有以下两种做法:
        (1)使用putenv函数,给当前进程中的env列表添加新的环境变量。调用不带”e“的exec*函数进行程序替换。

#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

char *const argv[] = {
        (char*const)"ls",
        (char*const)"-a",
        (char*const)"-l",
        NULL
};

int main()
{
        printf("我的程序要运行了!\n");
        if (fork() == 0)
        {
                sleep(1);
                putenv("MYVAL1=1234567");
                putenv("MYVAL2=6666666");
                execvp("./other", argv);
                exit(1);
        }

        waitpid(-1, NULL, 0);
        printf("我的程序运行完毕!\n");
        return 0;
}

        (2)使用putenv函数,给当前进程中的env列表添加新的环境变量。调用带”e“的exec函数进行程序替换时,给 argv[] 形参位置传递参数environ。下列代码的结果和上述代码的结果一样。

#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

char *const argv[] = {
        (char*const)"ls",
        (char*const)"-a",
        (char*const)"-l",
        NULL
};

char*const env[] = {
        "MYVAL1=1234567",
        "MYVAL2=6666666",
        NULL
};

int main()
{
        printf("我的程序要运行了!\n");
        if (fork() == 0)
        {
                sleep(1);
                putenv("MYVAL1=1234567");
                putenv("MYVAL2=6666666");
                extern char **environ;
                execvpe("./other", argv, environ);
                exit(1);
        }

        waitpid(-1, NULL, 0);
        printf("我的程序运行完毕!\n");
        return 0;
}

 4.3.6 execle函数

        execle函数,需要传递程序路径,以列表形式传递命令行参数,不使用当前环境变量,需自己组装环境变量。

#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>


char*const env[] = {
        "MYVAL1=1234567",
        "MYVAL2=6666666",
        NULL
};

int main()
{
        printf("我的程序要运行了!\n");
        if (fork() == 0)
        {
                sleep(1);
                execle("./other", "other", "-a", "-b", NULL, env);
                exit(1);
        }

        waitpid(-1, NULL, 0);
        printf("我的程序运行完毕!\n");
        return 0;
}

4.3.7 execve函数

        事实上,只用execve是真正的系统调用,其他函数内部都封装了execve,这些函数之间的关系如下图所示。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值