我们为什么要创建子进程并且用子进程去进行进程替换?
写个代码来增加理解:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
pid_t id = fork();
if(id == 0)
{
printf("子进程开始运行,pid:%d\n",getpid());
sleep(1);
execl("/usr/bin/ls","ls","-a","-l",NULL);
}
else
{
printf("父进程开始运行,pid:%d\n",getpid());
int status = 0;
pid_t res = waitpid(-1,&status,0);
if(res > 0)
{
printf("等待子进程成功\n");
}
}
return 0;
}
从代码我们可以很容易看出:
我们用子进程做任务,这样就可以让父进程阻塞式等待子进程做任务的结果,父进程只要派发任务即可。
Xshell的模拟实现思路
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
while(1)
{ //1.首先肯定是要一直循环的,因为我们的xshell一直在运行
//2.显示提示行
//3.获取用户输入的字符串
//4.对字符串进行解析
//5.执行以下代码
pid_t id = fork();
if(id == 0)
{
printf("子进程开始运行,pid:%d\n",getpid());
sleep(1);
execl("/usr/bin/ls","ls","-a","-l",NULL);
}
else
{
printf("父进程开始运行,pid:%d\n",getpid());
int status = 0;
pid_t res = waitpid(-1,&status,0);
if(res > 0)
{
printf("等待子进程成功\n");
}
}
}
return 0;
}
如果不创建子进程,那么我们替换的进程只能是父进程。如果创建了子进程,那么我们替换的进程就是子进程,而就算替换的代码出现了什么问题这样也不会影响父进程。
为什么我们不能影响父进程?
因为我们想让父进程聚焦在读取数据,解析数据,指派任务的功能上。
加载子进程程序替换之前,父子数据和代码的关系?
之前我们学到的是:父子进程代码共享,数据写时拷贝
当子进程加载新程序的时候,不就是一种写入吗?代码要不要写时拷贝,将父子代码分离?
必须要!!!
这样父子进程在代码和数据上就彻底分离开了,虽然之前也并不冲突。
学习多种进程替换的函数
execv:
使用:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#define NUM 64
int main()
{
//while(1)
//{ //1.首先肯定是要一直循环的,因为我们的xshell一直在运行
//2.显示提示行
//3.获取用户输入的字符串
//4.对字符串进行解析
//5.执行以下代码
pid_t id = fork();
if(id == 0)
{
printf("子进程开始运行,pid:%d\n",getpid());
sleep(1);
char* const _argv[NUM] = {
(char*)"ls",
(char*)"-a",
(char*)"-l",
NULL
};
execv("/usr/bin/ls",_argv);
//printf("子进程开始运行,pid:%d\n",getpid());
//execl("/usr/bin/ls",_argv[0],_argv[1],_argv[2],NULL);
}
else
{
printf("父进程开始运行,pid:%d\n",getpid());
int status = 0;
pid_t res = waitpid(-1,&status,0);
if(res > 0)
{
printf("等待子进程成功\n");
}
}
//}
return 0;
}
execv其实和execl差不多,知识把要执行的指令保存到数组中罢了。
然后通过传入指令数组给execv表示要执行的指令。
execlp
execlp会自己在环境变量PATH中进行查找,不用告诉该函数执行的程序在哪里,我们只要插入文件名即可。
使用:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#define NUM 64
int main()
{
//while(1)
//{ //1.首先肯定是要一直循环的,因为我们的xshell一直在运行
//2.显示提示行
//3.获取用户输入的字符串
//4.对字符串进行解析
//5.执行以下代码
pid_t id = fork();
if(id == 0)
{
printf("子进程开始运行,pid:%d\n",getpid());
sleep(1);
char* const _argv[NUM] = {
(char*)"ls",
(char*)"-a",
(char*)"-l",
NULL
};
execlp("ls","ls","-a","-l",NULL);
//execv("/usr/bin/ls",_argv);
//printf("子进程开始运行,pid:%d\n",getpid());
//execl("/usr/bin/ls",_argv[0],_argv[1],_argv[2],NULL);
}
else
{
printf("父进程开始运行,pid:%d\n",getpid());
int status = 0;
pid_t res = waitpid(-1,&status,0);
if(res > 0)
{
printf("等待子进程成功\n");
}
}
//}
return 0;
}
这里就有一个问题:
我们第一个参数已经传入ls了,为什么后面还要再传?
第一个ls:代表你要执行谁->找到它
第二个ls:代表你想怎么执行->让程序能知道你要传递什么选项。
execvp
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#define NUM 64
int main()
{
//while(1)
//{ //1.首先肯定是要一直循环的,因为我们的xshell一直在运行
//2.显示提示行
//3.获取用户输入的字符串
//4.对字符串进行解析
//5.执行以下代码
pid_t id = fork();
if(id == 0)
{
printf("子进程开始运行,pid:%d\n",getpid());
sleep(1);
char* const _argv[NUM] = {
(char*)"ls",
(char*)"-a",
(char*)"-l",
NULL
};
execvp("ls",_argv);
//execlp("ls","ls","-a","-l",NULL);
//execv("/usr/bin/ls",_argv);
//printf("子进程开始运行,pid:%d\n",getpid());
//execl("/usr/bin/ls",_argv[0],_argv[1],_argv[2],NULL);
}
else
{
printf("父进程开始运行,pid:%d\n",getpid());
int status = 0;
pid_t res = waitpid(-1,&status,0);
if(res > 0)
{
printf("等待子进程成功\n");
}
}
//}
return 0;
}
execle
多了一个环境变量参数,表示该函数取用环境变量时用该envp[]数组的,而不使用当前环境。
思考一个问题:
如何用程序执行我们自己写的代码?
比如我们要执行一下,自己写的代码,我们要如何用程序执行呢?
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char* argv[])
{
if(argc != 2)
{
printf("can not excuate\n");
exit(1);
}
if(strcmp(argv[1],"-a") == 0)
{
printf("hello a\n");
}
else if(strcmp(argv[1],"-b") == 0)
{
printf("hello b\n");
}
else if(strcmp(argv[1],"-c") == 0)
{
printf("hello c\n");
}
else
{
printf("default!\n");
}
return 0;
}
Makefile一下形成多个可执行程序
改exec.c代码
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#define NUM 64
const char* myfile = "/home/xiaolin/linux/2023/lesson17/mycmd";
int main()
{
//while(1)
//{ //1.首先肯定是要一直循环的,因为我们的xshell一直在运行
//2.显示提示行
//3.获取用户输入的字符串
//4.对字符串进行解析
//5.执行以下代码
pid_t id = fork();
if(id == 0)
{
printf("子进程开始运行,pid:%d\n",getpid());
sleep(1);
/*char* const _argv[NUM] = {
(char*)"ls",
(char*)"-a",
(char*)"-l",
NULL
};*/
/*execvp("ls",_argv);
execlp("ls","ls","-a","-l",NULL);
execv("/usr/bin/ls",_argv);
printf("子进程开始运行,pid:%d\n",getpid());
execl("/usr/bin/ls",_argv[0],_argv[1],_argv[2],NULL);*/
execl(myfile,"mycmd","-a",NULL);
}
else
{
printf("父进程开始运行,pid:%d\n",getpid());
int status = 0;
pid_t res = waitpid(-1,&status,0);
if(res > 0)
{
printf("等待子进程成功\n");
}
}
//}
return 0;
}
运行结果:
我们看到确实成功了,程序运行的结果和linux运行的结果一致。
上面找到我们要的可执行程序文件用的是绝对路径,但是我们也可以用相对路径。
我们看到确实可以改成相对路径。
由上面的知识可知我们也可以用C语言的可执行程序,调用其它语言的可执行程序。
exec*函数功能其实就是加载器的底层接口。
其实OS就提供了一个execve函数,其余的函数都是对这个函数的封装。
之前的接口是系统提供的基本封装,用于满足不同的调用场景,