【Linux】剧幕中的灵魂更迭:探索Shell下的程序替换

🎬 个人主页:谁在夜里看海.

📖 个人专栏:《C++系列》《Linux系列》《算法系列》

⛰️ 一念既出,万山无阻


目录

📖一、进程程序替换

1.替换的演示

❓替换与执行流

❓程序替换≠进程替换

2.替换的原理

📚 系统调用exec

📚 进程控制块 (PCB)

📚 内存管理

3. 替换的函数

📚 execl

📚 execv

📚 execp

📚 exece

🚩本质 

📖二、命令行解释器shell

1.shell的本质

2.shell的模拟实现

📚头文件

📚宏定义

📚全局变量

📚获取信息

📚交互式命令行输入

📚字符串分割

📚内置命令

📚普通命令

📚main函数


📖一、进程程序替换

上一篇博客我们讲到了进程的诞生过程:父进程调用fork创建子进程,子进程执行父进程相同的程序。但是很多时候我们希望子进程执行另一个程序,此时就要用到exec函数调用,子进程中调用exec函数之后,该程序就会被调用的程序代替,这就是程序替换

1.替换的演示

#include<stdio.h>
#include<unistd.h>
int main()
{int a = 0;a++;execl("/usr/bin/pwd", "pwd", NULL);printf("%d\n", a++);
}

此时程序执行结果:

我们可以看到,原先的程序执行结果应该是打印变量a,但是被替换成了pwd指令(指令本身也是一个可执行程序),这就是程序替换的过程:当进程调用exec函数时,该进程的代码和数据完全被新程序替换,从新程序的启动例程开始执行。

替换与执行流
int main()
{int a = 0;printf("Before: %d\n",a++);execl("/usr/bin/pwd", "pwd", NULL);printf("After: %d\n", a++);
}

不对呢,不是说程序替换之后原来的代码和数据都会被替换吗,那为什么这里还会显示原程序的打印信息呢?下面进行分析:

✅虽然进程调用exec函数后会发生程序替换,原程序的代码和数据会被覆盖,但在调用 exec 函数之前,执行流还是要经过原来的步骤的,上述代码中,在调用execl之前,执行流先执行printf函数代码,由于以“\n”结尾,输出缓冲区的数据会被刷新到终端,所以我们能看到“Before: 0”:

修改一下代码,结尾不加“\n”, 此时数据会被保留在输出缓冲区当中,后面又因为发生程序替换,缓冲区的内容被清除了,所以最终终端不会显示"Before: 0"内容:

int main()
{int a = 0;printf("Before: %d",a++);execl("/usr/bin/pwd", "pwd", NULL);printf("After: %d\n", a++);
}

❓程序替换≠进程替换

程序替换会改变进程的执行内容,但它不会改变进程的进程ID,也就是说,进程还是原来的进程,程序替换并不是进程替换,且看下面示例:

先写一个可执行程序test2,源代码为:

#include<stdio.h>
#include<unistd.h>int main()
{// 打印当前pid,ppidprintf("After: pid = %d, ppid = %d\n",getpid(),getppid()); 
}

另一个可执行程序test源代码为:

#include<stdio.h>
#include<unistd.h>int main()
{// \n结尾直接打印当前内容printf("Before: pid = %d, ppid = %d\n",getpid(),getppid());// 程序替换成test2execl("/home/ywh/linux_gitee/test_excel/test2", "test2", NULL);
}

test执行结果:

我们可以看到,程序替换前后都是同一个进程,结论:exec并不创建新进程。

2.替换的原理

📚 系统调用exec

exec 系列函数(如 execl, execv, execve 等)是用来将当前进程的内存空间、程序代码段、数据段等替换成一个新的程序。该系统调用不会创建新进程,而是直接用新程序替换当前进程的内容。

具体来说,exec 调用会:

①:清空当前进程的代码段、数据段、堆栈等。

②:加载并执行新程序的代码段、数据段、堆栈等。

③:保留当前进程的进程 ID (PID)、父进程标识符 (PPID)、文件描述符等。

📚 进程控制块 (PCB)

操作系统通过 进程控制块 (PCB) 来管理进程,每个进程都有一个独立的 PCB,包含了进程的各种状态信息,比如进程的 PID、父进程 ID、程序计数器、堆栈指针等。

当调用 exec 时,进程的 PCB 中的状态信息并没有被改变,操作系统只会根据 exec 调用的参数加载新的程序内容(代码段、数据段等),并且更新程序计数器和堆栈指针等信息。

📚 内存管理

操作系统中的内存管理模块负责为进程分配内存。当进程调用 exec 时,操作系统会:

①:释放原进程的内存(代码段、数据段、堆栈)。

②:加载新程序的内存:从磁盘(例如 ELF 文件或其他可执行文件)中加载新的程序到内存,包括新的代码段、数据段等。

③:更新堆栈和堆的布局,准备新程序的运行环境。

3. 替换的函数

其实有六种以exec开头的函数,统称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[]);

为了便于理解,我们可以把exec后面出现的 l、p、e、v 看作exec的四个选项,下面我们依次介绍这些选项:

📚 execl

l(list) : 参数采用列表 

path:表示要执行的程序路径;

arg:表示程序本身的参数,第一个是程序本身的名称,后续为程序的参数(传递系统指令时,参数就是指令的选项),必须以NULL结尾。

示例:

execl("/bin/ls", "ls", "-l", (char *)NULL);
📚 execv

v(vector) : 参数用数组

path:表示要执行的程序路径;

argv:参数列表,程序的参数以数组的形式传递,数组内部也必须以NULL结尾。

示例:

execv("/bin/ls", (char *[]){"ls", "-l", NULL});
📚 execp

p(path) : 自动搜索环境变量PATH

它可以通过环境变量 PATH 来查找可执行文件,而不需要提供绝对路径。

示例:

execlp("ls", "ls", "-l", (char *)NULL);
📚 exece

e(env) : 表示自己维护环境变量 

execle 允许显式地传递一个 环境变量数组,而不是继承当前进程的环境变量。通过 execle,你可以自定义新进程的环境变量。

示例:

char *const envp[] = {"PATH=/bin:/usr/bin", "TERM=console", NULL};
execle("ps", "ps", "-ef", NULL, envp);
🚩本质 

事实上,只有execve才是真正的系统调用,而其他四个函数最后都会调用execve:

📖二、命令行解释器shell

我们在linux学习过程中离不开shell,shell是命令行解释工具,是用户与内核之间的工具,提供了一个接口,通过它,我们可以执行命令、启动程序等与操作系统进行交互。shell解析用户输入的命令,返回执行结果。

❓shell的本质是什么呢?

1.shell的本质

shell本质其实是一个进程

当我们启动一个终端或打开一个命令行窗口的时候,相当于启动了一个shell进程(也叫bash进程),这个进程会等待用户输入的命令,并将命令通过系统调用传递给内核,内核执行相应的操作后,返回给shell。

shell的工作原理就是循环以下操作

1️⃣获取命令行 --> 2️⃣解析命令行 --> 3️⃣fork创建子进程 

--> 4️⃣execve替换子进程 --> 5️⃣wait等待子进程退出 ->1️⃣

根据这些思路,我们可以模拟实现一个shell:

2.shell的模拟实现

实现一个简化版的shell,需要执行以下功能:

① 获取当前工作目录、用户名、主机名。

② 解析用户输入的命令行并执行命令。

③ 内置支持一些常见命令,如cdechoexport等。

④ 创建子进程来执行普通命令,并支持基本的命令分割和管道处理。

📚头文件
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

这些头文件提供了标准输入输出、字符串处理、系统调用等功能。unistd包含与进程相关的函数(如fork,exit)

📚宏定义
#define LEFT "["
#define RIGHT "]"
#define LABLE "#"
#define DELIM " \t"
#define LINE_SIZE 1024
#define ARGC_SIZE 32
#define EXIT_CODE 44

LEFT、RIGHT、LABLE:用于命令行提示符的格式化;

DELIM:用于命令行字符串的分隔符;

LINE_SIZE、ARGC_SIZE:定义了命令行和参数的缓冲区大小;

EXIT_CODE:用于子进程异常退出的返回值。

📚全局变量
int lastcode = 0;
int quit = 0;
extern char **environ;
char commandline[LINE_SIZE];
char *argv[ARGC_SIZE];
char pwd[LINE_SIZE];
char myenv[LINE_SIZE];

lastcode:保存上一个命令的退出码;

quit:用于控制shell是否退出;

commandline:存储用户输入的命令行字符串;

argv:存储解析后的命令和参数;

pwd:保存当前工作目录;

myenv:存储自定义的环境变量。

📚获取信息
const char *getusername() {return getenv("USER");
}const char *gethostname() {return getenv("HOSTNAME");
}void getpwd() {getcwd(pwd, sizeof(pwd));
}

getusername:获取用户名

gethostname:获取主机名

getpwd:获取当前工作目录

📚交互式命令行输入
void interact(char *cline, int size) {getpwd();printf(LEFT"%s@%s %s"RIGHT""LABLE" ", getusername(), gethostname(), pwd);char *s = fgets(cline, size, stdin);assert(s);(void)s;cline[strlen(cline)-1] = '\0';
}

interact函数显示格式化的提示符,并等待用户输入命令。输入命令存储在cline中;输入的命令符末行换行符替换成终止符 '\0'。

📚字符串分割
int splitstring(char cline[], char *_argv[]) {int i = 0;argv[i++] = strtok(cline, DELIM);while(_argv[i++] = strtok(NULL, DELIM));return i - 1;
}

splitstring函数使用strtok将输入的命令行字符串按空格和制表符分割成多个命令或参数,存储在指针数组argv中。

📚内置命令
int buildCommand(char *_argv[], int _argc) {if(_argc == 2 && strcmp(_argv[0], "cd") == 0) {chdir(argv[1]);getpwd();sprintf(getenv("PWD"), "%s", pwd);return 1;}else if(_argc == 2 && strcmp(_argv[0], "export") == 0) {strcpy(myenv, _argv[1]);putenv(myenv);return 1;}else if(_argc == 2 && strcmp(_argv[0], "echo") == 0) {if(strcmp(_argv[1], "$?") == 0) {printf("%d\n", lastcode);lastcode=0;}else if(*_argv[1] == '$') {char *val = getenv(_argv[1]+1);if(val) printf("%s\n", val);}else {printf("%s\n", _argv[1]);}return 1;}if(strcmp(_argv[0], "ls") == 0) {_argv[_argc++] = "--color";_argv[_argc] = NULL;}return 0;
}

提供了几个内置命令:

cd:改变当前目录

export:设置一个新的环境变量

enho:打印变量值或退出码

📚普通命令

队友普通命令的执行,需要调用exec程序替换成目标命令的程序:

void NormalExcute(char *_argv[]) {pid_t id = fork();if(id < 0) {perror("fork");return;}else if(id == 0) {execvp(_argv[0], _argv);exit(EXIT_CODE);}else {int status = 0;pid_t rid = waitpid(id, &status, 0);if(rid == id) {lastcode = WEXITSTATUS(status);}}
}

NormalExcute使用fork创建子进程,子进程调用execvp,替换当前程序,父进程等待子进程结束。

📚main函数
int main() {while(!quit) {interact(commandline, sizeof(commandline));int argc = splitstring(commandline, argv);if(argc == 0) continue;int n = buildCommand(argv, argc);if(!n) NormalExcute(argv);}return 0;
}

main函数进入循环,不断接收用户输入的命令并解析执行。

如果命令是内置命令,则在当前进程中执行;如果是普通命令,通过程序替换在子进程中执行。


以上就是【剧幕中的灵魂更迭:探索Shell下的程序替换】的全部内容,欢迎指正~  

码文不易,还请多多关注支持,这是我持续创作的最大动力! 

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

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

相关文章

【MySQL】数据库开发技术:内外连接与表的索引穿透深度解析

前言:本节内容主要讲解表的内连和外连以及索引的一部分。 注意&#xff1a; 索引是很重要的知识点。务必学习&#xff01;&#xff01;本节将会主要谈一谈什么是索引&#xff0c;如何理解索引。 以及怎么理解MySQL与磁盘的关系。 下面友友们开始学习吧&#xff01; ps&#xff…

Matlab Simulink HDL Coder开发流程(三)— 验证从Simulink模型生成的HDL代码

验证从Simulink模型生成的HDL代码 一、什么是HDL Test Bench&#xff08;测试台&#xff09;二、简单的计数器模型三、验证方法四、生成HDL Test Bench生成VHDL Test Bench生成Verilog Test Bench代码 五、查看HDL Test Bench文件六、运行仿真和验证生成的HDL代码七、在目标设备…

设计模式:11、迭代器模式(游标)

目录 0、定义 1、迭代器模式的四种角色 2、迭代器模式的UML类图 3、示例代码 4、迭代器的next()方法与集合的get(int index)方法的效率对比&#xff08;LinkedList为例&#xff09; 0、定义 提供一种方法顺序访问一个聚合对象中的各个元素&#xff0c;而又不需要暴露该对象…

基于SpringBoot的“招聘信息管理系统”的设计与实现(源码+数据库+文档+PPT)

基于SpringBoot的“招聘信息管理系统”的设计与实现&#xff08;源码数据库文档PPT) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringBoot 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 系统功能结构图 系统首页界面图 企业招聘界面…

Linux高阶——1123—服务器基础服务器设备服务器基础能力

目录 1、服务器基础 1、服务器基本概述 2、服务器设计之初解决的问题 网络穿透 网络数据设备间的收发 3、服务器的类型C/S、B/S 2、服务器设备 将自己的服务器软件部署上线 3、代理服务器负载均衡&#xff0c;以及地址绑定方式 4、服务器的基础能力 1、服务器基础 1…

探索 Python 任务自动化的新境界:Invoke 库揭秘

文章目录 探索 Python 任务自动化的新境界&#xff1a;Invoke 库揭秘背景&#xff1a;为何选择 Invoke&#xff1f;什么是 Invoke&#xff1f;如何安装 Invoke&#xff1f;5个简单的库函数使用方法1. 定义任务2. 带参数的任务3. 运行 Shell 命令4. 任务参数化5. 列出任务 场景应…

docker部署nginx,并配置SSL证书

、拉取nginx镜像 docker pull nginx:latest 在此过程中会遇到网络的问题&#xff0c;导致镜像无法下载&#xff0c;这时候需要在服务器中配置下国内的镜像地址。下面包含近期最新的国内镜像&#xff0c;截至2024年11月27日&#xff1a; "https://<你的阿里云账号ID&…

使用NAS开启无纸化办公,Docker部署开源文档管理系统『Paperless-ngx』

使用NAS开启无纸化办公&#xff0c;Docker部署开源文档管理系统『Paperless-ngx』 哈喽小伙伴们好&#xff0c;我是Stark-C~ 对于文案类的办公场景来说&#xff0c;手头堆放最多的可能就是各种文档文件&#xff0c;以及各种用过的打印废纸。 这么多年来&#xff0c;不管是领…

ES 基本使用与二次封装

概述 基本了解 Elasticsearch 是一个开源的分布式搜索和分析引擎&#xff0c;基于 Apache Lucene 构建。它提供了对海量数据的快速全文搜索、结构化搜索和分析功能&#xff0c;是目前流行的大数据处理工具之一。主要特点即高效搜索、分布式存储、拓展性强 核心功能 全文搜索:…

矩阵的拼接

矩阵的拼接分为横向拼接和纵向拼接 注意&#xff1a;横向拼接要求两矩阵行数相同&#xff0c;纵向拼接要求两矩阵列数相同 h o r z c a t horzcat horzcat和 v e r t c a t vertcat vertcat函数 h o r z c a t ( a , b ) horzcat(a,b) horzcat(a,b)将 a a a和 b b b横向拼接&a…

【Qt】重写QComboBox下拉展示多列数据

需求 点击QComboBox时&#xff0c;下拉列表以多行多列的表格展示出来。 实现 直接上代码&#xff1a; #include <QComboBox> #include <QTableWidget> #include <QVBoxLayout> #include <QWidget> #include <QEvent> #include <QMouseEve…

七牛云AIGC内容安全方案助力企业合规创新

随着人工智能生成内容(AIGC)技术的飞速发展,内容审核的难度也随之急剧上升。在传统审核场景中,涉及色情、政治、恐怖主义等内容的标准相对清晰明确,但在AIGC的应用场景中,这些界限变得模糊且难以界定。用户可能通过交互性引导AI生成违规内容,为审核工作带来了前所未有的不可预测…

告别 Kafka,拥抱 Databend:构建高效低成本的用户行为分析体系

用户行为数据埋点指标是数据仓库中不可或缺的重要数据源之一&#xff0c;同时也是企业最宝贵的资产之一。通常情况下&#xff0c;用户行为数据分析包含两大数据源&#xff1a;用户行为分析日志和上游关系型数据库&#xff08;如 MySQL&#xff09;。基于这些数据&#xff0c;企…

数据结构 (8)线性表的应用——一元多项式的表示及应用

一、一元多项式的定义 一元多项式是代数学研究的基本对象之一&#xff0c;可以表示为&#xff1a; P_n(x) p_0 p_1x p_2xn 其中&#xff0c;p_0, p_1, ..., p_n 是数域 F 中的数&#xff0c;n 是非负整数&#xff0c;x 是变量。 二、一元多项式的线性表表示 在计算机中&…

如何安全高效地打开和管理动态链接库(DLL)?系统提示dll丢失问题的多种有效修复指南

动态链接库&#xff08;DLL&#xff09;文件是Windows操作系统中非常重要的一部分&#xff0c;它们包含了程序运行所需的代码和数据。当系统提示DLL文件丢失时&#xff0c;可能会导致应用程序无法正常运行。以下是一些安全高效地打开和管理DLL文件以及修复DLL丢失问题的方法&am…

基于微信小程序的平价药房管理系统+LW参考示例

1.项目介绍 系统角色&#xff1a;管理员、医生、普通用户功能模块&#xff1a;用户管理、医生管理、药品分类管理、药品信息管理、在线问诊管理、生活常识管理、日常提醒管理、过期处理、订单管理等技术选型&#xff1a;SpringBoot&#xff0c;Vue&#xff0c;uniapp等测试环境…

鸿蒙ArkUI-X已更新适配API13啦

ArkUI-X 5.0.1 Release版配套OpenHarmony 5.0.1 Rlease&#xff0c;API 13&#xff0c;新增适配部分API 13接口支持跨平台&#xff1b;框架能力进一步完善&#xff0c;支持Android应用非压缩模式&#xff0c;支持Android Fragment对接跨平台。ACE Tools工具易用性提升&#xff…

《生成式 AI》课程 第3講 CODE TASK执行文章摘要的机器人

课程 《生成式 AI》课程 第3講&#xff1a;訓練不了人工智慧嗎&#xff1f;你可以訓練你自己-CSDN博客 任务1:总结 1.我们希望你创建一个可以执行文章摘要的机器人。 2.设计一个提示符&#xff0c;使语言模型能够对文章进行总结。 model: gpt-4o-mini,#gpt-3.5-turbo, import…

【人工智能】深入解析GPT、BERT与Transformer模型|从原理到应用的完整教程

在当今人工智能迅猛发展的时代&#xff0c;自然语言处理&#xff08;NLP&#xff09;领域涌现出许多强大的模型&#xff0c;其中GPT、BERT与Transformer无疑是最受关注的三大巨头。这些模型不仅在学术界引起了广泛讨论&#xff0c;也在工业界得到了广泛应用。那么&#xff0c;G…

Python开发环境搭建+conda管理环境

下载Miniconda 推荐从清华镜像下载安装包 Index of /anaconda/miniconda/ | 清华大学开源软件镜像站 | Tsinghua Open Source Mirror 打开网页后&#xff0c;下拉到最后找到Miniconda3-latest前缀的文件&#xff0c;或者网页中直接搜索Miniconda3-latest&#xff0c;都可以找…