目录
一、进程退出
1.创建一批进程
在谈进程退出之前,我们先来谈谈如何创建多个进程。
我们可以直接用循环创建一批进程。
我们可以看到所有子进程的父进程都是同一个,并且过了一会由于父进程没有回收导致子进程全部变僵尸了,在下面的进程等待的时候会解决进程僵尸的问题。
exit可以直接让进程退出,参数是一个整数,这个参数表示退出时候的返回值。
2.进程退出场景
我们先来看进程退出都有哪些场景。
第一种情况:代码运行完毕,结果正确
如果代码运行完毕结果正确,你会关心他为啥正确吗?现实生活中你考试考了第一名,你的父亲会问你,你咋考这么好?是不会问的。结果正确还不好吗?根本不用关心。
第二种情况:代码运行完毕,结果不正确
如果代码运行完毕了,结果不正确,你要不要关心他为啥不正确?如果你考试考了倒数第一,你的父亲会关心你为啥考的这么不好吗?会问你原因吗?大概率是会问的,那么在进程中,谁会关心进程的运行情况呢?一般而言是父进程。
那父进程怎么知道子进程运行结果到底是对还是不对?可以让子进程 return 不同的数字表示不同的退出原因,这个数字就叫做退出码,如果返回值是0,表示进程正确执行完毕。如果是其他表示代码执行不正确。
所以,我们为啥写main函数的时候返回值都是0呢,本质上表示:进程运行完成时结果是否正确,如果不是,可以用不同的数字表示不同的出错原因!
linux中我们可以用 echo $? 来查看最近一次进程退出时候的退出码。我们写的代码,默认父进程都是bash,也就是xshell这个进程。?是里一个环境变量,echo $?就是把这个环境变量的值打印出来给我们看。
但是这些退出码是啥意思我咋知道呢? 我们可以用语言提供给我们函数strerror查看对应数字的错误码描述。
这里其实是有134个退出信息的,但太长了就不截了,大家可以自己打印出来看看,直接写一个循环,然后调用strerror就可以。这些都是系统默认给我们提供的错误码。
#include <stdio.h>
#include <string.h>
int main()
{
//show();
for(int i = 0; i < 200; i++)
{
printf("%d : %s\n", i, strerror(i));
}
return 0;
}
当我们用ls查找一个不存在的文件,会给我们显出错信息,这个出错信息不觉得很熟悉吗?不就是上面2号错误码描述吗?ls命令运行之后也是一个进程,也有返回值,当他查一个不存在的文件时,就会返回对应的错误码,再给我们转成错误信息,就让我们看到了下面的 NO such......我们直接 echo $?查看上一个进程的退出码发现,果然是2。
这就是退出码的作用,可以让我们知道代码执行结果是否正确。
我们可不可以不用系统提供的错误码,我们自己定义行不,当然可以,我们直接可以用指针数组就可以定制化我们的错误码。
上面我们了解了父进程是如何知道子进程是否出错的,是通过错误码,如果错误码为0表示正常退出,如果非0,就表示出错了,可以用strerror查看错误码的信息,strerror一般是和配合errno使用的,这样我们不仅可以知道错误码,也可以知道错误信息。
C语言提供的errno,它里面保存的是最近一次执行的错误码,我们知道C语言是给我们提供了很多库函数的,这些库函数是不一定调用成功的,如果调用失败,错误信息就会写到errno里面,如果有很多个函数调用出错,会覆盖式的写入到errno里面,errno只会保存最后一个函数调用出错的信息!
下面我们申请一个4G的内存空间。显然是不被允许的,此时可以使用errno获取malloc函数失败的错误码,然后用strerror获取错误信息。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
int main()
{
int ret = 0;
int* p = (int*)malloc(1000 * 1000 * 1000 * 4);
if(p == NULL)
{
printf("%s\n",strerror(errno));
ret = errno;
}
return ret;
}
第三种情况:代码异常终止
如果代码异常了,退出码还有意义吗?
如果代码异常了,代码本身可能就没跑完,如果代码都没跑完,程序提前退了,根本没执行return最后一条语句,所以退出码没有意义。
那有没有可能最后执行return了,然后可能操作系统资源不足了把我Kill了,有可能吗?有可能。
但关键问题在于,你怎么知道return了呢?姑且认为异常的时候,你的确return了,但你敢用这个退出码吗?异常可能在任何地方发生!
还是你去考试了,考试期间作弊被抓了,这就是你考试没考完出现异常了,然后你爸问你考的怎么样,你说我考了90,你爸问你怎么考的90,你非说我中间作弊了,但是不影响,我是在考试结束1分钟的时候作弊的,此时已经没有意义了,你爸不会相信你了,作弊还分什么时候作弊,我怎么知道你是一开始考试就作弊,还是结束时作弊,我不确定,反正作弊事实你承认了,我就不相信你了。
同理,当你退出的时候出异常,退出码无意义,我们不关心退出码。
你考试完毕了,要发奖学金了,是先看你考了多少分,还是看你有没有作弊?毫无疑问,先看你有没有作弊,凡是作弊的一律淘汰。
所以当一个进程退出的时候先看退出码还是先看是否出现异常,当然先看是否出现异常!没出异常在看退出码。
但是你要不要关心我为什么异常?以及我发生了什么异常?当然要了,那我们怎么关心?我咋知道进程有没有出异常?
进程出现异常,本质是收到了信号!
比如说除0错误,寄存器就会发生溢出错误。或者野指针,野指针是页表没有建立映射或者对应的映射是只读的权限。其实所有的异常本质都是硬件错误,后面信号会谈。
我们可以用kill -l查看所有的信号。
浮点数错误是下列哪一个信号呢?是8号信号FPE,野指针是段错误,是11号信号SEGV。当出现异常,系统会以信号的方式发送给我们进程。
但是我们怎么证明呢?我们直接代码验证。
代码中浮点数错误
代码中野指针错误
代码没问题,我们给该进程发送8号信号
代码没问题,我们给该进程发送11号信号
因此对于进程退出,我们先看有没有收到信号, 如果没有收到信号,在看错误码,如果错误码不是0,就看错误信息,这就是进程退出的逻辑。
exit和_exit
eixt
exit作用就是引起一个进程进行终止,这个地方的参数是什么意思,是当前进程的退出码。
return 也可以达到一样的效果,在main函数里return和exit作用的等价的。
下面main函数里面return0,调用show,里面exit退出码是12。最后打印进程退出码是12.
return和exit的区别在于:exit在任何地方被调用,都表示进程退出。return只表示当前函数返回。
_exit
_exit和exit有何区别呢?
它们2个只有一个区别,exit会刷新缓冲区的内容,_exit不会刷新缓冲区的内容。
exit:
_exit:
它们两都可以打出hellow world, 并且退出码也都是0。
但是当我们把hello world的\n去掉就不一样了。
exit:将hello world成功打印了出来,这里我们把\n去掉了,就不会刷新缓冲区,但我们调用exit,它会帮我们把缓冲区的数据刷出来。
_exit:并没有把 hello world 打印出来,表明_exit并不会刷新缓冲区,因此hello world还在缓冲区中,所有就没有显示。_exit它是纯粹的系统调用,而exit是封装了_exit。这个缓冲区是在用户区的,exit封装了_exit的同时,并还会把数据从缓冲区刷出来,这就是它们的区别。
exit和return的区别
(1)在main函数中调用return和exit都表示进程退出。
(2)在其它函数调用return,表示该函数返回。在其它函数调用exit,表示进程结束。
二、进程等待
前面有一个问题一直没有解决,那就是僵尸进程。如果子进程退出,父进程放任不管,会造成内存泄漏,因此父进程需要对退出的子进程做回收,那么如何回收呢?需要用进程等待,就是调系统接口。
1.进程等待是什么?
通过系统调用wait/waitpid,来进行对子进程进行状态检测与回收的功能。
2.为什么要进程等待?
僵尸进程无法被杀死,需要通过进程等待来杀死它,进而解决内存泄漏问题---必须解决的。我们需要通过进程等待,获得子进程的退出情况---知道我布置给子进程的任务,它完成的怎么样了---要么关心,也可能不关心---可选的 。
3.要怎么做呢?
父进程通过调用wait/waitpid进行僵尸进程的回收。
我们先来看wait这个接口。
wait:等待任意一个子进程
参数:它就一输出型参数,我们给它传递一个整形的指针变量,wait会把子进程的退出信息保存在这个指针变量里,从而让我们外部获取到,如果我们不关心子进程的退出信息,直接传递NULL。
返回值:如果返回值 < 0,表示等待失败, 成功返回子进程pid。
下面这段代码中,让子进程运行5秒后然后退出,退出后变为僵尸,父进程运行10秒,10秒结束后调用wait等待子进程,我们可以看到子进程会从僵尸状态变为直接退出。
1.等待一个进程
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
pid_t id = fork();
if(id == 0)
{
// 子进程
int cnt = 5;
while(cnt--)
{
printf("I am child, pid: %d, ppid: %d\n", getpid(), getppid());
sleep(1);
}
exit(0); // 执行5次后退出
}
else
{
// 父进程
int cnt = 10;
while(cnt--)
{
printf("I am father, pid: %d, ppid: %d\n", getpid(), getppid());
sleep(1);
}
pid_t ret = wait(NULL);
if(ret == id)
{
printf("process wait success !\n");
}
}
sleep(5);
return 0;
}
2.等待一批进程
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
void RunChild()
{
int cnt = 5;
while(cnt--)
{
printf("I am child, pid: %d, ppid: %d\n", getpid(), getppid());
sleep(1);
}
}
const int N = 5;
int main()
{
for(int i = 0; i < N; i++)
{
pid_t id = fork();
if(id == 0)
{
RunChild();
exit(0); // 子进程直接退出,不会执行后续代码。
}
printf("create child process: %d success\n", id); // 这句话只有父进程会打印
}
sleep(10); //等待
for(int i = 0; i < N; i++)
{
pid_t ret = wait(NULL);
if(ret > 0)
{
printf(" wait %d success !\n", ret);
}
}
return 0;
}
3.wait等待的进程一个都不退
wait当任意一个子进程退出的时候,wait会回收子进程,但如果子进程都不退呢?
如果子进程不退出,父进程就死等,等你退,这种你不退我就一直死等你退叫做阻塞等待,所以一个进程等待是否只会等待硬件资源呢?软件资源也需要等待的。
因此,一个完整的进程代码,应该有fork创建,也要有wait退出。但接下来的问题是,创建一个子进程要获取子进程的退出情况呀,我创建子进程我要让他帮我办事的,事办的怎么样?我怎么知道啊。就要用到等待的参数了,我们直接看下一个等待的系统调用,下一个系统调用搞明白了wait这个系统调用就是轻轻松松。
下面我们来看看waitpid这个系统调用。
4.waitpid
wait和waitpid这两个调用是啥关系呢?wait是waitpid的子集,wait可以做的事情waitpid都可以做。
waitpid的第一个参数:第一个参数是要等待哪一个进程,如果传-1,表示等待任意进程。
waitpid的第二个参数:和wait的参数是一样的。如果不关心退出结果直接填NULL。
waitpid的第三个参数:表示等待的方式,可以传递0,传递0就表示默认阻塞等待。
我们要通过第二个参数来获取子进程的退出信息,这个status,是被当做好几部分使用的,前7个比特位表示是否收到信号,可以表示退出是否出现异常!8-15的比特位表示退出码。我们使用位运算,将这两部分提取出来,如何提取呢?退出信号:status&0x7F,退出码:(status>>8)&0xFF
这两部分相互组合就可以将进程所有的退出情况表示出来,如果前7个比特位结果为0,就表示没出异常。但结果对还是不对呢,看后面的8个比特位即可。如果前7个比特位结果不为0,表示异常了,退出码都不用看了。
下面我们来使用一下。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
void RunChild()
{
int cnt = 5;
while(cnt--)
{
printf("I am child, pid: %d, ppid: %d\n", getpid(), getppid());
sleep(1);
}
}
int main()
{
pid_t id = fork();
if(id == 0)
{
RunChild(); // 子进程运行5秒后直接退出
exit(11); // 子进程直接退出,不会执行后续代码。
}
int status = 0;
pid_t ret = waitpid(id, &status, 0);
if(ret > 0)
{
printf(" wait %d success ! exitSignal: %d, exitCode: %d\n", ret, status & 0x7F, (status >> 8) & 0xFF);
}
return 0;
}
退出信号是0,表示没有出异常,退出码果然是我们写的11。
接下来我们直接在子进程代码中搞一个 / 0错误。可以看到,退出信号就是8,是除零错误的信号。
下面搞一个野指针,可以看到退出信号是11号信号。
我们可以通过status获取子进程的退出结果。但是,每次获取都要按位与,不容易记忆还很难理解,有没有啥更简单的方法呢?让我们获取退出码和信号呢?
WIFEXITED(status)这个宏,W就是等待的意思,IF就是如果,EXIT就是退出,ED可以理解为结束的意思。如果进程没有异常,返回的结果就是真。
但是这个宏的一个不好的地方就是只能表示有没有出异常,如果出异常了结果就是假,但我们无法知道异常信号是啥,如果要关心异常的时候信号是什么还是得用上面的位运算获取。
好处就是方便记忆,而且写出来的代码逻辑性更强。
WEXITSTATUS(status)这个宏,可以帮我们提取退出码。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
void RunChild()
{
int cnt = 5;
while(cnt--)
{
printf("I am child, pid: %d, ppid: %d\n", getpid(), getppid());
sleep(1);
}
}
int main()
{
pid_t id = fork();
if(id == 0)
{
RunChild(); // 子进程运行5秒后直接退出
exit(11); // 子进程直接退出,不会执行后续代码。
}
int status = 0;
pid_t ret = waitpid(id, &status, 0);
if(ret == id) // 等待成功
{
if(WIFEXITED(status))
{
// 表示进程没有出异常
printf("进程是正常跑完的,退出码:%d\n", WEXITSTATUS(status));
}
else // 子进程出异常
{
printf("进程出异常了\n");
}
}
else // 等待失败
{
printf("wait fail !\n");
}
return 0;
}
此时,我们就可以写出完整的进程代码了。
5.非阻塞轮询
使用waitpid的时候,默认是阻塞方式等待,子进程不退出,父进程就一直等待。但是对于父进程来说,子进程不退出,父进程就一直等待,父进程等待的时候啥都没做,如果父进程在等待的时间做一些事情是不是会更好呢?然后做完之后再去看子进程是否退出,如果没退出继续做自己的事情。
所以非阻塞轮询本质就是,当子进程状态不就绪(不是退出的状态),我就去做自己的事情,而不是傻等,然后做完再去看子进程的状态是否是退出状态,然后重复这个过程,直到子进程状态就绪。
那怎么设置非阻塞轮询呢?
waitpid的第三个参数,options就是用来设置阻塞方式的,如果是0,就是阻塞式等待,如果我们要
想设置成非阻塞轮询,可以传WNOHANG,W就是等待,NO就是不,HANG就是夯住了的意思。整体就是等待的时候不要夯住,就是非阻塞。
那接下来waitpid的返回值是不是就需要加一个呢? > 0表示等待成功, < 0等待失败,那非阻塞是失败吗?不是,当返回值==0的时候,就表示子进程还没有退出,父进程在等待。既然是轮询那我们就需要写一个死循环,如果等待成功或者等待失败就跳出循环。
void RunChild()
{
int cnt = 5;
while (cnt--)
{
printf("I am child, pid: %d, ppid: %d\n", getpid(), getppid());
sleep(1);
}
}
int main()
{
pid_t id = fork();
if (id == 0)
{
RunChild(); // 子进程运行5秒后直接退出
exit(11); // 子进程直接退出,不会执行后续代码。
}
int status = 0;
while (1) // 轮询
{
pid_t ret = waitpid(id, &status, WNOHANG); // 非阻塞
if (ret > 0)
{
if (WIFEXITED(status))
{
// 表示进程没有出异常
printf("进程是正常跑完的,退出码:%d\n", WEXITSTATUS(status));
}
else
{
printf("进程出异常了\n");
}
// 表明出结果了,直接退出
break;
}
else if (ret == 0)
{
printf("你好了没?子进程还没退出,我在等等...\n");
sleep(1);
}
else
{
printf("wait fail !\n");
// 表明出结果了,直接退出
break;
}
}
return 0;
}
但是让父进程光打印有啥意思呢?下面加点东西。
我们可以定义一个返回值为void,无参的函数指针,在一个函数指针数组(任务数组),然后写一些初始化、添加任务、执行任务的方法,让我们的父进程在等待的时候去执行。
#define TASK_NUM 5
void Task1()
{
printf("这是一个打印日志的任务..., pid: %d\n", getpid());
}
void Task2()
{
printf("查看野区资源是否刷新的任务..., pid: %d\n", getpid());
}
void Task3()
{
printf("查看基地是否被摧毁的任务..., pid: %d\n", getpid());
}
typedef void (*task_t)(); // 定义一个函数指针
task_t tasks[TASK_NUM]; // 函数指针数组
int addTask(task_t t) // 给函数指针数组里面添加任务
{
int pos = 0;
for(; pos < TASK_NUM; pos++)
{
if(!tasks[pos]) break;
}
if(pos == TASK_NUM) return -1;
tasks[pos] = t;
return 0; //添加成功
}
void initTask() // 初始化任务数组
{
for(int i = 0; i < TASK_NUM; i++)
{
tasks[i] = NULL;
}
addTask(Task1);
addTask(Task2);
addTask(Task3);
}
void executeTast()
{
for(int i = 0; i < TASK_NUM; i++)
{
if(!tasks[i])
continue;
tasks[i]();
}
}
void RunChild()
{
int cnt = 5;
while (cnt--)
{
printf("I am child, pid: %d, ppid: %d\n", getpid(), getppid());
sleep(1);
}
}
int main()
{
pid_t id = fork();
if (id == 0)
{
RunChild(); // 子进程运行5秒后直接退出
exit(11); // 子进程直接退出,不会执行后续代码。
}
int status = 0;
initTask(); //初始化任务
while (1) // 轮询
{
pid_t ret = waitpid(id, &status, WNOHANG); // 非阻塞
if (ret > 0)
{
if (WIFEXITED(status))
{
// 表示进程没有出异常
printf("进程是正常跑完的,退出码:%d\n", WEXITSTATUS(status));
}
else
{
printf("进程出异常了\n");
}
// 表明出结果了,直接退出W
break;
}
else if (ret == 0)
{
// printf("你好了没?子进程还没退出,我在等等...\n");
executeTast();
sleep(1);
}
else
{
printf("wait fail !\n");
// 表明出结果了,直接退出
break;
}
}
return 0;
}
我们可以看到,父进程在等待的时候去完成了任务,这里父进程的主要工作还是等待子进程,等待的时候去做其他工作只是顺便的,因此父进程去完成的工作最好是轻量化的,必要太重,不然可能子进程已经退出了,但父进程手里的任务还没有执行完,因此子进程等了很久才被回收,不过影响也不是很大,总是要回收的,但是尽量还是让父进程执行的工作别太重。
6.waitpid的原理
当CPU调度父进程的代码的时候,waitpid这个函数是在父进程的代码和数据里的,因此这个代码要被CPU执行,子进程也有自己的PCB和代码数据,当子进程退出的时候,代码和数据可以被直接释放,但是子进程的PCB先不能被释放,子进程退出的时候退出信息会保存在自己的PCB里面,然后父进程是通过waitpid读取子进程的退出信息的,waitpid先看子进程是否是z状态,如果是z状态直接读取退出信号和退出信息,这样就可以让我们上层拿到。
为啥不能让我们直接拿到子进程的退出信息呢?还要通过系统调用,因为父进程的代码是我们写的,父进程直接去拿子进程的退出信息,不就相当于我们用户去拿吗?操作系统允许你去拿我管理的信息吗?操作系统是不允许的,就好比你是一个编程能力很强的人,然后隔壁学校的校长想借调一下你帮我们学校参加一个比赛,然后隔壁校长不经过你们校长的同意直接跑到你宿舍就把你借走了,这行吗?不行,我们必须通过管理者拿到被管理者的数据,不能通过用户访问底层数据,因为操作系统不相信任何人。
三、进程替换
由于子进程是把父进程的PCB、地址空间、页表拷贝了一份,因此子进程和父进程执行的代码基本是大同小异的,那么如果我们想让子进程执行和父进程完全不同的代码,此时就可以使用程序替换。
1.单进程的程序替换
在我们的代码中,有两个printf,也就是会在显示器中打印两句话,可是我们可以看到,运行的结果中,只打印了execl之前的那一句,而且后面居然把 ls -a -l这条命令的运行结果也打印了出来,这说明下面的printf完全被替换走了。
程序替换就是把别的地方的程序代码,替换到我们的代码当中。
(1) execl的使用
第一个参数:是可执行程序的路径。我们要执行一个程序,首先第一步要先找到它在哪里放着吧?
第二个参数:是可变参数列表,我们可以给它传递任意多个字符串,最后以NULL作为结尾。我们找到它在哪里放着,那我们如何执行它呢?第二个参数就决定了如何执行该程序。
上面的代码,我们要执行ls指令,ls指令在哪放着呢?就在/usr/bin/ls这个路径下,我怎么知道的呢?因为环境变量呀,PATH这个环境变量就记录了ls指令默认要查找的路径。然后知道在这个路径下,接下来怎么执行呢?ls这条指令后面可以带各种选项的,你要怎么执行,你就怎么传就行,最后以NULL结尾即可。
2. 多进程的程序替换
下面的结果如我们所料,子进程的代码被替换掉了,exec之后的代码全都不会被执行了。
如果替换失败了呢?比如说路径写错了,或者执行的方法不存在,那就只能往后走。
上面我们似乎没谈返回值,exec系列的函数如果成功了,没有返回值,你返回给谁呢?成功之后,后续的代码全都被替换了,你怎么拿到返回值呢?因此,没有成功返回值。只有失败返回值。
除了execl,还有6个程序替换的函数。下面我们一一来介绍一下。
3.execlp
与execl相比多加了个p,p是PATH的意思,执行的程序默认会去PATH环境变量下面去找,因此我们在传递第一个参数路径的时候,不需要加/usr/bin/,只需要传ls即可,虽然回去PATH路径里面去找,但是你要执行ls还是其它命令呢?你要告诉我呀,因此要传你要执行的那个命令,也就是ls。后面的参数如同execl一样。
我们既然可以把命令当做可执行程序替换,那我们可不可以把我们自己写的代码替换呢?当然可以。
C调用C++程序
在我们当前路径下有两个可执行程序一个proc,一个otherExe,otherExe的代码很简单就是打印了一句话。接下里我们要用proc这个可执行程序去调otherExe这个可执行程序。
第一个参数代表你要执行那个可执行程序,第二个参数是你要怎么执行。
C语言都可以去调C++的可执行程序,那可以调其它语言的可执行程序吗?是没问题的,java、python都可以去调。
C调用python程序
无论是我们的可执行程序,还是脚本,为啥能跨语言调用呢??因为所有的语言运行起来,本质都是进程!!只要是进程都能被调用。几乎所有语言都有类似的execl接口。execl就相当于一个加载器。
4.execle
我们可以把e理解为env,环境变量列表,我们可以替换的可执行程序传递环境变量。
库里面给我们提供了了一个环境变量列表的二级指针environ,这个二级指针就指向环境变量列表,使用这个二级指针之前需要声明一下,因此我们可以直接把这个二级指针传递过去。
我们不仅可以把环境变量传过去,参数列表也是可以的。打印出来的环境变量是很多的,这里就不全部截出来了。
如果我们自己定义一个环境变量列表,传进去会发生什么?直接就会把我们传递的环境变量。
5.execv
v是vector,把怎么执行放到一个数组里面传递数组即可。我们可以让对应的程序获取到命令行参数。这里我们没有传环境变量,但我们对应的程序可以打印出来环境变量。为啥呢?因为环境变量也是数据,创建子进程的时候,环境变量已经被子进程继承下去了。我们写的程序是bash的子进程。地址空间里是有专门的环境变量和命令行参数空间的,子进程会继承父进程的地址空间页表,环境变量当然也能继承下去,因此直接就可以找到。
程序替换的时候,环境变量信息是不会被替换的。
如果我们要增加一个环境变量呢?
我们可以直接在命令行上xxx=xxx,给bash新增环境变量,这样的话,就可以让子进程获得。
但是我们如果就想在我们当前的进程新增环境变量呢?我们可以用putenv这个函数。
可以看到otherExe直接就把我们新增的环境变量打印出来了。
但是我们在bash中打印的时候是看不到的,也就是说bash是没有这个环境变量的。就类似于继承一样,子进程是可以既用于父进程的环境变量,也可以有自己新增的环境变量 。
下面这两个大家一看就能懂。
6.execvp
7.execvpe
8.execve
操作系统提供的程序替换的接口就这一个,上面的6个全是库函数,上面的6个全都是封装了这个系统调用。我相信大家一看这个参数列表就知道咋用了。
9.程序替换原理
当一个进程没被执行时,代码和数据都在磁盘里放着,当程序被CPU调度之后,代码和数据会加载到物理内存中,当执行到程序替换的时候,比如说要替换的是ls,ls也是C/C++写的程序,也有代码和数据,此时就把ls的代码和数据替换到我们进程的代码和数据对应的物理内存中,如果比替换前的物理内存小,该释放的内存释放,如果替换之后空间变大了,就扩容。就是让新程序的数据替换我们老数据的程序。
标签:控制,int,退出,exit,linux,进程,include,我们 From: https://blog.csdn.net/2301_76227083/article/details/143579017