创建子进程的目的:
1.想让子进程执行父进程代码的一部分(执行父进程对应的磁盘代码中的一部分)
2.想让子进程执行全新的程序(让子进程想办法,加载磁盘上指定的程序(进程的程序替换),执行新程序的代码和数据)
我们一共有这么多函数来实现进程程序替换,我们一个一个了解
一、execl
#include<stdio.h>
#include<unistd.h>
int main()
{
printf("process is running\n");
//将程序load到内存
execl("/usr/bin/ls","ls",NULL);
printf("process is done\n");
return 0;
}
我们也可以带上参数,不管可变参数有多少个,最后一定要以NULL结尾
#include<stdio.h>
#include<unistd.h>
int main()
{
printf("process is running\n");
//讲程序load到内存
execl("/usr/bin/ls","ls","--color=auto","-a","-l",NULL);
printf("process is done\n");
return 0;
}
不过我们发现我们不会执行process is done这句话,这是为什么呢?
mm_struct是进程的地址空间,程序替换的本质就是将制定的代码和数据加载到指定的位置,覆盖自己的代码和数据,并且进程替换的时候没有创建新的进程
这个函数在失败的时候返回-1,成功的时候不返回,为什么成功的时候不返回呢?因为后面的代码也被覆盖了,判断也毫无意义
如果我们用fork写会怎么样呢?
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{
pid_t id=fork();
if(id==0)
{
execl("/usr/bin/ls","ls","--color=auto","-a","-l",NULL);
//如果执行到exit表明一定执行失败
exit(-1);
}
int status=0;
pid_t ret=waitpid(id,&status,0);
if(ret>0)
{
printf("wait success,exit code:%d\n",(status>>8)&0xFF);
}
return 0;
}
我们发现父进程正常执行,原因在于进程具有独立性,一旦有执行流想替换代码或者数据,就会发生写时拷贝
二、execlp
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{
printf("process is running\n");
execlp("ls","ls","--color=auto","-a","-l",NULL);
printf("process is done\n");
return 0;
}
三、execv
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{
printf("process is running\n");
char * const argv[]={
"ls",
"--color=auto",
"-a",
"-l",
NULL
};
execv("/usr/bin/ls",argv);
printf("process is done\n");
return 0;
}
四、execvp
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{
printf("process is running\n");
char * const argv[]={
"ls",
"--color=auto",
"-a",
"-l",
NULL
};
execvp("ls",argv);
printf("process is done\n");
return 0;
}
我们可以利用之前的main函数的两个参数来实现一个小命令
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
int main(int argc,char* argv[])
{
//相当于./test ls -a -l .....
//argv[1]就是ls指令,然后&argv[1]从ls包括ls往后读取,直到NULL
execvp(argv[1],&argv[1]);
return 0;
}
五、执行自己的程序
我们可以编写一个run.c
Makefile应该这样写,因为Makefile默认只能一个可执行,要避免这种情况,写成如下方式:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{
printf("process is running\n");
execl("./run","run",NULL);
printf("process is done\n");
return 0;
}
因为是程序替换,可以调用任何的后端语言对应的可执行程序
六、execle
#include<stdio.h>
#include<stdlib.h>
int main()
{
//获取环境变量
printf("PATH:%s\n",getenv("PATH"));
printf("PWD:%s\n",getenv("PWD"));
printf("MYENV:%s\n",getenv("MYENV"));
return 0;
}
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{
printf("process is running\n");
char *const envp[]={
(char*)"MYENV=1234",
NULL
};
//execle("./run","run",NULL,envp); 自定义环境变量
extern char** environ;
execle("./run","run",NULL,environ); //实际上,默认的环境变量你不传,子进程也能获取
printf("process is done\n");
return 0;
}
分别执行第一个execle和第二个execle,我们会发现第一次没有系统的环境变量,但是存在MYENV,第二次调用系统环境变量的时候又没有MYEVN了
如果我们想同时获取系统和自己的环境变量表,我们就需要使用putenv把我们的环境变量表导入到系统的environ中
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{
printf("process is running\n");
char *const envp[]={
(char*)"MYENV=1234",
NULL
};
//execle("./run","run",NULL,envp); 自定义环境变量
extern char** environ;
putenv((char*)"MYENV=4321");
execle("./run","run",NULL,environ); //实际上,默认的环境变量你不传,子进程也能获取
printf("process is done\n");
return 0;
}
我们的main函数也是被exec*系列的函数调用的,因为执行程序之前必须被加载到内存中
七、总结
八、制作简易shell
#include<stdio.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
#define NUM 1024
char LineCommand[NUM];
#define OPT_NUM 64
char* myargv[OPT_NUM];//指针数组
int lastcode=0;
int lastsign=0;
int main()
{
while(1)
{
char LineCommand[NUM]={0};
char* myargv[OPT_NUM]={NULL};
//输出提示符
printf("用户名@主机 当前路径# ");
//刷新缓冲区,因为没有\n不会刷新缓冲区
fflush(stdout);
//获取用户输入,预留一个\0,所以需要-1
char* s=fgets(LineCommand,sizeof(LineCommand)-1,stdin);
assert(s!=NULL);
保证在release方式发布的时候,因为去掉assert了,所以s就没有被使用,而带来的编译告警, 什么都没做,但是充当一次使用
(void)s;
//这里会把用户的\n也输入进去,就会有2个\n
//清楚最后一个\n
LineCommand[strlen(LineCommand)-1]='\0';
//printf("test:%s\n",LineCommand);
//我们输入的是
//"ls -a -l" 但是系统想要读取的是"ls" "-a" "-l"
//字符串切割
//用空格切割
myargv[0]=strtok(LineCommand," ");
int i=1;
if(myargv[0]!=NULL&&strcmp(myargv[0],"ls")==0)
{
myargv[i++]="--color=auto";
}
//如果没有子串strtok会返回NULL
//而我们的exec*系列的函数需要以NULL结束
while(myargv[i++]=strtok(NULL," "));
//子进程运行结束以后,继续用的还是父进程,即shell
if(myargv[0]!=NULL&&strcmp(myargv[0],"cd")==0)
{
//我们执行cd ..没有用,原因在于我们改变的是子进程的目录,父进程的目录没有改变
//这种指令需要我们父进程自己执行,这种指令叫做内置/内建指令
if(myargv[1]!=NULL)
{
chdir(myargv[1]);
continue;
}
}
if(myargv[0]!=NULL&&myargv[1]!=NULL&&strcmp(myargv[0],"echo")==0)
{
if(strcmp(myargv[1],"$?")==0)
{
printf("%d,%d\n",lastcode,lastsign);
}
else
{
printf("%s\n",myargv[1]);
}
continue;
}
//条件编译
#ifdef DEBUG
for(int i=0;myargv[i];++i)
{
printf("myargv[%d]:%s\n",i,myargv[i]);
}
#endif
//执行命令一般给子进程执行
pid_t id=fork();
assert(id>=0);
(void)id;
if(id==0)
{
//child
printf("process is running\n");
execvp(myargv[0],myargv);
printf("process is done\n");
//只要返回一定失败
exit(-1);
}
int status=0;
pid_t ret = waitpid(id,&status,0);
lastcode=(status>>8)&0xff;
lastsign=status&0x7f;
}
return 0;
}