怎样建网站买东西,深圳网站建设知了网络,免费域名申请方法,微信营销手机网站模板#x1f3ac; 个人主页#xff1a;谁在夜里看海.
#x1f4d6; 个人专栏#xff1a;《C系列》《Linux系列》《算法系列》
⛰️ 一念既出#xff0c;万山无阻 目录
#x1f4d6;一、进程程序替换
1.替换的演示
❓替换与执行流
❓程序替换≠进程替换
2.替换的原理
… 个人主页谁在夜里看海. 个人专栏《C系列》《Linux系列》《算法系列》
⛰️ 一念既出万山无阻 目录
一、进程程序替换
1.替换的演示
❓替换与执行流
❓程序替换≠进程替换
2.替换的原理 系统调用exec 进程控制块 (PCB) 内存管理
3. 替换的函数 execl execv execp exece
本质
二、命令行解释器shell
1.shell的本质
2.shell的模拟实现
头文件
宏定义
全局变量
获取信息
交互式命令行输入
字符串分割
内置命令
普通命令
main函数 一、进程程序替换
上一篇博客我们讲到了进程的诞生过程父进程调用fork创建子进程子进程执行父进程相同的程序。但是很多时候我们希望子进程执行另一个程序此时就要用到exec函数调用子进程中调用exec函数之后该程序就会被调用的程序代替这就是程序替换
1.替换的演示
#includestdio.h
#includeunistd.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源代码为
#includestdio.h
#includeunistd.hint main()
{// 打印当前pidppidprintf(After: pid %d, ppid %d\n,getpid(),getppid());
}
另一个可执行程序test源代码为
#includestdio.h
#includeunistd.hint 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.hint 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, TERMconsole, NULL};
execle(ps, ps, -ef, NULL, envp);
本质
事实上只有execve才是真正的系统调用而其他四个函数最后都会调用execve 二、命令行解释器shell
我们在linux学习过程中离不开shellshell是命令行解释工具是用户与内核之间的工具提供了一个接口通过它我们可以执行命令、启动程序等与操作系统进行交互。shell解析用户输入的命令返回执行结果。
❓shell的本质是什么呢
1.shell的本质
✅shell本质其实是一个进程
当我们启动一个终端或打开一个命令行窗口的时候相当于启动了一个shell进程也叫bash进程这个进程会等待用户输入的命令并将命令通过系统调用传递给内核内核执行相应的操作后返回给shell。 shell的工作原理就是循环以下操作
1️⃣获取命令行 -- 2️⃣解析命令行 -- 3️⃣fork创建子进程
-- 4️⃣execve替换子进程 -- 5️⃣wait等待子进程退出 -1️⃣
根据这些思路我们可以模拟实现一个shell
2.shell的模拟实现
实现一个简化版的shell需要执行以下功能 ① 获取当前工作目录、用户名、主机名。 ② 解析用户输入的命令行并执行命令。 ③ 内置支持一些常见命令如cd、echo、export等。 ④ 创建子进程来执行普通命令并支持基本的命令分割和管道处理。 头文件
#include stdio.h
#include stdlib.h
#include string.h
#include assert.h
#include unistd.h
#include sys/types.h
#include sys/wait.h这些头文件提供了标准输入输出、字符串处理、系统调用等功能。unistd包含与进程相关的函数如forkexit
宏定义
#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 %sRIGHTLABLE , 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);lastcode0;}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下的程序替换】的全部内容欢迎指正~
码文不易还请多多关注支持这是我持续创作的最大动力