【Linux】进程的程序替换、自定义shell命令行解释器
Linux
- 1.进程的程序替换
- 1.是什么?
- 2.C程序调用任何语言所写的程序
- 3.验证程序替换并未创建新进程
- 4.程序替换所有接口说明
- 5.系统调用execve
- 2.自定义shell命令行解释器
- 1.目标
- 2.步骤
- 3.代码
1.进程的程序替换
1.是什么?
进程程序替换是指在操作系统中,将一个进程中正在运行的程序替换为另一个全新的程序的过程,但此替换并非通过创建新进程来实现,而是直接替换对应程序的代码和数据。
直接看现象:
- 在程序替换的过程中,并没有创建新的进程,只是把当前进程的代码和数据用新程序的代码和数据覆盖式的进行替换。
- 一但程序替换成功,就去执行新代码了,原始代码的后半部分就不存在了。
- exec* 系列的函数只有失败有返回值(返回-1),成功时没有返回值(代码被替换掉了,判断没有意义),exec* 系列的函数不用对返回值做判断,只要返回就是失败。
下面是子进程发生进程程序替换的情况:
- 进程之间具有独立性。
- 起初父子进程共享同一份代码和数据,当子进程,程序替换时(本质就是修改代码和数据),代码和数据发生 “写时拷贝”,代码和数据彻底分离。
- 子进程的 task_struct、虚拟地址空间、页表都是拷贝父进程来的,子进程的 task_struct 部分属性需要修改,代码和数据的虚拟地址不变,但是通过页表映射的物理内存地址为 “写时拷贝” 后的新的代码和数据的地址。
命令是任何跑起来的?
- 经过编译、链接的可执行程序要通过 “加载器”,加载到内存中,而任何一个程序要加载到内存都需要变成进程,程序的加载过程本质就是动态创建进程的过程。
- 所有在命令行启动的所有进程,都是 bash 的子进程。在命令行中输入的命是如何跑起来的?答案:bash 创建子进程,父进程 bash 只需要 wait,子进程进行程序替换,替换为新的程序执行,命令就跑起来了。
2.C程序调用任何语言所写的程序
进程程序替换除了能替换命令,还可以替换我们自己写的程序(不限制任何语言)
C语言调用C++
C语言调用 python 脚本
C语言调用 shell 脚本
不管什么语言所写的代码,运行时都是进程,只要是进程就能进程程序替换。
3.验证程序替换并未创建新进程
4.程序替换所有接口说明
#include <unistd.h>extern char** environ;
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 execvpe(const char *file, char *const argv[], char *const envp[]);
//这些接口都遵循上面所有的特点
参数解释:
- path:路径 + 程序名(我要执行谁)
- arg:可变参数列表。命令行怎么写,就怎么传(我想怎么执行它)
- file:文件名(含有参数 file 的函数,函数名都存在 p,会自动在环境变量中查找指定的命令,一般都是执行系统的命令,不需要完整的路径)
- argv:命令行参数表,本质就是一个指针数组。
- envp:环境变量表,本质就是一个指针数组。
命名解释:
l(list):表示参数采用列表形式,后面必须以 NULL 结尾。
v(vector):表示参数采用数组形式,
p(path):有 p 自动搜索环境变量PATH
e(env):表示自己维护环境变量
5.系统调用execve
#include<unistd.h>//系统调用
int execve(const char *filename, char *const argv[], char *const envp[]);
上面的6个接口都是语言层面上的,本质调用了execve,用户使用更方便。
2.自定义shell命令行解释器
1.目标
- 能处理普通命令。
- 能处理内建命令(例如:cd 命令,必须由父进程亲自执行,不能交给子进程执行。否则改变的是子进程的路径,父进程的路径不会发生变化)
- 能帮助我们理解内建命令/本地变量/环境变量。
- 能帮助我们理解shell的执行原理。
2.步骤
- 获取命令行。
- 解析命令。
- 创建子进程(fork)
- 替换子进程(execvp)
- 父进程等待子进程退出(waitpid)
3.代码
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<unordered_map>#define COMMAND_SIZE 1024
#define FORMAT "[%s@%s %s]# "//命令行参数表
#define MAXARGC 128
char* g_argv[MAXARGC];
int g_argc = 0;//环境变量表
#define MAX_ENVS 128
char* g_env[MAX_ENVS];
int g_envs = 0;//别名映射表
std::unordered_map<std::string, std::string> alias_list;//for test
char cwd[1024];
char cwdenv[1024];//last exit code
int lastcode = 0;void InitEnv()
{extern char** environ;memset(g_env, 0, sizeof(g_env));g_envs = 0;//1.获取环境变量for(int i = 0; environ[i]; i++){//申请空间g_env[i] = (char*)malloc(strlen(environ[i] + 1));//拷贝环境变量strcpy(g_env[i], environ[i]);g_envs++;}g_env[g_envs++] = (char*)"XZY=123456";g_env[g_envs] = NULL;//2.导入环境变量for(int i = 0; g_env[i]; i++){putenv(g_env[i]);}environ = g_env;
}//获取用户名
const char* GetUserName()
{const char* name = getenv("USER");return name == NULL ? "None" : name;
}//获取主机名
const char* GetHostName()
{const char* name = getenv("HOSTNAME");return name == NULL ? "None" : name;
}//获取当前路径
const char* GetPwd()
{//const char* pwd = getenv("PWD"); 根据环境变量PWD获得当前路径(当cd修改路径时:环境变量不会被修改)const char* ret = getcwd(cwd, sizeof(cwd)); //通过系统调用getcwd:获取当前路径cwdif(ret != NULL) //获取成功时:ret也是当前路径{snprintf(cwdenv, sizeof(cwdenv), "PWD=%s", cwd); //格式化环境变量"PWD=cwd"putenv(cwdenv); //更新环境变量"PWD=cwd"}return ret == NULL ? "None" : cwd;
}//获取家目录
const char* GetHome()
{const char* home = getenv("HOME");return home == NULL ? "NULL" : home;
}//根据当前绝对路径修改为相对路径
std::string DirName(const char* pwd)
{
#define SLASH "/"std::string dir = pwd;if(dir == SLASH) return SLASH;auto pos = dir.rfind(SLASH);if(pos == std::string::npos) return "BUG?";return dir.substr(pos + 1);
}//制作命令行提示符
void MakeCommandLinePrompt(char prompt[], int size)
{//snprintf(prompt, size, FORMAT, GetUserName(), GetHostName(), DirName(GetPwd()).c_str()); snprintf(prompt, size, FORMAT, GetUserName(), GetHostName(), GetPwd());
}//打印命令行提示符
void PrintCommandLinePrompt()
{char prompt[COMMAND_SIZE];MakeCommandLinePrompt(prompt, sizeof(prompt));printf("%s", prompt);fflush(stdout);
}//获取命令
bool GetCommand(char* command, int size)
{char* ret = fgets(command, size, stdin);if(ret == NULL) return false;command[strlen(command) - 1] = '\0'; //清理\nif(strlen(command) == 0) return false;return true;
}//命令解析
bool CommandPrase(char* command)
{
#define SEP " "g_argc = 0;g_argv[g_argc++] = strtok(command, SEP);while((bool)(g_argv[g_argc++] = strtok(NULL, SEP)));g_argc--;return g_argc > 0 ? true : false;
}//打印命令行参数
void PrintArgv()
{for(int i = 0; g_argv[i]; i++){printf("argv[%d]:%s\n", i, g_argv[i]);}printf("argc:%d\n", g_argc);
}//父进程执行cd命令
bool Cd()
{if(g_argc == 1){std::string home = GetHome();if(home.empty()) return true;chdir(home.c_str());}else {std::string where = g_argv[1];if(where == "~"){}else if(where == "-"){}else {chdir(where.c_str());}}return true;
}bool Echo()
{if(g_argc == 2){std::string opt = g_argv[1];if(opt == "$?") //echo $?{std::cout << lastcode << std::endl;lastcode = 0;}else if(opt[0] == '$'){std::string env_name = opt.substr(1);const char* env_value = getenv(env_name.c_str());if(env_value)std::cout << env_value << std::endl;}else {std::cout << opt << std::endl;}}return true;
}//检测并执行内建命令:由父进程执行
bool CheckAndExecBuiltin()
{std::string cmd = g_argv[0];if(cmd == "cd"){Cd();return true;}else if(cmd == "echo"){Echo();return true;}else if(cmd == "export") {//1.在环境变量表中查找环境变量名:是否存在//2.存在修改,不存在新增}else if(cmd == "alias"){//std::string nickname = g_argv[1];//alias_list.insert(k, v)}return false;
}//执行普通命令:由子进程执行
void ExecuteCommand()
{ pid_t id = fork();if(id == 0){//子进程 execvp(g_argv[0], g_argv);exit(1);}//父进程int status = 0;pid_t rid = waitpid(id, &status, 0);if(rid > 0){lastcode = WEXITSTATUS(status);}
}int main()
{//shell启动的时候,需用从系统中获取环境变量//我们的环境变量信息应该从父shell中获取InitEnv();while(true){ //1.输出命令行提示符PrintCommandLinePrompt();//2.获取用户输入的命令char command[COMMAND_SIZE];if(!GetCommand(command, sizeof(command)))continue;//3.命令解析:"ls -a -l" -> "ls"、"-a"、"-l" if(!CommandPrase(command))continue;//PrintArgv();//检测别名//4.检测并处理内建命令if(CheckAndExecBuiltin())continue;//5.执行命令ExecuteCommand();}return 0;
}