Linux进程替换 自主shell程序

        本篇将要讲解有关进程中最后一个知识点——进程替换,其中主要介绍有关进程替换的六个函数,直接从函数层面来理解进程替换(在使用函数的过程中,也会对进行替换进行解释)。本篇主要围绕如下的进程替换函数:

        以上的 exec* 函数就是 Linux 中的加载函数,可以将进程加载到内存中。

1. 进程替换函数

execl 函数

        我们将首先介绍 execl 函数,关于该函数的参数列表如下:

int execl(const char *path, const char *arg, ...);

        如上,该函数是一个可变参数列表,第一个参数为路径(需要替换的进程的目录,应该去哪找到该进程),接下来的参数的类型都是 const char* ,需要传入的参数为进程的名字,进程需要带上的参数(也就是需要告诉函数,要求替换进程怎么执行),最后在结束的位置加上 NULL 即可。具体的使用如下:

        如上所示,当我们运行 execl 函数的时候,接下来会运行我们替换后的进程。但是需要注意的一点,我们在进程替换代码的后面也加入了一行打印代码,但是在运行之后并没有将其打印出来。这是为什么呢?

        这是因为我们进行了程序替换。在每个进程加载的时候,都会加载对应的页表,地址空间,在内存中开辟空间,以及建立各种的映射关系。当我们调用 execl 函数的时候,会在磁盘中找到对应的进程,然后使用该进程的代码和数据覆盖原有的代码和数据,但是并不会覆盖掉原进程的内核管理数据结构(其中个别的数据将会发生变化),所以进程替换的时候,并不会创建新进程(老进程壳子、新进程运行)。所以以上的代码执行完 execl 函数之后,不会在运行之后的代码了,因为以已经被覆盖了。

多进程运行程序

        通过以上函数可知,我们可以使用系统调用函数将进程进行替换,那么结合我们之前的 fork 创建子进程,我们就可以使用 fork 创建子进程,然后使用程序替换,让子进程运行我们想要运行的程序,这样就达到了多进程运行程序的效果,如下:

        如上所示,我们可以在子进程中进行程序替换,运行我们想要运行的程序。这样发生的原理又是什么呢?

        当我们进行程序替换的时候,会对子进程的代码和数据进行写时拷贝(因为和父进程共享的,不能直接覆盖原来的代码数据),从磁盘中写入我们想要运行进程的代码和数据。父进程在外可以进行阻塞等待和非阻塞等待,也就是可以做自己的事情,若还想要同时运行多个进程,可以创建多个子进程,然后进行替换。

execv 函数

        现在我们将开始讲解 execv 函数,关于该函数的参数形式如下:

int execv(const char *path, char *const argv[]);

        其中,我们需要传入的参数分别为:需要替换进程的地址(目录),第二个参数是一个指针数组,里面存的是我们要运行进程的方式,和以上的 execl 的形式一样,但是需要注意的是,指针数组的最后一个元素得是:NULL,使用如下:

        如上,我们使用 execv 函数成功替换了子进程。其实这些函数的使用都大同小异,我们只需要掌握其中一两个主要的用法,其余也就无师自通了。

execvp execlp 函数

        接下来我们将讲解 execvp 和 execlp 这两个函数,其函数参数形式如下:

int execlp(const char *file, const char *arg, ...);
int execvp(const char *file, char *const argv[]);

        以上两个函数分别相对于 execv 函数和 execl 函数多了一个 p ,其实是多了一个环境变量,也就是我们不需要在第一个参数中传入路径了,直接传入我们想要执行的语句,后面的参数形式,根据以上 execv 和 execl 函数形式传参即可,如下:

        如上所示,传参形式也是和 execv execl 函数相差无几。

execvpe 函数

        现在我们来讲解我们将要讲解的最后一个函数:execvp 函数,如下:

int execvpe(const char *file, char *const argv[], char *const envp[]);
int execle(const char *path, const char *arg, ..., char * const envp[]);

        如上所示,其实和 execvp execl 函数相比,只多了一个 envp 参数,也就是我们的环境变量,在以下只演示一种函数的使用即可,另一种的使用方法大同小异,就不在演示,如下:

        如上所示,我们在我们的c语言程序中替换了一个C++程序,然后传入环境变量与参数,我们在C++程序中也在命令行中使用 argv 和 env 变量接收了命令行参数和环境变量。所以在 execvpe 函数中的 envp 变量可以将环境变量给替换掉,将程序中的环境变量替换为我们想要输入的环境变量(若我们想要传入bash中的环境变量,我们只需要传入environ进去即可)。

execve 系统调用

        我们先查看我们使用的进程替换所在的手册,如下:

        如上显示的为 3 好手册,是我们的 C语言中的库,说明这些进程替换函数都是来自于C语言,其中的底层调用的是系统调用:execve:

        其中,关于这些函数的关系如下:

 2. 自主shell -- code

        在我们在 Linux 中使用的命令行解释器(bash)就是一个 shell 程序,其中主要负责解释我们输入的命令,将其反馈给我们用户。比如我们常用的 ls pwd touch 指令,但其实这些指令都是进程,那么关于 bash 运行这些指令,其实在底层中就是使用进程替换函数实现的,现在我们将在下文中使用C语言实现一个简陋版的 shell 程序(主要依靠进程替换)。我们先给出我们所有的代码,然后在下文中解释:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>#define SIZE 512 
#define NUM 32char cwd[SIZE * 2];
char* MyArgv[NUM];
char tmp[SIZE * 2];
int lastcode = 0;const char* GetUserName(){const char* name = getenv("USER");if(name == NULL) return "None";return name;
}const char* GetHostName(){const char* hostname = getenv("HOSTNAME");if(hostname == NULL) return "None";return hostname;
}const char* GetCwd(){const char* cwd = getenv("PWD");if(cwd == NULL) return "None";return cwd;
}void MakeCommandLine(){const char* name = GetUserName();const char* hostname = GetHostName();const char* cwd = GetCwd();if(strlen(cwd) != 1){cwd += strlen(cwd) - 1;while(*cwd != '/')cwd--;cwd++;}printf("[%s@%s %s]> ", name, hostname, cwd);
}char* GetCommandFromStdin(){char* command = fgets(tmp, sizeof(tmp), stdin);if(command == NULL) exit(1);command[strlen(command) - 1] = '\0';return command;
}void SplitComandToArgv(char* command){// 开始截取MyArgv[0] = strtok(command, " ");//MyArgv[0] = command;//char* tmp = strtok(command, " ");//tmp = '\0';int index = 1;while((MyArgv[index++] = strtok(NULL, " "))){}MyArgv[index] = NULL;
}void ExecuteCommand(char* cmd){pid_t id = fork();if(id < 0) exit(1);else if(id == 0){execvp(MyArgv[0], MyArgv);exit(errno);}else{int status = 0;pid_t rid = waitpid(id, &status, 0);if(rid > 0){lastcode = WEXITSTATUS(status);if(lastcode != 0)printf("%s:%s:%d\n", MyArgv[0], strerror(lastcode), lastcode);}}
}void ChangeDir(){char* path = MyArgv[1];if(strcmp(MyArgv[1], "-") == 0) path = getenv("HOME");chdir(path);// 更新环境变量getcwd(tmp, sizeof(tmp));snprintf(cwd, sizeof(cwd), "PWD=%s", tmp);putenv(cwd);
}int BuildInCommand(){int yes = 0;if(strcmp(MyArgv[0], "cd") == 0){yes = 1;ChangeDir();}return yes;
}int main(){// 先创建一个命令行输入int quit = 1;while(quit != 0){MakeCommandLine();    // 创建一个读入字符串char* command = GetCommandFromStdin();//printf("%s", command);// 将command指令截取到argv表格中// 检测是否存在重定向//CheckRedir(command);// printf("cmd: %s\n", command);// printf("file: %s\n", command + RedirPos);SplitComandToArgv(command);// int i = 0;// for(; MyArgv[i]; i++){//    printf("%s\n", MyArgv[i]);// }// 截取命令之后,就可以开始执行程序了int buildin = BuildInCommand();if(buildin) continue;ExecuteCommand(command);}return 0;
}

命令行读取命令

        首先我们需要先模拟出在命令行读取命令,其中还包括要显示出我们当前所在的目录,这就会涉及到我们的在之前文章中所提到的环境变量了(Linux环境变量-CSDN博客),我们需要从环境变量中找出当前所在的目录,以及当前的用户,实现如下:

const char* GetUserName(){const char* name = getenv("USER");if(name == NULL) return "None";return name;
}const char* GetHostName(){const char* hostname = getenv("HOSTNAME");if(hostname == NULL) return "None";return hostname;
}const char* GetCwd(){const char* cwd = getenv("PWD");if(cwd == NULL) return "None";return cwd;
}void MakeCommandLine(){const char* name = GetUserName();const char* hostname = GetHostName();const char* cwd = GetCwd();if(strlen(cwd) != 1){cwd += strlen(cwd) - 1;while(*cwd != '/')cwd--;cwd++;}printf("[%s@%s %s]> ", name, hostname, cwd);
}

        其中主要使用了能寻找到环境变量的返回值的系统调用函数 getenv。以上为函数为在命令行打印读取命令行信息的提示,接下来我们还需要读取我们的指令,其中的指令是以字符串的形式传入的,如下:

char* GetCommandFromStdin(){char* command = fgets(tmp, sizeof(tmp), stdin);if(command == NULL) exit(1);command[strlen(command) - 1] = '\0';return command;
}

        其中主要使用的函数为一个文件读取函数,一次性就可以读取一行。

截取命令行 / 进程替换

        我们进行环境变量之前,需要使用空格传入不同的穿,而我们刚刚的读取指令函数读取的是一整行的字符串,所以我们需要将其截取,以空格为间隙将其截取。如下:

void SplitComandToArgv(char* command){// 开始截取MyArgv[0] = strtok(command, " ");int index = 1;while((MyArgv[index++] = strtok(NULL, " "))){}MyArgv[index] = NULL;
}

        截取完命令之后,我们就可以进行进程替换了,进行进程替换,我们只需要使用使用 fork 创建出子进程,然后让父进程进行阻塞等待即可,如下:

void ExecuteCommand(char* cmd){pid_t id = fork();if(id < 0) exit(1);else if(id == 0){execvp(MyArgv[0], MyArgv);exit(errno);}else{int status = 0;pid_t rid = waitpid(id, &status, 0);if(rid > 0){lastcode = WEXITSTATUS(status);if(lastcode != 0)printf("%s:%s:%d\n", MyArgv[0], strerror(lastcode), lastcode);}}
}

        以上的进程替换虽然能替换大部分的指令,但是还是有部分内建命令不能替换,因为内建命令本质是 shell 程序中的一个函数,所以并不能算得上是进程替换,我们只能在函数中检测,然后运行,如下给出了 cd 内建命令的实现,关于其他的内建命令便可由读者自行完成,如下:

void ChangeDir(){char* path = MyArgv[1];if(strcmp(MyArgv[1], "-") == 0) path = getenv("HOME");chdir(path);// 更新环境变量getcwd(tmp, sizeof(tmp));snprintf(cwd, sizeof(cwd), "PWD=%s", tmp);putenv(cwd);
}int BuildInCommand(){int yes = 0;if(strcmp(MyArgv[0], "cd") == 0){yes = 1;ChangeDir();}return yes;
}

测试

         测试结果如下:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/342167.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

体验式营销+旅行文化:品牌海外推广的创新之路

在全球化的时代背景下&#xff0c;体验式营销作为一种新兴的营销方式&#xff0c;以其独特的参与性、互动性和情感共鸣&#xff0c;成为品牌与消费者建立深层次联系的有效手段。而将体验式营销与旅行文化相结合&#xff0c;能够为海外消费者提供独特的品牌体验。本文Nox聚星将和…

HTML+CSS+JS 动态登录表单

效果演示 实现了一个登录表单的背景动画效果,包括一个渐变背景、一个输入框和一个登录按钮。背景动画由多个不同大小和颜色的正方形组成,它们在页面上以不同的速度和方向移动。当用户成功登录后,标题会向上移动,表单会消失。 Code <!DOCTYPE html> <html lang=&q…

独著出书的出版流程是怎样的?

独著出书的出版流程一般包括以下几个步骤&#xff1a; 1. 准备书稿&#xff1a;确保书稿内容完整、准确&#xff0c;并符合出版社的要求。 2. 选择出版社&#xff1a;根据书稿的主题和内容&#xff0c;选择合适的出版社。可以考虑出版社的专业性、声誉和出版范围等因素。 3.…

Qt如何让按钮的菜单出现在按钮的右侧

直接上代码&#xff0c;我们用到了一个eventfilter的函数功能。这个函数比较厉害和重要&#xff0c;大家务必经常拿出来看看。 void MainWindow::initMenu() { QMenu* menuLiXiang new QMenu; QAction* actXiangMuZhangCheng new QAction("项目章程"); …

计算机网络学习记录 应用层 Day6

你好,我是Qiuner. 为记录自己编程学习过程和帮助别人少走弯路而写博客 这是我的 github https://github.com/Qiuner ⭐️ ​ gitee https://gitee.com/Qiuner &#x1f339; 如果本篇文章帮到了你 不妨点个赞吧~ 我会很高兴的 &#x1f604; (^ ~ ^) 想看更多 那就点个关注吧 我…

若依前后端分离Spring Security新增手机号登录

备忘贴 转自&#xff1a;【若依RuoYi短信验证码登录】汇总_数据库_z_xiao_qiang-RuoYi 若依 配置Security: 按照Security的流程图可知&#xff0c;实现多种方式登录&#xff0c;只需要重写三个主要的组件&#xff0c;第一个用户认证处理过滤器&#xff0c;第二个用户认证tok…

vue3引入cesium和olcs

首先引入包 pnpm i olcs; pnpm i -D vite-plugin-cesium pnpm i -S cesium在vite.config.js中配置&#xff0c;参考这位大佬的笔记 添加链接描述 import { defineConfig } from vite import vue from vitejs/plugin-vue import cesium from vite-plugin-cesium; // https://…

操作系统入门系列-MIT6.828(操作系统工程)学习笔记(三)---- xv6初探与实验一(Lab: Xv6 and Unix utilities)

系列文章目录 操作系统入门系列-MIT6.S081&#xff08;操作系统&#xff09;学习笔记&#xff08;一&#xff09;---- 操作系统介绍与接口示例 操作系统入门系列-MIT6.828&#xff08;操作系统工程&#xff09;学习笔记&#xff08;二&#xff09;----课程实验环境搭建&#x…

UE5-AI

AI角色 角色控制器 AI角色必须要一个角色控制器 角色控制器最基本只需要执行行为树&#xff0c;在EventOnPossess后runBehaviorTree 如果要的是一个角色&#xff0c;可以创建一个Character&#xff0c;在类默认设置中可以找到 Pawn->AIControllerClass&#xff0c;在这里…

WebGL开发地理信息系统

WebGL开发地理信息系统&#xff08;GIS&#xff09;是一项复杂且具有挑战性的任务&#xff0c;需要解决一系列技术难点。以下是一些主要的技术难点及其可能的解决方案。北京木奇移动技术有限公司&#xff0c;专业的软件外包开发公司&#xff0c;欢迎交流合作。 1.大规模数据渲染…

PHP序列化、反序列化

目录 一、PHP序列化&#xff1a;serialize() 1.对象序列化 2.pop链序列化 3.数组序列化 二、反序列化&#xff1a;unserialize() 三、魔术方法 ​四、NSSCTF相关简单题目 1.[SWPUCTF 2021 新生赛]ez_unserialize 2.[SWPUCTF 2021 新生赛]no_wakeup 学习参考&#xff1…

数据提取:构建企业智能决策的基石

在数字化时代&#xff0c;数据已成为企业最宝贵的资产之一。而数据提取&#xff0c;作为数据分析和智能决策的第一步&#xff0c;正日益成为企业构建竞争优势的关键环节。本文将探讨数据提取的重要性、方法以及它如何为企业的智能决策奠定坚实基础。 一、数据提取的重要性 洞…

全新市场阶段,Partisia BlockChain 将向 RWA、DeFi 等领域布局

Partisia Blockchain 是一个全新范式的 Layer1&#xff0c;该链通过 MPC 方案来构建链上隐私方案&#xff0c;同时该链通过系列独特且创新的设计&#xff0c;旨在进一步解决目前 Web3 中所面临的不可能三角问题&#xff0c;包括安全性、互操作性和可扩展性&#xff0c;为更多的…

MySQL深分页,limit 100000,10 优化

文章目录 一、limit深分页为什么会变慢二、优化方案2.1 通过子查询优化&#xff08;覆盖索引&#xff09;回顾B树结构覆盖索引把条件转移到主键索引树 2.2 INNER JOIN 延迟关联2.3 标签记录法&#xff08;要求id是有序的&#xff09;2.4 使用between...and... 我们日常做分页需…

拿捏红黑树(C++)

文章目录 前言一、红黑树介绍二、插入操作三、验证红黑树四、红黑树与AVL性能比较与应用五、总体代码总结 前言 我们之前介绍了一种AVL的高阶数据结构&#xff0c;在本篇文章中&#xff0c;我们将会介绍一种与AVL旗鼓相当的数据结构–红黑树。 我们并且会对它的部分接口进行模…

Autoxjs 实践-Spring Boot 集成 WebSocket

概述 最近弄了福袋工具&#xff0c;由于工具运行中&#xff0c;不好查看福袋结果&#xff0c;所以我想将福袋工具运行数据返回到后台&#xff0c;做数据统计、之后工具会越来越多&#xff0c;就弄了个后台&#xff0c;方便管理。 实现效果 WebSocket&#xff1f; websocket是…

动态规划(01背包+并查集)

P1455 搭配购买 题意&#xff1a;就是说有n朵云&#xff0c;每朵云有自己的价钱&#xff08;重量&#xff09;和价值&#xff08;价值&#xff09;&#xff0c;还有我自己现在有钱的数目&#xff08;背包&#xff09;&#xff0c;然后还告诉你&#xff0c;哪几朵云是属于捆绑销…

“独特团购策略引领小程序商城一月狂赚600万“

你是否曾经对那些富有创意且成功的商业模式心生羡慕&#xff0c;最终它们通过非凡的业绩证明了自身的价值&#xff1f;今日&#xff0c;我要分享的是一个独特的小程序商城案例&#xff0c;它凭借一种别出心裁的团购策略&#xff0c;在短短一个月内实现了超过600万的营收&#x…

LeetCode 56 合并区间

本题中可以学到的比较重要的方法 lambda表达式定义自定义比较器Comparator Arrays.sort(intervals,(v0,v1)->{return v0[0] - v1[0];}); (附 : 这种形式也适合于优先队列创建时的自定义比较器定义) 比如&#xff1a; PriorityQueue<Integer> minTop new Priorit…

JAVA小案例-输出100-150中能被3整除的数,每5个换行

JAVA小案例-输出100-150中能被3整除的数&#xff0c;每5个换行 代码如下&#xff1a; public class Continue {/*** continue练习&#xff0c;输出100-150中能被3整除的数&#xff0c;每5个换行* param args*/public static void main(String[] args) {int count 0;//计数器…