👍作者主页:进击的1++
🤩 专栏链接:【1++的Linux】
文章目录
- 一,什么是进程替换
- 二,替换函数
- 三,实现我们自己的shell
一,什么是进程替换
我们创建出来进程是要其做事情的,它可以去调用函数,或者是执行其他的程序,子进程通过exec函数族执行其他的程序就叫做进程替换。也就是在调用进程内部执行一个可执行文件。当进程调用一种exec函数时,该进程的代码和数据完全被新程序替换,新程序从main函数开始执行,由于未创建新进程,所以替换前后进程的id等并不改变。
在加载新程序之前,父子进程的关系是:代码共享,数据写时拷贝。
当子进程加载新程序的时候就是一种“写入”,此时代码也就需要进行写时拷贝,进行分离!!!
二,替换函数
下面是六种exec开头的函数,统称exec函数。
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 execve(const char *path, char *const argv[], char *const envp[]);
其中只有execve()是真正意义上的系统调用,其它都是在此基础上经过包装的库函数。
这些函数,如果调用成功则加载新的程序开始执行,不再返回;若调用失败,则返回-1 。
下面是这些函数的演示:
#include<stdio.h>
#include<unistd.h>
int main()
{
//execl---带路径,参数包传参
//execl("/usr/bin/ps","ps","-ef",NULL);
// execlp("ls","-l",NULL);
char* env[]={"PATH=/bin:/usr/bin",NULL};
char* argv[]={"ls","-l",NULL};
// execv("/usr/bin/ls",argv);
// execle("./mike","mike",NULL,env);
// execvp("ls",argv);
execve("/usr/bin/ls",argv,env);
return 0;
}
execl运行结果(要写路径,参数格式未列表)
execlp运行结果(带p的:可以使用环境变量PATH,无需写路径)
execle运行结果:
execv运行结果:
execvp运行结果:
execve运行结果:
我们总结以下:
带p可以使用环境变量PATH,无需写完整路径
带e,自己组装环境变量。// 改变替换程序的环境变量,正确来说,让替换程序只保留 env 的环境变量
带l参数格式为列表
带v参数格式为数组
事实上,只有execve是真正的系统调用,其它五个函数最终都调用 execve,所以execve在man手册 第2节,其它函数在man手册第3节。
exec*实际上就是一个加载器的底层接口。
三,实现我们自己的shell
代码如下:
#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<stdlib.h>
#include<string.h>
#define NUM 32
char cmd_line[NUM];
char* _argv[32];
char myval[32];//这个buffer用来保存我们添加的环境变量,不然保存在cmd_line中会被覆盖。
int main()
{
extern char** environ;//是一个外部的全局变量,储存着系统的全局变量。
while(1)
{
//打印提示信息
printf("[hyp @myshell]#");
fflush(stdout);
memset(cmd_line,'\0',sizeof(cmd_line));
//用户输入
if(fgets(cmd_line,sizeof(cmd_line),stdin)==NULL)
{
continue;
}
cmd_line[strlen(cmd_line)-1]='\0';
//分割字符
_argv[0]=strtok(cmd_line," ");
int i=0;
if(strcmp(_argv[0],"ls")==0)//加颜色
{
_argv[++i]="--color=auto";
}
if(strcmp(_argv[0],"ll")==0)
{
_argv[0]="ls";
_argv[++i]="--color=auto";
_argv[++i]="-l";
}
while(_argv[i])//分割
{
i++;
_argv[i]=strtok(NULL," ");
}
if(strcmp(_argv[0],"export")==0 && _argv[1]!=NULL)
{
strcpy(myval,_argv[1]);
int ret=putenv(myval);
if(ret==0)
{
printf("%s export success\n",myval);
}
continue;
}
if(strcmp(_argv[0],"cd")==0)
{
if(_argv[1]!=NULL)
{
//内置命令,让父进程自己执行的命令,本质就是shell的一个函数调用。
chdir(_argv[1]);//改变当前工作目录
}
continue;
}
int id=fork();
if(id==0)//child
{
printf("child MYVAL:%s\n",getenv("MYVAL"));
printf("PATH:%s\n",getenv("PATH"));
execvp(_argv[0],_argv);
exit(1);
}
//father
int status=0;
int ret=waitpid(id,&status,0);
if(ret>0)
{
printf("退出码:%d\n",WEXITSTATUS(status));
}
}
return 0;
}