【Linux进程控制】进程创建 进程终止 进程等待 进程替换_linux进程创建和终止

#include<sys/wait.h>

int main()
{
pid_t id = fork();
if(id < 0)
{
perror(“fork”);
return 1;
}
else if(id == 0)
{
int count = 5;
while(count)
{
printf(“child is running: %d, ppid: %d, pid: %d\n”, count–, getppid(), getpid());
sleep(1);
}
printf(“child quit…\n”);
exit(0);
}
else
{
printf(“father is waiting…\n”);
pid_t ret = wait(NULL);
printf(“father is wait done, ret: %d\n”, ret);
}
return 0;
}


💨运行结果:


![请添加图片描述](https://img-blog.csdnimg.cn/eb30c215a5f2446eb50575c55153dc34.gif)


✔ 测试用例二:


相比测试用例一,更直观的等待,并维持了一段时间的僵尸,进程从无到有,从有到无。



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

int main()
{
pid_t id = fork();
if(id < 0)
{
perror(“fork”);
return 1;
}
else if(id == 0)
{
int count = 5;
while(count)
{
printf(“child is running: %d, ppid: %d, pid: %d\n”, count–, getppid(), getpid());
sleep(1);
}
printf(“child quit…\n”);
exit(0);
}
else
{
printf(“father is waiting…\n”);
sleep(10);
pid_t ret = wait(NULL);
printf(“father is wait done, ret: %d\n”, ret);
sleep(3);
printf(“father quit…\n”);
}
return 0;
}


💨运行结果:


  监控脚本:`while :; do ps ajx | head -1 && ps ajx | grep process | grep -v grep; sleep 1; echo "####################"; done` (grep -v grep 查找所有非 grep)


![请添加图片描述](https://img-blog.csdnimg.cn/49c8b533d037407b901a3cdb40c1ae3a.gif)


✔ 测试用例三:


fork 5 个子进程后,父进程依次等待,并回收僵尸进程。



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

int main()
{
int i = 0;
while(i < 5)
{
pid_t id = fork();
if(id < 0)
{
perror(“fork”);
return 1;
}
if(id == 0)
{
int count = 5;
while(count)
{
printf(“child is running: %d, ppid: %d, pid: %d\n”, count–, getppid(), getpid());
sleep(1);
}
printf(“child quit…\n”);
exit(0);
}
i++;
}
for(i = 0; i < 5; i++)
{
printf(“father is waiting…\n”);
sleep(10);
pid_t ret = wait(NULL);
printf(“father is wait done, ret: %d\n”, ret);
sleep(3);
printf(“father quit…\n”);
}
return 0;
}


💨运行结果:


![请添加图片描述](https://img-blog.csdnimg.cn/80759d3e5dc84cb68830e75ddc8a4249.gif)


现象 ❓


在 1 秒内,父进程很快的就创建了 5 个子进程,并开始走 for 循环,然后输出 father is waiting…,随后就休眠 10 秒。5 秒前,5 个子进程开始走 while 循环中的 while 循环,随后全部退出。5 秒后父进程开始每隔 13 秒循环的回收僵局进程。


子进程僵尸了,父进程也退出了 ❓


  此时 ps ajx 能否看到僵尸进程是不确定的。因为父进程退出,子进程会被操作系统领养。那么这个僵尸进程是在被操作系统领养后立马回收,还是积累到一定的僵尸进程再回收,这是由操作系统的策略决定的,同时也跟当前操作系统的状态有关系,如果操作系统发现内存资源已经很紧张了,就会提前回收。


一般而言,我们需要 fork 之后,让父进程进行等待 ❗


###### 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相等的子进程;
因为父进程返回的是子进程的pid,所以父进程就可以等待指定的子进程,等待本质是管理的一种方式;
status,
输出型参数,我们传了一个整数地址进去,最终通过指针解引用把期望的数据拿出来。与之对应的是实参传递给形参是输入型参数;
WIFEXITED(status),查看进程是否正常退出,是则真,不是则假;
WEXITSTATUS(status),查看进程退出码,需要WIFEXITED(status)返回true,WIFEXITED(status)正常退出则返回true;
WTERMSIG(status),返回导致子进程终止的信号的编号,需要WIFSIGNALED(status)返回true,WIFSIGNALED(status)子进程被信号终止返回true;
options,
WNOHANG,若pid指定的子进程没有结束,则waitpid()函数返回0,本次不予以等待,需要我们再次等待;若非正常结束,则返回该子进程的ID;或者小于0,失败了。
0,阻塞式等待,同wait————子进程没退出、回收,父进程继续等待;


status ❓


* wait 和 waitpid,都有一个 status 参数,该参数是一个输出型参数,由操作系统填充。
* 如果传递NULL,表示不关心子进程的退出状态信息。
* 否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。
* status 不能简单的当作整型来看待,可以当作位图来看待,具体细节如下图 (目前只研究 status 低16 比特位)。


![在这里插入图片描述](https://img-blog.csdnimg.cn/c78a812a8bd64ac88e123208f0c06765.png)


阻塞和非阻塞 ❓


![在这里插入图片描述](https://img-blog.csdnimg.cn/f454ec76a57d4d42a6dbf3202317708c.png)


  这个概念我们是第一次接触,也不会深入,后面再学习文件和网络时会经常接触。如果 waitpid 中的 options 传 WNOHANG ,那么等待方式就是非阻塞;如果传 0,那么等待方式就是阻塞。


  比如你的学习很差,所以打电话给楼上学习好的同学张三,说:张三,你下来,我请你吃个饭,然后你帮我复习一下。张三说:行,没问题,但是我在写代码,半个小时之后再来。一般一个班,学习好的人总是少数,所以你怕你电话一挂,有人又跟张三打电话求助,导致你不能及时复习,所以你又跟张三说:张三,你电话不要挂,你把电话放你旁边,我喜欢看你写代码的样子。然后你什么事都不做,就在那等待,直到张三下来。当然现实中很少有这种情况,但是这样的场景是存在的,一般是比较紧急的情况,比如你爸打电话让你做件事且告诉你不要挂电话。此时张三不下来,电话就不挂就类似于调用函数,这种等待方式就叫做`阻塞等待`。我们目前所调用的函数,全部是阻塞函数,不管是你自己写的、库里的、系统的,因为我们目前写的代码都是一跑就结束,所以压根就遇不到非阻塞的场景。阻塞函数最典型的特征是调用 ➡ 执行 ➡返回 ➡ 结束,其中调用方始终在等待,什么事情都没做。


  又比如,你跟张三说:明天要考试了,一会我们去吃个饭,然后去自习室,你帮我复习下。张三说:没问题,但是我在写代码,你得等我下。你说:行吧,我在食堂等你,然后挂电话。过了两分钟,你给张三打电话说:张三,你来了没。张三说:我还得一会,你再等下。你说:行吧,然后挂电话。又过了两分钟,你又给张三打电话说:张三,你来了没 … … 。你不断重复的给张三打电话,这种场景在生活中比较多,我们经常催一个人做一件事时,他老是不动,你就不断重复给他打电话。你本质并不是给张三打电话,而是检测张三的状态,张三有没有达到我所期望的状态,每次检测张三是不一定立马就就绪的,如他有没有写完、开始下楼等。这里的检测张三的状态,只是想查看进度,所以这里打电话过程并不会把我卡住,我通过多次打电话来检测张三的进度。每次打电话挂电话的过程就叫做`非阻塞等待`。 我们只要看了它的状态不是就绪,就立马返回。这种基于多次的非阻塞的调用方案叫做`非阻塞轮询检测方案`。


为什么现实世界中大部分选择非阻塞轮询 ❓


这种高效体现在:主要是对调用方高效,你给张三打电话,张三就要 10 分钟,那就是 10 分钟,类似于计算机,你再怎么催都没用,所以我们就不会死等,我们可以先做其它的事,反正不会让因为等待你,而让我做不了事情。


那为什么我们写的代码大部分都是阻塞调用 ❓


根本原因在于我们的代码都是单执行流,所以选择阻塞调用更简单。


为什么是 WNOHANG ❓


在服务器资源即将被吃完时,卡住了,我们一般称服务器`hang`住了,进而导致`宕机`。所以 W 表示等待,NO 表示不要,HANG 表示卡了,所以这个宏的意思是等待时不要卡住。


如何理解父进程等子进程中的 “ 等 ” ❓


![在这里插入图片描述](https://img-blog.csdnimg.cn/22c55aeab76c4181a440965b76399118.png)


所谓的等并不是把父进程放在 CPU 上,让父进程在 CPU 上边跑边等。本来父子进程都在运行队列中等待 CPU 运行,当子进程开始被 CPU 运行后,就把父进程由 R 状态更改为 !R 状态,并放入等待队列中,此时父进程就不运行了,它就在等待队列中等待。当子进程运行结束后,操作系统就会把父进程放入运行队列,并将状态更改为 R 状态,让 CPU 运行,这个过程叫做`唤醒等待`的过程。


操作系统是怎么知道子进程退出时就应该唤醒对应的父进程呢 ❓


wait 和 waitpid 是系统函数,是由操作系统提供的,你是因为调用了操作系统的代码导致你被等待了,操作系统当然知道子进程退出时该唤醒谁。


这里,我们只要能理解等待就是将当前进程放入等待队列中,将状态设置为 !R 状态。所以一般我们在平时使用计算机时,肉眼所发现的一些现象,如某些软件卡住了,根本原因是要么进程太多了,导致进程没有被 CPU 调度;要么就是进程被放到了等待队列中,长时间不会被 CPU 调度。我们曾经在写 VS 下写过一些错误代码,一旦运行,就会导致 VS 一段时间没有反应。所谓的没有反应就是因为程序导致系统出现问题,操作系统在处理问题区间,把 VS 进程设置成 !R 状态,操作系统处理完,再把 VS 唤醒。


验证子进程僵尸后,退出结果会保存在 PCB 中 ❓


可以看到在 Linux 2.6.32 源码中,task\_struct 里包含了退出码和退出信息。


![在这里插入图片描述](https://img-blog.csdnimg.cn/cd8957753c544d69b628976bb2644c9c.png)




---


✔ 测试用例一:


同 wait 测试用例二。



#include<stdio.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 count = 5;
while(count)
{
printf(“child is running: %d, ppid: %d, pid: %d\n”, count–, getppid(), getpid());
sleep(1);
}
printf(“child quit…\n”);
exit(0);
}
//father
sleep(8);
pid_t ret = waitpid(id, NULL, 0);//只有一个进程,同waitpid(-1, NULL, 0)
printf(“father wait done, ret: %d\n”, ret);
sleep(3);

return 0;

}


💨运行结果:


![请添加图片描述](https://img-blog.csdnimg.cn/ff9dd63dc0e748a887d2d6f2c09fd4c2.gif)


✔ 测试用例二:


父进程 fork 派生一个子进程干活,父进程通过 status 拿到子进程的退出码,可以知道子进程把活做的怎么样。



#include<stdio.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 count = 5;
while(count)
{
printf(“child is running: %d, ppid: %d, pid: %d\n”, count–, getppid(), getpid());
sleep(1);
}
printf(“child quit…\n”);
//exit(0);//做好了
exit(123);//没做好
}
//father
int status = 0;
pid_t ret = waitpid(-1, &status, 0);
int code = (status >> 8) & 0xFF;
printf(“%d\n”, status);
printf(“father wait done, ret: %d, exit code: %d\n”, ret, code);
if(code == 0)
{
printf(“做好了\n”);
}
else
{
printf(“没做好\n”);
}

return 0;

}


💨运行结果:


![请添加图片描述](https://img-blog.csdnimg.cn/216d9e50ba0d48cbba27e3e336116019.gif)


(31488)10 = (0111 1011 0000 0000)2 ;


0111 1011 0000 0000 >> 8 = 0111 1011;


(0111 1011)2 = (123)10 ;


子进程已经退出了,子进程的退出码放在哪 ❓


换句话说,父进程通过 waitpid 要拿子进程的退出码应该从哪里去取呢,明明子进程已经退出了。子进程是结束了,但是子进程的状态是僵尸,也就是说子进程的相关数据结构并没有被完全释放。当子进程退出时,进程的 task\_struct 里会被填入当前子进程退出时的退出码,所以 waitpid 拿到的 status 值是通过 task\_struct 拿到的。


✔ 测试用例三:


针对测试用例二,父进程无非就是想知道子进程的工作完成的结果,那全局变量是否可以作为子进程退出码的设置,以此告知父进程子进程的退出码。



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

int code = 0;

int main()
{
pid_t id = fork();
if(id == 0)
{
int count = 5;
while(count)
{
printf(“child is running: %d, ppid: %d, pid: %d\n”, count–, getppid(), getpid());
sleep(1);
}
printf(“child quit…\n”);
code = 123;
exit(0);
}
//father
int status = 0;
pid_t ret = waitpid(-1, &status, 0);
printf(“father wait done, ret: %d, exit code: %d\n”, ret, code);
if(code == 0)
{
printf(“做好了\n”);
}
else
{
printf(“没做好\n”);
}

return 0;

}


💨运行结果:


![请添加图片描述](https://img-blog.csdnimg.cn/42bacc5ed15843b2b0aec4f2729689ae.gif)


很显然,不可以。这里对于全局变量,发生了写时拷贝,在进程地址空间里我们说过父子是具有独立性的,虽然变量是同一个,但实际上子进程或父进程所写的数据,它们都是无法看到彼此的,所以不可能让父进程拿到子进程的退出结果。


✔ 测试用例四:


模拟异常终止 —— 野指针。



#include<stdio.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 count = 5;
while(count)
{
printf(“child is running: %d, ppid: %d, pid: %d\n”, count–, getppid(), getpid());
sleep(1);
//err
int* p = 0x12345;
*p = 100;
}
printf(“child quit…\n”);
exit(123);
}
//father
int status = 0;
pid_t ret = waitpid(-1, &status, 0);
int code = (status >> 8) & 0xFF;
int sig = status & 0x7F;//0111 1111
printf(“father wait done, ret: %d, exit code: %d, sig: %d\n”, ret, code, sig);

return 0;       

}


💨运行结果:


![请添加图片描述](https://img-blog.csdnimg.cn/475699fdbec243b0badbc6f2da8d0427.gif)


![在这里插入图片描述](https://img-blog.csdnimg.cn/d660d971445541019cfeb6067fced449.png)


子进程崩溃后,立马退出,变成僵尸,并不会影响父进程,这叫做父子具有独立性,父进程等待成功 (不管你是正常还是非正常退出),随后进行回收。此时子进程的退出码是无意义的,子进程的异常终止导致父进程获得了子进程退出时的退出信号,我们发现它的信号是第 11 号信号(SIGSEGV),它一般都是段错误。


✔ 测试用例五:


模拟异常终止 —— 使用`kill -9`信号亲手杀死子进程。



#include<stdio.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 count = 50;
while(count)
{
printf(“child is running: %d, ppid: %d, pid: %d\n”, count–, getppid(), getpid());
sleep(1);
}
printf(“child quit…\n”);
exit(123);
}
//father
int status = 0;
pid_t ret = waitpid(-1, &status, 0);
int code = (status >> 8) & 0xFF;
int sig = status & 0x7F;//0111 1111
printf(“father wait done, ret: %d, exit code: %d, sig: %d\n”, ret, code, sig);

return 0;                                                                       

}


💨运行结果:


![请添加图片描述](https://img-blog.csdnimg.cn/3d442ddd79df4b96b8a15d20fc905e9e.gif)


当我们把正在运行的子进程亲手杀掉后,父进程立马做回收工作,此时退出码是什么已经不重要了,父进程拿到的信号是第 9 号信号(SIGKILL),此时我们就知道子进程连代码都没跑完,是被别人杀掉才退出的。


✔ 测试用例六:


父进程完整的等待子进程的全过程。



#include<stdio.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 count = 5;
while(count)
{
printf(“child is running: %d, ppid: %d, pid: %d\n”, count–, getppid(), getpid());
int* p = 0x12345;
*p = 100;
sleep(1);
}
printf(“child quit…\n”);
exit(123);
}
//father
int status = 0;
pid_t ret = waitpid(id, &status, 0);
if(ret > 0)
{
printf(“wait success!\n”);
if((status & 0x7F) == 0)//正常退出
{
printf(“process quit normal!\n”);
printf(“exit code: %d\n”, (status >> 8) & 0xFF);
}
else//信号退出
{
printf(“process quit error!\n”);
printf(“sig: %d\n”, status & 0x7F);
}
}

return 0;

}


💨运行结果:


正常,


![请添加图片描述](https://img-blog.csdnimg.cn/bfb27b41dfc3419ca1e5d582ffbb65ed.gif)


异常,


![请添加图片描述](https://img-blog.csdnimg.cn/290dde1dd4d74fc48baa0b9361f3e2f7.gif)


✔ 测试用例七:


可以看到需要对数据进行加工才可以获取退出码和退出信号,比较麻烦,我们一般也不会自己加工。其实系统有提供一些宏(函数),可以直接使用,我们主要学习 3 个 —— WIFEXITED(status)、WEXITSTATUS(status)、WTERMSIG(status),其相关介绍可在 waitpid 手册里查找。


![在这里插入图片描述](https://img-blog.csdnimg.cn/42cb68757a6940a1a698f738108f3085.png)



#include<stdio.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 count = 5;
while(count)
{
printf(“child is running: %d, ppid: %d, pid: %d\n”, count–, getppid(), getpid());
//err
//int* p = 0x12345;
//*p = 100;
sleep(1);
}
printf(“child quit…\n”);
exit(123);
}
//father
int status = 0;
pid_t ret = waitpid(id, &status, 0);
if(ret > 0)
{
printf(“wait success!\n”);
if(WIFEXITED(status))//查看进程是否正常退出,是则真,不是则假。同(status & 0x7F) == 0
{
printf(“normal quit!\n”);
printf(“quit code: %d\n”, WEXITSTATUS(status));//查看进程退出码。同(status >> 8) & 0xFF
}
else
{
printf(“process quit error!\n”);
printf(“sig: %d\n”, WTERMSIG(status));//查看导致子进程终止的信号的编号。同status & 0x7F
}
}

return 0;

}


💨运行结果:


正常,


![请添加图片描述](https://img-blog.csdnimg.cn/1577e6a9f56f4d5c9f94a6aeb8b99f9c.gif)


异常,


![请添加图片描述](https://img-blog.csdnimg.cn/f1c87dcf03d840a68638942ca6516bfd.gif)


✔ 测试用例八:


非阻塞等待。



#include<stdio.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 count = 3;
while(count)
{
printf(“child is running: %d, ppid: %d, pid: %d\n”, count–, getppid(), getpid());
//err
//int* p = 0x12345;
//*p = 100;
sleep(1);
}
printf(“child quit…\n”);
exit(123);
}

//father 非阻塞等待
int status = 0;
while(1)
{
    pid_t ret = waitpid(id, &status, WNOHANG);
    if(ret == 0)//等待失败,继续等待
    {
        printf("wait next!\n");
        printf("father do other thing!\n");
    }
    else if(ret > 0)//等待成功
    {
        printf("wait success, ret: %d, code: %d\n", ret, WEXITSTATUS(status));
        break;
    }
    else//等待失败,真的失败
    {
        printf("wait failed!\n");
        break;
    }
}

//father 阻塞等待
//int status = 0;
//pid\_t ret = waitpid(id, &status, 0);
//if(ret > 0)
//{
// printf("wait success!\n");
// if(WIFEXITED(status))
// {
// printf("normal quit!\n");
// printf("quit code: %d\n", WEXITSTATUS(status));
// }
// else
// {
// printf("process quit error!\n");
// printf("sig: %d\n", WTERMSIG(status));
// }
//}

return 0;

}


💨运行结果:


![请添加图片描述](https://img-blog.csdnimg.cn/e20f119a63c041ce85720c0506c3f363.gif)


#### 三、进程替换


###### 💦 为什么要进程替换 && 什么是进程替换


![在这里插入图片描述](https://img-blog.csdnimg.cn/accbfb07f7364e0dbb76ad98049d5a16.png)


创建子进程的目的:


* 执行父进程的部分代码

 我们之前所写的代码都属于这种情况。
* 执行其它程序的代码

 不要父进程的代码和数据。所以我们要学习进程替换。


所以进程替换是为了子进程能够执行其它程序的代码;进程替换就是以写时拷贝的策略,让第三方进程的代码和数据替换到父进程的代码和数据,给子进程用,因为进程间具有独立性,所以不会影响父进程。以前我们说数据是可写的,代码是不可写的,现在看来代码确实是不可写的,但是接下来要把其它程序的代码放在内存里让子进程看到,以前子进程看到的是父进程的代码,现在有新的代码来了,此时也要对代码进行写时拷贝。99% 的情况是对数据进行写时拷贝,代码只读。1% 的情况代码依旧是只读,本质就是对父进程不可写,而子进程后续调用某些系统调用,实际是给子进程重新开辟空间,把新进程的代码加载,此时就不让子进程执行父进程的代码,这个过程叫做程序替换。


###### 💦 替换原理


![在这里插入图片描述](https://img-blog.csdnimg.cn/cc7d29bb831047bf95a2cb214f4d6665.png)


我们想让子进程里执行新的程序,可以一步到位在内存里重新开辟两块空间以加载新程序的代码和数据,再修改子进程页表的映射关系,之后父子就彻底脱离了,这个过程就是进程替换。


系统是如何做到重新建立映射关系的呢 ❓


当子进程里要加载新进程时,操作系统可以设置一些特殊信号让该进程对全部代码和数据的写入,子进程会自动触发写时拷贝,重新开辟空间,再重新把代码和数据加载。


在进行进程替换时,有没有创建新的进程 ❓


我们并不需要重新开辟新的 PCB、地址空间、页表,没有创建新进程的最有力证据是 pid 没变。所以我们曾经说过,程序要运行起来,必须先加载到内存,这句话当然没问题。但是反过来,程序只要加载到内存了,一定是变成一个进程,这句话有纰漏,因为进程是否是新创建是不一定的。不过大部分情况下是创建新进程的,进程替换是属于少数。


所以进程替换不会改变进程内核的数据结构,只会修改部分页表数据,然后把新进程的代码和数据加载至内存,重新构建页表映射关系,和父进程彻底脱离。


###### 💦 替换函数



其实严格来说有7种以exec开头的系列函数,统称exec函数:

#include<unistd.h>

int execl(const chaar* path, const char* arg, …);
int execlp(const char* file, const chr* arg, …);
int execv(const char* path, char* const argv[]);
int execvp(const char* file, char* const argv[]);
int execle(const chra* path, const char* arg, …, char* const envp[]);
int execve(const char* path, char* const argv[], char* const envp[]);
int execvpe(const char* file, char* const argv[], char* const envp[]);


这些函数的功能都是一样的,如果用 C++ 去设计这样的接口,一定是重载。这里是使用 C 去设计的,函数名的命名也有区分。下面我们会对这些接口进行演示,但实际在后面常用的也只是部分而已。


为什么 execve 要单独拎出来 ❓


![在这里插入图片描述](https://img-blog.csdnimg.cn/640bc125502a4f1682c5479e09c9ad74.png)


虽然头文件都是 <unistd.h>,但实际上真正是系统提供函数只有 execve,其余的 6 个都是封装的,最后底层调用的依旧是 execve,这样做的原因是需要根据不同的用户来定制不同的使用场景。好比,大家最后吃的米饭都会转换成能量,但是有的人喜欢蛋炒饭、有的人喜欢肉丝炒饭。


###### 💦 函数解释及使用


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


✔ 测试用例一:


![在这里插入图片描述](https://img-blog.csdnimg.cn/b46771daac6d44768235364c544fe7fa.png)


单进程,父进程亲自干活:



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

int main()
{
printf(“my process begin!\n”);
execl(“/usr/bin/ls”, “ls”, “-a”, “-l”, “-i”, NULL);
printf(“my process end!\n”);
return 0;
}


多进程,父进程创建子进程干活:



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

int main()
{
pid_t id = fork();
if(id == 0)
{
printf(“I am child, pid: %d, ppid: %d\n”, getpid(), getppid());
execl(“/usr/bin/ls”, “ls”, “-a”, “-l”, “-i”, NULL);
exit(1);//这里一定是替换失败
}
//father
int status = 0;
pid_t ret = waitpid(id, &status, 0);
if(ret > 0)
{
printf(“child status -> sig: %d, code: %d\n”, status & 0x7F, (status >> 8) & 0xFF);
}
else
{
printf(“wait error!\n”);
}

return 0;                                                                              

}


💨运行结果:


![在这里插入图片描述](https://img-blog.csdnimg.cn/b65b16c1702044ba8287c428f7438ebf.png)


![在这里插入图片描述](https://img-blog.csdnimg.cn/f46418f63871454f9e39ffe64e562f36.png)


为什么图一没有输出 my process end ! && 图二的退出码是 0 ❓


因为在这之前 execl 已经程序替换了,所以 execl 后面的代码已经不是当前进程的代码了,所以图二获取到的退出码 0 是 ls 的退出码。换言之,一旦程序替换,你到底执行正确与否是取决于 ls 程序。


所以 exec 系列的函数不用考虑返回值,只要返回了,一定是这个函数调用或程序替换失败了。注意编程规范是父进程创建子进程干活。


![在这里插入图片描述](https://img-blog.csdnimg.cn/4ee69f86cf704c69b563e81c77ed7106.png)


加载器 ❓


![在这里插入图片描述](https://img-blog.csdnimg.cn/1ecf39d2a0b34bd28fd6ef6cac373329.png)


一个完整的集成开发环境的组件肯定包括编辑器、编译器、调试器、加载器等。一个软件被加载到内存里,肯定是运行起来,形成进程,进程再调用 exec 系列的函数就可以完成加载的过程。所以 exec 可以理解成一种特殊的加载器。


✔ 测试用例二:


![在这里插入图片描述](https://img-blog.csdnimg.cn/86dd6093edba472e92c9eb4fc9559f3e.png)


execv 与 execl 较为类似,它们的唯一差别是,如果需要传多个参数,那么:execl 是以可变参数的形式进行列表传参;execv 是以指针数组的形式传参。



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

int main()
{
pid_t id = fork();
if(id == 0)
{
//const char* const my_argv[] = {“ls”, “-l”, “-a”, “-i”, NULL};//err,注意要与函数原型的参数类型char* const argv[]匹配
char* const my_argv[] = {“ls”, “-l”, “-a”, “-i”, NULL};
printf(“I am child, pid: %d, ppid: %d\n”, getpid(), getppid());
execv(“/usr/bin/ls”, my_argv);
exit(1);
}
//father
int status = 0;
pid_t ret = waitpid(id, &status, 0);
if(ret > 0)
{
printf(“child status -> sig: %d, code: %d\n”, status & 0x7F, (status >> 8) & 0xFF);//同WTERMSIG(status), WEXITSTATUS(status)
}
else
{
printf(“wait error!\n”);
}

return 0;

}


💨运行结果:


![在这里插入图片描述](https://img-blog.csdnimg.cn/07b59006035847239f2d1df09939acd7.png)


✔ 测试用例三:


![在这里插入图片描述](https://img-blog.csdnimg.cn/6c24800af0ae474fbc122f85a7a4ae8f.png)


execlp 相比 execl 在命名上多了 1 个 p,且参数只有第 1 个不同:不同点在于 execlp 不需要带路径,execlp 在执行时,它会拿着你要执行的程序自动的在系统 PATH 环境变量中查找你要执行的目标程序。



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

int main()
{
pid_t id = fork();
if(id == 0)
{
printf(“I am child, pid: %d, ppid: %d\n”, getpid(), getppid());
execlp(“ls”, “ls”, “-a”, “-i”, “-l”, NULL);
exit(1);
}
//father
int status = 0;
pid_t ret = waitpid(id, &status, 0);
if(ret > 0)
{
printf(“child status -> sig: %d, code: %d\n”, WTERMSIG(status), WEXITSTATUS(status));
}
else
{
printf(“wait error!\n”);
}

return 0;

}


💨运行结果:


![在这里插入图片描述](https://img-blog.csdnimg.cn/277cc5e67c274fcc81fcc10a247d9fd4.png)


带 p 的含义就是不用带路径,系统会自动搜索你要执行的程序,不带 p 则相反。所以你要执行哪个程序,背后的含义是 a) 你在哪 b) 你是谁。可见 execlp 就是 b,execl 就是 ab。当然这里的搜索默认只有系统的命令才能找到,如果需要执行自己的命令,需要提前把自己的命令与 PATH 关联。


✔ 测试用例四:


![在这里插入图片描述](https://img-blog.csdnimg.cn/cc532e6f08d545bdaaf4edba19ec9191.png)  
 所以 execvp 无非就是不带路径,使用指针数组传参。



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

int main()
{
pid_t id = fork();
if(id == 0)
{
先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前在阿里

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Linux运维全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上运维知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

需要这份系统化的资料的朋友,可以点击这里获取!

a247d9fd4.png)

带 p 的含义就是不用带路径,系统会自动搜索你要执行的程序,不带 p 则相反。所以你要执行哪个程序,背后的含义是 a) 你在哪 b) 你是谁。可见 execlp 就是 b,execl 就是 ab。当然这里的搜索默认只有系统的命令才能找到,如果需要执行自己的命令,需要提前把自己的命令与 PATH 关联。

✔ 测试用例四:

在这里插入图片描述
所以 execvp 无非就是不带路径,使用指针数组传参。

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

int main()
{
    pid_t id = fork();
    if(id == 0)
    {
**先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前在阿里**

**深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!**

**因此收集整理了一份《2024年最新Linux运维全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。**
[外链图片转存中...(img-2aevAkxg-1714584878793)]
[外链图片转存中...(img-yoqkNK8C-1714584878794)]
[外链图片转存中...(img-w2vMGqx5-1714584878795)]
[外链图片转存中...(img-Z42HoXVZ-1714584878795)]
[外链图片转存中...(img-XjnfvRRV-1714584878795)]

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上运维知识点,真正体系化!**

**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**

**[需要这份系统化的资料的朋友,可以点击这里获取!](https://bbs.csdn.net/topics/618542503)**

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值