1.概念
一个进程被另一个进程替换,想实现这种功能可以通过OS给的一些函数接口来实现。
例:
execl("user/bin/ls","ls","-l","-a",NULL);
使用这个函数后原本进程就被替换为ls了。这种改变进程逻辑的方式就叫程序替换。
2.本质

发生程序替换时,该进程中的数据段与代码段(真实内存)被新进程的数据与代码直接覆盖,然后再调整一下页表的映射关系即可,也就是整个过程中PCB几乎没有进行任何修改。因此程序一但替换成功旧代码和数据就不再存在了。
3.exec*系列的介绍
#include <unistd.h>
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[]);
exec*如果替换失败了旧继续运行源代码了,且只有替换失败了才有返回值-1,否则没有返回值。
exec*系列常用于子进程中,以保证父进程的正常运行。
不影响父进程的核心原因是exec*在加载新进程时会对代码和数值都发生写时拷贝。
加载器:广义上讲就是将进程加载进内存的工具,exec*接口可以算的上是加载器一种。
(1)execl
int execl(const char*path,const char*arg,...);
path:一个绝对路径,对应一个可执行文件。
arg:一个个命令符的字符串,最后要加一个NULL。
例:
execl("usr/bin/ls","ls","-al",NULL);
路径可以是绝对路径,也可以是相对路径,后面的指令格式我们统一用指令的格式即可。
这个可执行文件可以是任何语言的程序,也是我们可以在c语言的环境下调用其他语言的程序
例:
execl("usr/bin/python","python","other.py",NULL);
//参数一为该语言的解释器路径
//参数二位该语言名
//三位我们想要执行的可执行文件(路径)
(2)execlp(const char*file,const char*arg,...);
p代表PATH,即file可以只写文件名,然后execlp会自动在PATH中存的路径中寻找该指令。其他的写法与execl一样。
例:
execlp("ls","ls","-ln",NULL);
(3)execv(const char* path,char*const argv[]);
argv就是类似于命令行参数表的东西
例:
char const*argv[] = {"ls","-la",NULL};
execv("usr/bin/ls",argv);
shell传参的原理:
子进程中的mian函数的参数就是父进程通过调用exec*的接口实现的。
(4)execvp(const char*file,char const *argv[])
execv和execlp的结合
(5)execvpe(const char*file,char const *argv[],char*const envp[])
envp就是环境变量表。
理一下整个调用顺序:父进程给子进程一段代码区间,在里面调用一个exec*接口,之后该子进程就变成新进程,新进程中的main函数参数由exec*的参数提供,其中envp就会把新进程中的环境变量全部覆盖成envp中的,也就是原本的环境变量表直接消失了。
不传envp的话就用原本旧进程中的环境变量表。
添加环境变量的方式:
1.int putenv(char*string)
给当前进程中添加一个环境变量(可以继承给子进程),这个string最好在当前程序(父进程)中定义为全局变量。
2.environ
一个指向当前环境变量表的一个指针,要加extern char** environ声明才能使用。我们可以先putenv新环境变量,然后给exec*传environ为参数即可。
(6)execve
这是OS创建的接口,前面的所有接口都是来至于c语言库。
4.shell底层代码的实现(基本版本)
复习:
(1)fgets(char*arr,int size,FILE*stream)
arr是被输入字符的数组,size是该arr的大小,stream是输入方式
(2)strtok(char*str,const char*delim);
分割字符串,delim是分隔符,str是被分割的字符串,返回每一段的子字符串,要继续切历史字符要传NULL。
(3)string.rfind,从后往前找标识符并返回对应位置的迭代器。
代码:
cpp
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<ctype.h>
using namespace std;
//环境变量的提取
string get_user_name()
{
string name = getenv("USER");
return name.empty() ? "None" : name;
}
string get_host_name()
{
string name = getenv("HOSTNAME");
return name.empty() ? "None" : name;
}
string get_pwd()
{
string name = getenv("HOSTNAME");
return name.empty() ? "None" : name;
return name;
}
//得到路径的最后一个字符
string last_dir()
{
string curr = get_pwd();
if (curr == "/")return curr;
//找到最后一个"/"
size_t pos = curr.rfind("/");
//不要"/",因此+1来不添加"/"
return curr.substr(pos + 1);
}
const int basesize = 1024;
//得到命令行
string make_command_line()
{
char command_line[basesize];
//将format中的字符串的前basesize拷贝给command_line
snprintf(command_line, basesize, "[%s@%s %s]# ",get_user_name().c_str(), get_host_name().c_str(), last_dir().c_str());
return command_line;
}
//打印命令行
void print_command_line()
{
printf("%s", make_command_line().c_str());
fflush(stdout);
}
//得到用户命令
bool get_command_line(char command_buffer[], int size)
{
//以行为单位得到字符串
char* result = fgets(command_buffer, size, stdin);
if (!result)
{
return false;
}
//减一用于去除最后的"\n"
command_buffer[strlen(command_buffer) - 1] = 0;
//什么都不输入就return false直接写一次循环了
if (strlen(command_buffer) == 0) return false;
return true;
}
const int num = 64;
int argc;
char* argv[num];
//分析(拆分)命令格式
void parse_command_line(char command_buffer[], int len)
{
(void)len;
//清0消除上一次的数据
memset(argv, 0, sizeof(argv));
argc = 0;
const char* sep = " ";
argv[argc++] = strtok(command_buffer, sep);
while ((bool)(argv[argc++] = strtok(nullptr, sep)));
//strtok在没有标识符后会返回NULL,此时argc依旧会++,因此要--来除去这一次
argc--;
}
//调用进程执行指令
bool execute_command()
{
pid_t id = fork();
if (id < 0) return false;
if (id == 0)
{
execvp(argv[0], argv);
//exit是用于execvp替换失败的
exit(1);
}
int status = 0;
pid_t rid = waitpid(id, &status, 0);
}
int main()
{
//存命令行的数组
char command_buffer[basesize];
while (true)
{
//1.打印命令行
print_command_line();
//2.输入命令
if (!get_command_line(command_buffer, basesize))
{
continue;
}
//3.系统剪切命令
parse_command_line(command_buffer, strlen(command_buffer));
//4.调用子进程实现指令
execute_command();
}
return 0;
}