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.mzph.cn/diannao/25186.shtml

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

相关文章

一个管理全局实例的python框架

一个管理全局实例的框架&#xff0c;利用元类&#xff08;metaclass&#xff09;和混合类&#xff08;mixin&#xff09; 。 1 代码实现 这段代码通过元类和混合类的机制提供了一个可以全局访问且线程安全的单例模式框架。这种设计模式在多线程应用程序中非常有用&#xff0c…

QT系列教程(9) 主窗口学习

简介 任何界面应用都有一个主窗口&#xff0c;今天我们谈谈主窗口相关知识。一个主窗口包括菜单栏&#xff0c;工具栏&#xff0c;状态栏&#xff0c;以及中心区域等部分。我们先从菜单栏说起 菜单栏 我们创建一个主窗口应用程序, 在ui文件里的菜单栏里有“在这里输入”的一个…

windows安装conda

1 Conda简介 Conda 是一个开源的软件包管理系统和环境管理系统&#xff0c;用于安装多个版本的软件包及其依赖关系&#xff0c;并在它们之间轻松切换。Conda 是为 Python 程序创建的&#xff0c;适用于 Linux&#xff0c;OS X 和Windows&#xff0c;也可以打包和分发其他软…

Web前端放图片位置:深入探索与最佳实践

Web前端放图片位置&#xff1a;深入探索与最佳实践 在Web前端开发中&#xff0c;图片作为重要的视觉元素&#xff0c;其放置位置往往影响着网页的整体布局和用户体验。然而&#xff0c;如何合理地放置图片&#xff0c;以最大化其视觉效果并提升用户体验&#xff0c;却是一个颇…

mnist的t-SNE二维空间可视化MATLAB

%% filename ‘mnist’; digitDatasetPath fullfile(matlabroot,‘toolbox’,‘nnet’,‘nndemos’, … ‘nndatasets’,‘DigitDataset’); imds imageDatastore(digitDatasetPath, … ‘IncludeSubfolders’,true,‘LabelSource’,‘foldernames’); %% labelCount coun…

【清华大学】《自然语言处理》(刘知远)课程笔记

自然语言处理基础&#xff08;Natural Language Processing Basics, NLP Basics&#xff09; 自然语言处理( Natural Language Processing, NLP)是计算机科学领域与人工智能领域中的一个重要方向。它研究能实现人与计算机之间用自然语言进行有效通信的各种理论和方法。自然语言…

RN:Error: /xxx/android/gradlew exited with non-zero code: 1

问题 执行 yarn android 报错&#xff1a; 解决 这个大概率是缓存问题&#xff0c;我说一下我的解决思路 1、yarn doctor 2、根据黄色字体提示&#xff0c;说我包版本不对&#xff08;但是这个是警告应该没事&#xff0c;但是我还是装了&#xff09; npx expo install --…

进军rust:从0开始学习rust语法

一.变量类型 Rust语言中的基础数据类型有以下几种&#xff1a; 1.整数型 整数型简称整型&#xff0c;按照比特位的长度和有无符号位可以分为以下几种 isize和usize两种整数类型是用来衡量数据大小的&#xff0c;它们的位长度取决于所运行的目标平台&#xff0c;如果是32位架…

Springboot+Vue的网上购物商城系统(前后端分离)

技术栈 JavaSpringBootMavenMySQLMyBatisVueShiroElement-UI 角色对应功能 用户商家 功能截图

Linux服务器配置一个简单的DNS

配置一个简单的DNS服务器可以使用BIND (Berkeley Internet Name Domain)。 配置DNS的步骤如下 一、安装BIND sudo apt-update sudo apt-get install bind9 二、配置BIND。 编辑 /etc/bind/named.conf.local 文件&#xff0c;添加一个简单的zone配置 zone "example.c…

【机器学习】基于图注意力网络(GAT)的Cora数据集论文主题预测

1. 引言 1.1. GAT概述 GAT是一种基于图神经网络的深度学习模型&#xff0c;专门用于处理图结构数据。与传统的神经网络不同&#xff0c;GAT能够直接对图结构数据进行学习和推理&#xff0c;通过捕捉和传递节点之间的关系和特征信息&#xff0c;实现对图结构数据的深度分析和挖…

GPU风扇不旋转:为什么会发生这种情况以及如何修复

GPU在处理数百万像素时往往会发热,因此冷却风扇静音可能会令人担忧,这是可以理解的!如果你注意到你的GPU风扇没有旋转,下面是如何评估是否存在真正的问题,以及如何解决问题。 风扇停止旋转可能是一个功能,而不是一个Bug 如果GPU没有用于密集任务或没有达到高温,则可以…

使用Spring Boot实现Redis多数据库缓存

Redis多数据库存储实现用户行为缓存 在我的系统中&#xff0c;为了优化用户行为数据的存储与访问效率&#xff0c;我引入了Redis缓存&#xff0c;并将数据分布在不同的Redis数据库中。通过这种方式&#xff0c;可以减少单一数据库的负载&#xff0c;提高系统的整体性能。 主要…

GEE案例——利用MODIS数据(NDVI)计算中国大陆2000-2023年的MK、Sens趋势性分析和Z值统计以及方差分析

简介 利用MODIS数据(NDVI)计算2000-2023年中国大陆的MK、Sens趋势性分析和Z值统计以及方差分析 流程 要进行NDVI数据的趋势性分析和统计分析,需要按照以下步骤进行: 1. 数据准备:获取2000-2023年的MODIS NDVI数据,可以从NASA的MODIS数据网站或其他可靠的数据来源获取…

yarn保姆级安装和使用

目录 前言 一、yarn简介 主要特性 使用场景 二、yarn的安装 yarn的下载 配置环境变量 三、yarn的常用命令 四、yarn的常用配置项 五、npm与yarn的区别 前言 本文旨在介绍如何安装和使用Yarn&#xff0c;以及它的一些常见用法。我们将从Yarn的基本概念开始&#xff0c;…

使用try-with-resources语句替代try-finally语句

Java类库中包含许多必须通过调用close方法手动关闭的资源&#xff0c;例如InputStream、OutputStream和java.sql.Connection。 开发人员经常忽视关闭资源&#xff0c;其性能结果可想而知。 从以往来看&#xff0c;try-finally语句是保证资源正确关闭的最佳方式&#xff0c;即使…

Web前端的规划:深度解构与未来展望

Web前端的规划&#xff1a;深度解构与未来展望 在数字化浪潮汹涌的时代&#xff0c;Web前端作为用户与互联网世界的桥梁&#xff0c;其重要性不言而喻。对于前端开发者而言&#xff0c;一份精心规划的Web前端策略&#xff0c;不仅是项目成功的基石&#xff0c;更是技术进步的引…

Foundation Model 通用大模型的评测体系

随着大模型评测需求逐渐增加,相关研究也进一步深入。大模型相比传统模 型&#xff0c;泛化能力更强、灵活性更高、适应性更广&#xff0c;多任务、多场景&#xff0c;评测维度、评测指标和数 据集更复杂&#xff0c;面向大模型的评估方法、评测基准、测试集成为新的研究课题。 …

Qt图表类介绍

本文主要介绍QCharts相关的模块及类。 Qt中图表模块有以下几种类型&#xff1a;折线图&#xff0c;样条曲线图&#xff0c;面积图&#xff0c;散点图&#xff0c;条形图&#xff0c;饼图&#xff0c;方块胡须图&#xff0c;蜡烛图&#xff0c;极坐标图。 QCharts的图表框架类似…

数据Ant-Design-Vue动态表头并填充

Ant-Design-Vue是一款基于Vue.js的UI组件库&#xff0c;广泛应用于前端开发中。在Ant-Design-Vue中&#xff0c;提供了许多常用的组件&#xff0c;包括表格组件。表格组件可以方便地展示和处理大量的数据。 在实际的开发中&#xff0c;我们经常会遇到需要根据后台返回的数据动…