外贸工厂的网站建设,游戏推广渠道,网站内怎么做链接,手机网站微信链接怎么做的引言#xff1a;
北京时间#xff1a;2023/3/22/6:59#xff0c;一晃3月都要过去了#xff0c;时间真快#xff0c;我都不知道自己这个月是怎么过的呢#xff1f;怎么就要结束了#xff0c;难受#xff0c;恍惚自己还在2022年#xff0c;刚刚晨跑回来#xff0c;洗完…引言
北京时间2023/3/22/6:59一晃3月都要过去了时间真快我都不知道自己这个月是怎么过的呢怎么就要结束了难受恍惚自己还在2022年刚刚晨跑回来洗完澡一个字形容困昏昏欲睡可能是昨天没怎么睡好也可能是睡的时间少了一点也可能是正常情况待会就不会了并且我只知道早上一睁眼就看见全宿舍都起床了都在卷一人独睡所以咱们起的比别人迟现在就更不能睡乘热打铁算了铁还没热乘虚而入不对咱不是那种人乘风而去算了这个咱也不行还是老老实实的乘胜追击、乘其不备的学习一下哈哈哈哈哈所以今天我们就接着上篇博客的内容继续谈谈什么是进程替换然后把进程替换玩明白之后自己实现一个命令行解释器bash
理解加载过程和进程程序替换原理
在上篇博客中我们了解了什么是进程替换知道进程替换的本质就是原进程的内核数据结构不变把原进程在物理内存中的代码和数据替换为新进程的代码和数据从磁盘中加载所以从原进程的角度来看可以看出进程替换的本质就是代码和数据被替换了那么此时我们站在替换代码和数据的角度看一看就又可以理解一个现象如下 替换原进程的代码和数据原本是在磁盘之中存储着的它是被系统调用接口execl识别然后被动的被操作系统加载到内存然后被动执行所以由于这个程序是被动的加载到了内存之中所以我们将这种现象中被动被加载到内存中的程序称之为 加载器
程序如何加载到内存
我相信我们一直都知道一个问题当然也是一个客观实际就是程序为什么要加载到内存这个问题的本质非常好解释无论是冯诺依曼体系规定的还是CPU和内存之间规定我们都知道一个文件想要被执行它就一定要加载到内存之中只有将程序加载到了内存CPU才可以从内存和该文件对应的进程pcb交互进而通过进程pcb来执行该文件中或者说是该进程中的代码和数据。明白了这个点此时通过该点我们接着来谈谈一个程序如何被加载到内存
一个程序需要被加载到内存和一个程序如何被加载到内存显然是两个不一样的问题并且后者比前者更加深入计算机系统所以此时我们就来聊聊程序是如何被加载到内存之中的这个问题就涉及到了我们这篇博客的主要内容进程替换 所以我们明白一个程序被加载到内存是利用了进程替换的方式并且我们上述也强调了这种使用进程替换的方式将一个程序加载到内存此时该程序也叫 加载器 并且程序加载到内存的过程就是程序替换的过程所以我们就有了很强烈的关系有了很强的扳手就是程序想要加载到内存之中就需要程序替换想要程序替换就需要有一个进程想要有一个进程首先就需要有一个bash命令行解释器所有指令和进程的父进程 公式指令 可执行文件 进程并且要知道此时的bash命令行解释器受操作系统控制 明白了这一串的联系之后此时就可以知道想要进行程序替换首先要有一个进程并且该进程中需要使用execl这样的系统调用接口所以程序加载到内存的本质就是使用 加载器通过加载器的形式将一个程序加载到内存。
明白了上述程序是如何别加载到内存之中的之后此时我们来看看程序被加载到内存的过程中操作系统做了什么想要回答这个问题此时就先要解决当我们创建进程的时候是先有进程数据结构还是先加载代码和数据到内存之中答案是显然的我们在学习虚拟地址空间的时候就有谈到操作系统是不允许任何资源的浪费所以它不允许先加载代码和数据而是先创建该进程对应的pcb通过pcb来管理该进程并且在该进程需要被执行的时候才会将该进程对应的代码和数据加载到内存 明白了这点之后此时就可以回答程序加载到内存操作系统做了什么工作或者间接就可以把这个问题改成操作系统如何把程序加载到内存或者是操作系统同应该如何执行可执行文件 从程序加载到内存我们应该先创建进程pcb为落脚点进而回答上述问题例如我们在test.c代码文件中写了一份C代码并且利用gcc生成了一个可执行文件mytest发现如果想要执行该可执行文件一定需要在该文件前面添加 ./ 的符号才能使该可执行文件运行起来所以此时通过这个最普通的现象我们可以有一个解释就是 ./ 就是用来将我们的可执行程序从磁盘中加载到内存因为上述说了创建一个进程的时候是先创建出该进程的数据结构所以明白该进程的代码和数据此时还并没有被加载到内存是需要通过 ./让 操作系统去调用相应的接口 加载器来将可执行文件在磁盘中的代码和数据加载到内存并且此时又通过加载器和程序替换之间的关系可以知道./ 的本质就是去调用了像execl这样的程序替换接口所以当我们的操作系统使用 ./ 加载我们的程序时此时操作系统就相当于把当前对应的指令bash指令就是命令行指令加载到了内存所以操作系统执行进程的方法就是先在内核中创建一个结构体在这个结构体上创建子进程然后直接让这些子进程去调用系统调用接口execl等函数就相当于是让我们自己的数据和代码加载到内存加载器和execl等函数挂钩所以创建进程时操作系统一定是先帮我们把进程的数据结构进程pcb给创建好然后在需要的时候再通过execl这个接口去把该进程对应的代码和数据加载到内存然后通过进程pcb来控制或者使用自然而然这个也就是一个进程的创建过程
总的来说 就是操作系统在内核中帮我们创建一个该进程数据结构此时CPU开始调度然后操作系统首先就把execl这个指令给给CPU执行然后把用户想要运行的指令传递给execl然后将该指令对应的在磁盘上的代码和数据加载到内存中这样就变成了一个用户想要执行的进程完成的就是一个地地道道的进程替换过程加载器加载过程
深入进程替换
因为进程替换把原程序的代码和数据都给替换了后续的代码是直接被替换是没有机会执行的从而证明程序替换是整体替换不是局部替换
如下图 可以发现程序替换只在子进程中进行是不会影响父进程的只会影响调用的进程本质是因为进程具有独立性但是此时是为什么呢子进程和父进程它们在物理内存中的代码和数据不是相同的吗如何理解代码数据相同但是又具备进程独立性呢 所以此时具体的原理如下 可以这么理解由于父进程和子进程在没有进行数据修改之前也就是没有进行写时拷贝之时两个进程的代码和数据是相同的此时如果将子进程进行程序替换此时就会导致一个问题就是子进程的代码和数据被替换是否会导致父进程的代码和数据被替换从上述的结论可以看出答案是不会的 所以想要搞定这个问题此时就又涉及到了在进程控制中了解的写时拷贝问题当我们的子进程或者父进程其中一个进程进行了程序替换或者说是被程序替换那么此时操作系统检测到之后就会对子进程或者父进程进行写时拷贝将子进程或者父进程的代码和数据拷贝两份其中一份供给程序替换并且回忆写时拷贝的目的就是防止资源浪费不必要是不开辟空间操作系统的特性所以当子进程进行程序替换时操作系统第一步先是进成写时拷贝然后才是加载新程序的代码和数据然后子进程再重新利用页表建立新的映射关系所以进程在程序替换之后还能保持进程独立性的本质原因就是操作系统会进行写时拷贝并且回忆之前的知识可以发现写实拷贝不仅可以在父子进程修改数据的时候进行也可以在代码区发生
程序替换失败问题
深入理解明白execl是一个函数接口它是有可能会调用失败的也就是无法进行程序替换进程太多等问题例如下图 当我们在execl中给了一个不存在的路径或者说路径中没有相应的可执行文件之时此时也就必然会导致execl程序替换失败所以此时的现象就是程序没有被替换而是继续执行原程序所以执行原程序就只执行了一次父进程中的程序因为子进程已经被exit退出了并且父进程也获取了子进程的退出码-1
所以得到一个小白白点就是程序替换成功执行新程序程序替换失败继续执行原程序
并且明白程序替换不需要对返回值进行判断因为只要程序替换函数有返回值就表示替换失败 如果execl成功执行新程序返回值是数据吗 这个数据是干嘛的呢所以此时可以明白使用了execl进程替换成功之后不会有返回值返回因为代码和数据已经被替换了去执行新的代码和数据了但是如果替换失败那么此时就一定要一个返回值也就是有返回值那么程序替换就一定失败所以该execl函数不需要对该函数进行返回值判断只要有返回值就是失败所以只要程序替换失败此时就可以无脑的exit直接进行程序终止就行不需要检查和判断
所以无论是让子进程执行新的程序还是旧的程序此时我们在父进程中使用 waitpid 接口父进程都是可以接收到相应的返回值的也就是检测到子进程的运行状态的是正常退出还是异常退出退出码正确还是退出码错误
类似execl的进程替换接口
明白了上述的知识此时我们就来看看有关程序替换的所有接口也就是开始熟悉熟悉execl等7个系统调用接口如下
1int execl(const char *path, const char *arg, ...);2int execlp(const char *file, const char *arg, ...);3int execle(const char *path, const char *arg, ..., char * const envp[]);4int execv(const char *path, char *const argv[]);5int execvp(const char *file, char *const argv[]);6int execve(const char *filename, char *const argv[], char *const envp[]);7int execvpe(const char *file, char *const argv[],char *const envp[]);注意execve是真正意思上的系统调用接口别的都是通过封装它实现
所以此时我们根据执行一个程序的基本步骤来讲讲这些函数的使用 第一步找到它 第二步加载它 第三步执行它 execl 第一个参数表示的就是你想执行谁一个字符指针所以第一个参数完成的步骤就是找到它第二个参数执行它想怎么执行它例ls -a 、ls -l 等此时就涉及我想怎么执行它就怎么传参原理在命令下怎么执行它我们的参数就怎么一个一个的传给它最后确定好了我要执行的程序和传递好了相应的指令参数此时最后一定还要跟上一个NULL结尾所以具体的使用方法就是execl(/bin/ls,ls,-a,-l,NULL) 注意 execl此时最后一个l的意思表示的就是list表示该接口是一个list实现的接口支持的是一个数据一个数据的传参 execv 第一个参数表示的也是你想要执行的指令但是区别就在于第二个参数此时它的第二个参数使用的是一个字符指针数组这个参数最大的好处就是传第二个参数的时候不需要一个一个字符的传而是可以直接传一个数组具体使用方法先建立一个字符指针数组char* myargv[]{ls,-a,-l,-n,NULL}; 然后直接使用该字符指针数组进行传参 execv(/bin/ls,myargv);并且从名字上出发execv最后的v代表的就是vector execlp参数const char* fileconst char* arg……首先从参数出发以p结尾的此时的第一个参数是file不以p结尾的第一个参数就是path如果第一个参数是path那么在找到它这个问题上就需要用户也就是我们自己去给给它一个路径如果带了p那么此时就不需要给给相对或者绝对路径只需要把程序名给给它就行系统会自动在环境变量path中查找自动查找和手动查找的区别具体使用方法execlp(ls,ls,-a,-l,NULL)这个也就是为什么execlp它的最后是以p结尾的原因以p结尾就支持自动查找不带p就不支持
总结 第一个参数是const char* file就支持自动查找第一个参数是const char* path就不支持自动查找需要手动查找 execvpconst char* file,char* const argv[],这个还是按照名字出发发现它不仅带v而且带p所以此时它的使用就是不仅可以直接传一个数组而且可以自动去环境变量中找 execleconst char* path,const char* arg,……char* const envp[]可以发现多了一个参数 char* const envp[]并且该参数此时就是涉及到了环境变量的相关知识就是我们可以通过该参数传一个环境变量给给这个接口然后让这个接口将我们的环境变量传递给那个被调用的程序此时这个程序就拥有了一个新的环境变量所以在进行程序替换的时候如果使用了该接口那么就是就可以传递一个我们想要传递的环境变量所以这个接口最重要的一个点就是理解自定义的环境变量可以替代系统环境变量的覆盖式传参原因就是我们可以手动传递环境变量给被调用的进程这个点也就涉及到了环境变量的继承问题下面单独讲解具体使用方法extern char** environ;或者char* const myenv[]{MYENVyou can see me,NULL};定义两个环境变量一个是自己实现的一个是系统自带的execle(./test/a.out,a.out,NULL,myenv); 或者execle(./test/a.out,a.out,NULL,environ); 进行进程替换了 execvpe 这个接口跟execle大致相同都是一个提供了传递环境变量的接口大致的区别就是这个接口是file接口支持自动查找相应的路径而execle不支持自动找路径一定要手动给路径。 execve 这个接口是真正的系统调用接口也就是操作系统的门户别的都是通过封装它实现所以这个接口是最重要的一切进程替换的源头 搞定了上述的这几个程序替换接口此时我们就可以从名字上发现一定的规律如下 l : 使用链表方式实现一个一个参数的传递 v通过构造指针数组的方式实现数组传参 p用来区分你是手动查找还是自动查找 e多了一个环境变量数组envp[]让我们可以使用新的环境变量代替调用进程的环境变量覆盖式但可以使用putenv先保存后覆盖解决 从execle深入理解环境变量 一个话题环境变量环境变量具有全局属性可以被子进程继承下去这是为什么呢 答案跟我们的execle接口密不可分因为所有的指令都是bash的子进程而bash执行所有的指令都可以调用execle去执行所以我们想要把bash的环境变量交给子进程只需要调用execle然后把我们的环境变量以最后一个参数的形式传给子进程子进程就可以拿到环境变量了environ所以这个就是环境变量具有全局性的原因。
使用程序替换接口自己实现简易bash 代码如下
#includestdio.h
#includestdlib.h
#includeunistd.h
#includesys/types.h
#includesys/wait.h
#includestring.h#define MAXLEN 1024
#define LEN 32int main()
{char shell[MAXLEN]{0};char* DOS[LEN]{0};while(1){printf([dodamceMy_centos dir] );fgets(shell,MAXLEN,stdin);shell[strlen(shell)-1]\0;DOS[0]strtok(shell, );int i1;while(DOS[i]strtok(NULL, )){i;}pid_t idfork();if(id0){//childexecvp(DOS[0],DOS);exit(1);}int status0;pid_t waitwaitpid(id,status,0);if(wait0){printf(Exit Code%d\n,WEXITSTATUS(status));}}return 0;
} 总结今天该博客非常的不尽人意主要还是因为笔试强训的原因困恼哎