【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.rhkb.cn/news/480078.html

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

相关文章

python爬虫案例——猫眼电影数据抓取之字体解密,多套字体文件解密方法(20)

文章目录 1、任务目标2、网站分析3、代码编写1、任务目标 目标网站:猫眼电影(https://www.maoyan.com/films?showType=2) 要求:抓取该网站下,所有即将上映电影的预约人数,保证能够获取到实时更新的内容;如下: 2、网站分析 进入目标网站,打开开发者模式,经过分析,我…

iscsi服务器

Iscsi 服务端 Iscsi客户端 Iscsi 客户端 创建lvm卷 安装iscsi服务端工具 创建lvm 用之前创建的lvm卷 创建服务端标识 创建客户端标识 Ls查看 删除之前的ip标识 创建服务端ip 和端口 创建双向认证用户名和密码 配置完后exit退出&#xff0c; 重启systemctl restart targe…

用shell脚本写一个通用的监听程序异常并重启脚本

进来服务器的程序php-fpm时常在并发下时常挂掉&#xff0c;而且时常在凌晨2点以后&#xff0c;通过排查是因为php配置需要调整并发&#xff0c;同时&#xff0c;为了不影响我休息&#xff08;以前老师说&#xff1a;能用机器和程序解决问题的坚决不用人去操作&#xff0c;这样才…

CA系统(file.h---申请认证的处理)

#pragma once #ifndef FILEMANAGER_H #define FILEMANAGER_H #include <string> namespace F_ile {// 读取文件&#xff0c;返回文件内容bool readFilename(const std::string& filePath);bool readFilePubilcpath(const std::string& filePath);bool getNameFro…

02-Linux系统权限维持

02-Linux系统权限维持 一 创建账号 1 在/etc/passwd中创建root的特权用户 /etc/passwd中数据的格式 账号:密码:uid:gid:描述:家目录:shell解释器&#xff0c;我们可以在/etc/passwd文件中添加一个test账号&#xff0c;密码为password123&#xff08;密文advwtv/9yU5yQ&#…

AWS codebuild + jenkins + github 实践CI/CD

前文 本文使用 Jenkins 结合 CodeBuild, CodeDeploy 实现 Serverless 的 CI/CD 工作流&#xff0c;用于自动化发布已经部署 lambda 函数。 在 AWS 海外区&#xff0c;CI/CD 工作流可以用 codepipeline 这项产品来方便的实现&#xff0c; CICD 基本概念 持续集成( Continuous…

[AutoSar]BSW_Diagnostic_007 BootLoader 跳转及APP OR boot response 实现

目录 关键词平台说明背景一、Process Jump to Bootloader二、相关函数和配置2.1 Dcm_GetProgConditions()2.2 Dcm_SetProgConditions() 三、如何实现在APP 还是BOOT 中对10 02服务响应3.1 配置3.2 code 四、报文五、小结 关键词 嵌入式、C语言、autosar、OS、BSW、UDS、diagno…

重塑用户体验!快手电商智能巡检平台的实践与探索

导读&#xff1a;随着科技的飞速发展&#xff0c;人工智能&#xff08;AI&#xff09;已经成为推动各行各业创新的重要力量。特别是在用户体验方面&#xff0c;AI 技术的应用不仅解决了许多传统问题&#xff0c;还带来了全新的交互方式和更高的用户满意度。本文将从快手电商B端…

sin函数拟合

目录 一、 目的... 1 二、 模型设计... 1 2.1 输入与输出.... 1 2.2 隐藏层设计.... 1 2.3 优化算法与损失函数.... 1 2.4 神经网络结构.... 1 三、 训练... 1 3.1 数据生成.... 2 3.2 训练过程.... 2 3.3 训练参数与设置.... 2 四、 测试与分析... 2 4.1 选取不同激活函数....…

【鸿蒙】鸿蒙开发过程中this指向问题

文章目录 什么是 this&#xff1f;常见 this 指向问题案例分析&#xff1a;HarmonyOS 组件中的 this 指向问题问题描述问题分析原因 解决方案&#xff1a;绑定 this 的正确方法方法一&#xff1a;使用箭头函数方法二&#xff1a;手动绑定 this 完整代码示例使用箭头函数使用 bi…

【摸鱼】Docker配置主从mysql数据库环境

docker pull mysql拉取docker镜像&#xff0c;国内现在访问不了docker hub&#xff0c;可以去阿里云上镜像加速器地址https://cr.console.aliyun.com/cn-hangzhou/instances/mirrors启动主库docker run -p 3306:3306 --name master-mysql --privilegedtrue -v /app/docker/data…

初试无监督学习 - K均值聚类算法

文章目录 1. K均值聚类算法概述2. k均值聚类算法演示2.1 准备工作2.2 生成聚类用的样本数据集2.3 初始化KMeans模型对象&#xff0c;并指定类别数量2.4 用样本数据训练模型2.5 用训练好的模型生成预测结果2.6 输出预测结果2.7 可视化预测结果 3. 实战小结 1. K均值聚类算法概述…

大数据笔记

第一章、大数据概述 人类的行为及产生的事件的一种记录称之为数据。 1、大数据时代的特征&#xff0c;并结合生活实例谈谈带来的影响。 &#xff08;一&#xff09;特征 1、Volume 规模性&#xff1a;数据量大。 2、Velocity高速性&#xff1a;处理速度快。数据的生成和响…

深度学习实战老照片上色

目录 1.研究背景与意义1. 卷积神经网络&#xff08;CNN&#xff09;在老照片上色中的应用1.1 卷积层与特征提取1.2 颜色空间转换1.3 损失函数与训练优化 2. 生成对抗网络&#xff08;GAN&#xff09;在老照片上色中的应用2.1 生成器与判别器2.2 对抗训练2.3 条件生成对抗网络&a…

C#面向对象,封装、继承、多态、委托与事件实例

一&#xff0e;面向对象封装性编程 创建一个控制台应用程序&#xff0c;要求&#xff1a; 1&#xff0e;定义一个服装类&#xff08;Cloth&#xff09;&#xff0c;具体要求如下 &#xff08;1&#xff09;包含3个字段&#xff1a;服装品牌&#xff08;mark&#xff09;,服装…

养老院、学校用 安科瑞AAFD-40Z单相电能监测故障电弧探测器

安科瑞戴婷 Acrel-Fanny 安科瑞单相电能监测故障电弧探测器对接入线路中的故障电弧&#xff08;包括故障并联电弧、故障串联电弧&#xff09;进行有效的检测&#xff0c;当检测到线路中存在引起火灾的故障电弧时&#xff0c;探测器可以进行现场的声光报警&#xff0c;并将报警…

PAT甲级 1056 Mice and Rice(25)

文章目录 题目题目大意基本思路AC代码总结 题目 原题链接 题目大意 给定参赛的老鼠数量为NP&#xff0c;每NG只老鼠分为一组&#xff0c;组中最胖的老鼠获胜&#xff0c;并进入下一轮&#xff0c;所有在本回合中失败的老鼠排名都相同&#xff0c;获胜的老鼠继续每NG只一组&am…

[SWPUCTF 2021 新生赛]include

参考博客: 文件包含 [SWPUCTF 2021 新生赛]include-CSDN博客 NSSCTF | [SWPUCTF 2021 新生赛]include-CSDN博客 考点:php伪协议和文件包含 PHP伪协议详解-CSDN博客 php://filter php://filter可以获取指定文件源码。当它与包含函数结合时&#xff0c;php://filter流会被当…

spring boot3.3.5 logback-spring.xml 配置

新建 resources/logback-spring.xml 控制台输出颜色有点花 可以自己更改 <?xml version"1.0" encoding"UTF-8"?> <!--关闭文件扫描 scanfalse --> <configuration debug"false" scan"false"><springProperty …

Unity shaderlab 实现LineSDF

实现效果&#xff1a; 实现代码&#xff1a; Shader "Custom/LineSDF" {Properties{}SubShader{Tags { "RenderType""Opaque" }Pass{CGPROGRAM#pragma vertex vert#pragma fragment frag#include "UnityCG.cginc"struct appdata{floa…