Linux进程控制(三)---进程替换+简易shell的实现

news2024/11/27 18:39:40

目录

execl()

execv()

execlp()

execvp()

如何利用execl执行自己写的C/C++可执行程序?

如何利用makefile同时编译两个文件

execle()

execvpe()

简单shell的编写


什么是进程替换?

我们之前fork之后,是父子进程各自执行代码的一部分,然后父子代码共享,数据写时拷贝各自一份.

但是如果子进程就想执行一个全新的程序呢?子进程想拥有自己的代码,这就用到了程序的替换.

程序替换,是通过特定的接口,加载磁盘上的一个全新的程序(代码和数据),加载调用进程的地址空间中.

比如只有一个进程,先不考虑父子进程.

先把可执行程序加载到内存中,然后进程通过地址空间及页表的映射找到代码地址,然后执行.

 此时如果发生了程序替换,要替换成磁盘中另一个可执行程序other.exe,此时便会将新的磁盘上的程序加载到内存,并和当前页表建立映射.而原本的程序myproc.exe几乎不发生变化.

而这些工作,可以使用exec系列的接口来实现的.

所以进程替换的原理是:

用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。

其中,exec系列函数的本质就是如何加载程序的函数.

我们来演示两个例子,一个是单独一个进程,一个是父子两个进程.

先来看一个进程的:

make编译然后运行:

正如我们的预期.

然后我们进行进程替换,当然需要用到上面所提到的exec系列函数.

execl()

我们man execl查看用法:

 

这里一共有6个相关的函数,但学会一个,后面的基本上也不是什么问题.

那我们先来第一个execl.

path是要新加载程序的路径。path:路径+目标文件名

第二个是传入一个字符串作为参数,等下会细说.

...这个叫做可变参数列表,即可以传入多个、不定个数的参数.最后一个参数必须是NULL结尾标识参数传递完毕.

我们在命令行上怎么写的,在这里参数就怎么填.什么意思呢,我们看下面的例子.

打开刚才那段代码,利用execl函数,然后我们先用一下系统的程序,例如填写ls的路径,然后参数的话,我们想执行ls,会在命令行上输入ls,所以参数写一个ls即可,如下:

 退出来之后,make编译执行.

首先,我们发现execl之前的代码正常运行了,而execl之后的代码并没有运行,这说明此时程序已经被ls替换了.它会将当前代码所有的代码和数据都进行替换,包括执行的和未执行的,一但替换成,后面的代码都不会被执行了.

其次我们发现ls的命令也被执行了,说明替换的程序也正常运行了.

这便是execl的一个用法.

当然还可以加更多的参数,例如:

然后运行:

 此时发现程序相当于执行了ls -a -l,显示了隐藏的文件及详细信息.

当然可以换成其它的命令,which,pwd,top...等等.只要填写其路径即可.

 知道了用法,然后返回值还没说

 说execl只有返回失败的时候才有返回值,返回-1.

也就是说,execl进程替换成功是不会有返回值的.

其实我们仔细想一下也是这样的,execl进程替换成功后,会连自己的execl这一行代码都替换掉,取而代之的一个全新的程序,所以返回值在这里也是没有意义的.

接下来我们演示一个父子两个进程的例子.

  然后我们预期的结果应该是子进程ls之后,父进程显示等待成功,并输出子进程的退出码.

可以发现,正如我们预期的所示.

为什么要创建子进程?

为什么不影响父进程,父进程聚焦在读取数据,解析数据,指派进程执行代码的功能!

父进程负责fork,管理这些子进程,子进程负责程序替换,完成自己的工作. 

execl之后,父子进程的关系?

加载新程序前,父进程和子进程的关系是代码共享,数据写时拷贝.

当子进程execl加载新程序后,它们父子间代码需要分离,代码需要进行写时拷贝,这样父子进程在代码和数据上就彻底分开了.

execv()

我们同样的先用man查看一下用法

 注意和execl的区别:

 execl的传参类似list的方式,

execv是指针数组,和execl没有本质的区别,只有传参上的区别,execv需要我们传入一个指针,这个指针指向的是这个argv数组.我们事先把选项写好到argv数组中,而execl需要我们把每个选项都当做参数传入.

以上是execv的传参方式.我们看用代码是如何写的.

 注意和execl的区别,execv相当于是先在外面写好之后,再把写好的数组传进来.

 

运行之后,结果依然正确.

execlp()

我们依然man查看一下用法:

这个file和之前的path有什么区别呢?

我们上面说的,要寻找替换的文件,需要写它的路径。那么如果不带路径,可以找到程序吗?

当然是可以的,环境变量便是如此。例如我们平常运行ls的时候,不需要加路径即可运行,而我们运行我们自己的可执行程序时,需要加上路径./来运行.

所以execlp的file意思是它会自动在环境变量PATH中寻找,不用告诉它程序在哪里.

用代码这样写:

 首先它会自动在环境变量PATH中寻找"ls",然后执行ls -a -l.

同样地,照样结果正确.

同时你要分清以上两个ls的区别:第一个ls意思是你想执行谁(查找路径),第二个ls是你想怎么执行(匹配). 

execvp()

这个类似于execl和execp的区别,只是第二个参数不同,即除了传参方式不一样,别的本质都是一样的.

 注意和上一个execlp的区别:

只有第二个参数不一样,只是传参方式改变:

 

照样可以正常运行:

如何利用execl执行自己写的C/C++可执行程序?

我们可以在myproc.c文件中利用execl函数调用 由mycmd.c文件编译形成的mycmd可执行文件.

然后利用make编译两个文件,再执行myproc,便可以调用到自己写的C可执行程序(mycmd).

首先继续刚才的,再myproc.c文件中,先把mycmd的路径写入,为什么方便以后修改,我们可以直接#define一下,然后传入到execl函数中,执行-a选项.

 然后编写mycmd.c文件:

需要用到main函数里的命令行参数,如果输入的没有两个参数,就直接结束程序,若输入

mycmd -a ,则输出a.

mycmd -b,则输出b.

然后退出,make编译,那么有一个问题:

如何利用makefile同时编译两个文件

我们之前在make/makefile里讲过,如果直接make,便会从makefile中从上到下执行只执行第一条语句,这样只能编译一个文件,到时候在编译很多文件时,得一个一个编译,会很麻烦.

这个时候便用到了伪对象,也是那一章说过的.

定义一个伪对象all,比如我们最后要形成两个可执行文件mycmd和myproc.

然后我们只维护一个依赖关系,让伪对象all依赖于mycmd与myproc.

这样编译器遇到all时,会自动向下找到这两个语句,然后再分别编译得到它们,这样就成功了.

make编译好之后,我们运行myproc文件

可以发现已经成功运行了我们自己写的C程序了. 

execle()

 可以发现前两个参数和我们之前的那一套一模一样,所以我们只用看第三个参数即可.

第三个参数是一个环境变量,准确来说是来传递环境变量给新的程序的.

我们在原来的程序myproc.c文件中,写入一个环境变量:

然后再mycmd.c文件中获取环境变量

此时我们再次make编译并运行.

 

便成功获取到了环境变量.

当我们不传入环境变量时,即新的进程获取不到,便会返回null.

execvpe()

这个接口无非是前面几个接口参数的叠加,只要会前面的几个接口,这个接口也照样可以使用.

file是从环境变量里面查找,argv是要传入的参数选项,envp是要传递的环境变量,前面几个接口都有所提到,这里便不再做演示.

当然以上6个都不是严格意义上的系统接口,只是系统提供的基本封装.真正的系统接口是execve.

 filename需要把文件的全路径写上,argv同样也是同上,是参数和选项,最后一个参数也是传递环境变量.

到这里进程替换就讲完了,这些函数很多而且名字相近,各函数的作用也不相同,要记忆起来也是比较困难.

其实仔细观察这些函数名,也是有规律的.

 l(list) : 表示参数采用列表,即将参数全部传入到函数中,例如execl,execlp,execle.
v(vector) : 参数用数组,即先在外部将参数选项写入到数组里,再传入到函数中,例如execv,execvp.
p(path) : 有p自动搜索环境变量PATH,自带path,不需要写文件的全部路径,如execlp,execvp.
e(env) : 表示自己维护环境变量,可以传入环境变量,如execle.

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[]);

这张图也是对以上的一种总结,不用死记硬背,理解以上几个意思,看到函数便知道需要怎么传入参数了.

简单shell的编写

简单shell的编写基本上覆盖了之前所有进程控制的知识点,包括进程创建,进程终止,进程等待以及今天的进程替换,具体细节可以查看源代码,会有详细的注释.

这里唯一需要注意的是,当父进程bash创建子进程后,子进程执行的进程替换,即命令是无法完成内置命令的,例如cd这样的,子进程刚完成cd然后进程就退出了,这样并没有意义.

所以我们需要让父进程bash亲自完成,这里需要用到一个chdir函数,具体怎么使用可以man chdir来查看用法,总体代码如下:

    include<stdio.h>                                                                                                                                
    2 #include<stdlib.h>
    3 #include<string.h>
    4 #include<unistd.h>
    5 #include<sys/types.h>
    6 #include<sys/wait.h>
    7 
    8 #define NUM 1024
    9 #define SIZE 32
   10 #define SEP " "
   11 //保存打散之后的命令行字符串
   12 char* g_argv[SIZE];
   13 //保存完整的命令行字符串
   14 char cmd_line[NUM];
   15 
   16 //shell 运行原理: 通过让子进程执行命令,父进程等待 和 解析命令
   17 int main()
   18 {
   19   //0.命令行解释器,一定是一个常驻内存的进程,不退出
   20   while(1)
   21   {
   22     //1.打印出提示信息:[root@localhost myShell]$ 
   23     printf("[root@localhost myShell]# ");
   24     fflush(stdout);
   25     memset(cmd_line,'\0',sizeof(cmd_line));
   26     //2.获取用户的输入[输入的是各种指令和选项:"ls -a -l"]
   27     if(fgets(cmd_line,sizeof(cmd_line),stdin) == NULL)
   28     {
   29       continue;
   30     }
   31     cmd_line[strlen(cmd_line)-1] = '\0';
   32     //ls -a -l\n
   33     //printf("echo: %s\n",cmd_line);
   34     //3.命令行字符串解析:"ls -a -l" -> "ls" "-a" "-l"
   35 
   36     g_argv[0] = strtok(cmd_line,SEP);//第一次调用,要传入原始字符串
   37     int index = 1;
   38     //这段代码等价于下面while(g_argv[index++] = strtok(NULL,SEP));
   39    // while(1)
   40    // {
   41    //   g_argvp[index] = strtok(NULL,SEP);//第二次,如果还要解析原始字符串,则传入NULL
   42    //   index++;
   43    // }
           //如果是ls命令,我们可以给它加上颜色
   44     if(strcmp(g_argv[0],"ls") == 0)
   45     {
   46       g_argv[index++] = "--color=auto";
   47     }
   48     while(g_argv[index++] = strtok(NULL,SEP));
   49     //for DEBUG
   50    // for(index = 0; g_argv[index]; index++)
   51    // {
   52    //   printf("g_argv[%d]:%s\n",index,g_argv[index]);
   53    // }
   54    //
   55     //4.TODO 内置命令:让父进程(shell)自己执行的命令,我们叫做内置命令(内建命令)
   56     //内置命令本质就是shell中的一个函数调用
   57     if(strcmp(g_argv[0],"cd") == 0)//不想让子进程执行,而是父进程执行
   58     {
   59       if(g_argv[1] != NULL)
   60       {
   61         chdir(g_argv[1]);
   62         continue;
   63       }
   64     }
   65     //5.fork()
   66     pid_t id = fork();
   67     if(id == 0)
   68     {
   69       //child process
   70       printf("功能让子进程执行\n");                                                                                                              
   71       execvp(g_argv[0],g_argv);//ls -a -l
   72       exit(1);
   73     }
   74        else
   75     {
   76       //father process
   77       int status = 0;
   78       pid_t ret = waitpid(id,&status,0);
   79       if(ret > 0)
   80       {
   81         printf("exit code:%d\n",WEXITSTATUS(status));
   82       }
   83     }
   84   }
   85   return 0;
   86 }                  

我们make编译好后然后运行:

发现功能都可以正常使用了. 

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

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

相关文章

【人工智能】大模型平台新贵——文心千帆

个人主页&#xff1a;【&#x1f60a;个人主页】 &#x1f31e;热爱编程&#xff0c;热爱生活&#x1f31e; 文章目录 前言大模型平台文心千帆发布会推理能力模型微调 作用 前言 在不久的之前我们曾讨论过在ChatGPT爆火的大环境下&#xff0c;百度推出的“中国版ChatGPT”—文…

深度神经网络基础——深度学习神经网络基础 Tensorflow在深度学习的应用

目录 一、二、Tesnsflow入门 & 环境配置 & 认识Tensorflow三、线程与队列与IO操作神经网络基础知识1.简单神经网络2.卷积神经网络卷积层新的激活函数-Relu池化层(Pooling)计算 案例&#xff1a;Mnist手写数字图片识别卷积网络案例 一、二、Tesnsflow入门 & 环境配置…

IDEA无法链接代理The driver has not received any packets from the server.

问题如下: 1、本地部署Proxifier,且设置全局代理,截图如下 代理工具 2、通过Navicat 工具连接该远程数据库,连接正常,截图如下 3、通过IDEA或者Eclipse连接(通过代理),抛连接失败(数据库地址绝对无误),如果把数据库地址改成本地的mysql地址,可以正常连接(不用通过代理)…

mac使用mvn下载node-sass 会Binary download failed, trying source

m1 上使用nvm 以下node的版本可以直接下载&#xff08;Binary download&#xff0c;而不是 trying source&#xff09;而不用切换mac cpu架构 zhiwenwenzhiwenwendeMBP cockpit % nvm install 14.15.5 Downloading and installing node v14.15.5... Downloading https://node…

二十五章:TransUNet:Transformer为医学图像分割提供强大的编码器

0.摘要 医学图像分割是发展医疗系统的重要先决条件&#xff0c;特别是对于疾病诊断和治疗计划。在各种医学图像分割任务中&#xff0c;U型架构&#xff0c;也称为U-Net&#xff0c;已成为事实上的标准&#xff0c;并取得了巨大的成功。然而&#xff0c;由于卷积操作的固有局部性…

Zabbix监控linux主机(agent端)

目录 一、Linux-clinet操作&#xff08;agent&#xff09; 二、源码安装zabbix 三、Zabbix添加linux主机 为agent.zabbix.com添加模板 等待一会 查看效果如下 一、Linux-clinet操作&#xff08;agent&#xff09; [rootlocalhost ~]# ifconfig ens33[rootlocalhost ~]# vim…

免费的游戏图标素材库分享

游戏图标设计在游戏UI中占有非常重要的地位。例如&#xff0c;当我们看到一个游戏的启动图标时&#xff0c;很容易区分它是哪个游戏。设计游戏图标不仅是一个图形&#xff0c;也是一个标志。 本文将通过各种游戏图标设计素材分享游戏图标的类别和设计游戏图标的思考。 1. 游戏…

程序员基础知识—IP地址

文章目录 一、什么是IP地址二、IP地址的分类三、子网掩码 一、什么是IP地址 IP地址就像我们需要打电话时的电话号码一样&#xff0c;它用来标识网络中的一台主机&#xff0c;每台主机至少有一个IP地址&#xff0c;而且这个IP地址是全网唯一的。IP地址由网路号和主机号两部分组…

vue 3.0 如何加载图片

.logo { background: url(~/assets/images/logo.svg) no-repeat center center/contain; width: 117px; height: 24px; margin: 0 20px; } <a class"logo" href"#"></a> 比较实用的书写方式

小程序制作教程

步骤一&#xff1a;规划和设计 在开始制作微信小程序之前&#xff0c;首先需要规划和设计您的小程序。确定您想要提供的服务或功能&#xff0c;并考虑用户体验和界面设计。绘制草图和构思完整的页面布局&#xff0c;这将使您更好地理解小程序结构和功能。 步骤二&#xff1a;…

Arrays.asList

文章目录 摘要详解我们再去看看 java.util.ArrayList 为什么可变的呢&#xff1f;Arrays.asList()和 Collections.singletonList()额外&#xff1a;Collections.singletonList() 摘要 先总结要点&#xff0c;接下来详细讲解 返回由指定数组支持的长度不可变的列表&#xff0c…

题目3 文件包含(保姆级教程)

url&#xff1a;http://192.168.154.253:83 #打开http://XXX:81/&#xff0c;XXX为靶机的ip地址 审题 1、打开题目看到有一个提示&#xff0c;此题目需要通过利用存在的文件包含漏洞&#xff0c;尝试获取webshell&#xff0c;最后从根目录下key.php文件中获得flag 2、开始答题…

老年公寓人员定位管理系统:提升安全与关怀的智能解决方案

老年公寓作为提供安全居住环境和关怀服务的重要场所&#xff0c;面临着人员管理和安全控制的挑战。为了解决这些问题&#xff0c;老年公寓人员定位管理系统应运而生。基于为提供全面的安全管理和个性化关怀服务&#xff0c;华安联大便通过老年公寓人员定位管理系统的技术原理、…

在react中配置less

第一步&#xff1a;暴露出webpack配置文件 终端命令&#xff1a;npm run eject (此命令一旦运行不可逆) 第二步&#xff1a;安装less以及less-loader npm install less less-loader --save-dev 第三步&#xff1a;修改webpack的配置文件 运行完以上命令后&#xff0c;项目…

文心千帆大模型测评分享,效果超出预期

一、前言 现如今&#xff0c;随着ChatGPT的爆火越来越多的人开始关注人工智能领域了&#xff0c;大家都在尝试使用它来帮助自己在工作上提高效率亦或是解决一些问题。但ChatGPT是有一定的使用门槛的&#xff1a;首先需要我们“科学上网”才能访问&#xff0c;其次GPT4的价格相…

02-线性结构3 Reversing Linked List

第一次提交 第二次 今日积累&#xff1a;while(n--){} n结束的值是-1而不是0 (꒪⌓꒪) code # include <iostream>struct Node {int data;int p_nxt; } L[100000];int main(void) {int p_start; // first node addressint K; int N; //不保真&#xff0c;待会要顺着链表…

刘铁猛C#教程笔记——方法

方法的由来 C#语言和Java语言都是由C语言发展而来&#xff0c;而C语言是由C语言发展而来&#xff0c;C语言全面兼容C语言&#xff0c;在C语言的基础上引入了类的概念&#xff0c;即面相对象程序设计思想的核心内容&#xff0c;C语言不是完全的面相对象程序设计语言&#xff0c…

十一、正则表达式详解:掌握强大的文本处理工具(三)

文章目录 &#x1f340;贪婪模式&#x1f340;应用的场景&#x1f340;总结 &#x1f340;非贪婪模式&#x1f340;应用的场景&#x1f340;总结 &#x1f340;贪婪模式与非贪婪模式在爬虫的应用&#x1f340;转义字符&#x1f340;正则表达式常见函数 &#x1f340;贪婪模式 在…

应用层协议:httphttps,如何进行安全握手?

目录 应用层协议序列化与反序列化JSON网络版本计算器URLurlencode和urldecode HTTP协议简单认识HTTP协议HTTP协议格式HTTP的一些方法HTTP状态码Http的特征cookieConnection HTTPSHTTPS是什么加密与解密常见的加密方式对称加密非对称加密 什么是数据摘要什么是证书HTTPS如何安全…

【MySQL】MySQL数据库的进阶使用

别灰心&#xff0c;一切都会好起来的… 文章目录 一、MySQL基本查询1.对表内容进行Create(增加)1.1 insert语句的使用1.2 插入查询结果&#xff08;删除表中的重复记录&#xff09; 2.对表内容进行Retrieve(读取)3.对表内容进行Update(更新)4.对表内容进行Delete(删除)4.1 del…