当前位置: 首页 > news >正文

【Linux】进程的程序替换、自定义shell命令行解释器

Linux

  • 1.进程的程序替换
    • 1.是什么?
    • 2.C程序调用任何语言所写的程序
    • 3.验证程序替换并未创建新进程
    • 4.程序替换所有接口说明
    • 5.系统调用execve
  • 2.自定义shell命令行解释器
    • 1.目标
    • 2.步骤
    • 3.代码

1.进程的程序替换

1.是什么?

进程程序替换是指在操作系统中,将一个进程中正在运行的程序替换为另一个全新的程序的过程,但此替换并非通过创建新进程来实现,而是直接替换对应程序的代码和数据。

直接看现象:

在这里插入图片描述
在这里插入图片描述

  1. 在程序替换的过程中,并没有创建新的进程,只是把当前进程的代码和数据用新程序的代码和数据覆盖式的进行替换。
  2. 一但程序替换成功,就去执行新代码了,原始代码的后半部分就不存在了。
  3. exec* 系列的函数只有失败有返回值(返回-1),成功时没有返回值(代码被替换掉了,判断没有意义),exec* 系列的函数不用对返回值做判断,只要返回就是失败。

在这里插入图片描述

下面是子进程发生进程程序替换的情况:

在这里插入图片描述
在这里插入图片描述

  1. 进程之间具有独立性。
  2. 起初父子进程共享同一份代码和数据,当子进程,程序替换时(本质就是修改代码和数据),代码和数据发生 “写时拷贝”,代码和数据彻底分离。
  3. 子进程的 task_struct、虚拟地址空间、页表都是拷贝父进程来的,子进程的 task_struct 部分属性需要修改,代码和数据的虚拟地址不变,但是通过页表映射的物理内存地址为 “写时拷贝” 后的新的代码和数据的地址。

命令是任何跑起来的?

  1. 经过编译、链接的可执行程序要通过 “加载器”,加载到内存中,而任何一个程序要加载到内存都需要变成进程,程序的加载过程本质就是动态创建进程的过程。
  2. 所有在命令行启动的所有进程,都是 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[]);
//这些接口都遵循上面所有的特点

参数解释:

  1. path:路径 + 程序名(我要执行谁)
  2. arg:可变参数列表。命令行怎么写,就怎么传(我想怎么执行它)
  3. file:文件名(含有参数 file 的函数,函数名都存在 p,会自动在环境变量中查找指定的命令,一般都是执行系统的命令,不需要完整的路径)
  4. argv:命令行参数表,本质就是一个指针数组。
  5. 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.目标

  1. 能处理普通命令。
  2. 能处理内建命令(例如:cd 命令,必须由父进程亲自执行,不能交给子进程执行。否则改变的是子进程的路径,父进程的路径不会发生变化)
  3. 能帮助我们理解内建命令/本地变量/环境变量。
  4. 能帮助我们理解shell的执行原理。

2.步骤

在这里插入图片描述

  1. 获取命令行。
  2. 解析命令。
  3. 创建子进程(fork)
  4. 替换子进程(execvp)
  5. 父进程等待子进程退出(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;
}

相关文章:

  • 批量将多个文件按扩展名分类到不同文件夹
  • 如何实现动态请求地址(baseURL)
  • 数据库案例1--视图和索引
  • lvs + keepalived + dns 高可用
  • 嵌入式开发
  • 实时数据同步方案
  • 网络安全·第四天·扫描工具Nmap的运用
  • libaom 码率控制实验:从理论到实践的深度探索
  • 水污染治理(生物膜+机器学习)
  • Android离屏渲染
  • ubuntu 常用指令
  • leetcode298.生命游戏
  • E-trace for risc-v
  • 机器视觉检测Pin针歪斜应用
  • 编写了一个专门供强化学习玩的贪吃蛇小游戏,可以作为后续学习的playgraound
  • L1-028 判断素数
  • Python asyncio 入门实战-2
  • 游戏引擎学习第226天
  • 381_C++_decrypt解密数据、encrypt加密数据,帧头和数据buffer分开
  • Nacos-Controller 2.0:使用 Nacos 高效管理你的 K8s 配置
  • 精细喂养、富养宠物,宠物经济掀起新浪潮|私家周历
  • 沈辛成评《主动出击》丨科学普及,究竟需要靠谁主动出击
  • 安徽省合肥市人大常委会原副主任杜平太接受审查调查
  • 上海自然博物馆下月开启中国恐龙大展,还在筹备中国古人类大展
  • 马上评丨敦煌网美国爆火,“市场之腿”总能跨越关税壁垒
  • 外交部:中国将深化同柬埔寨等周边国家友好合作,携手推进亚洲现代化进程