学会了程序替换,我决定手写一个简易版shell玩一玩...

news2024/11/26 11:51:01

在这里插入图片描述

文章目录

  • 💐专栏导读
  • 💐文章导读
  • 🐧程序进程替换
      • 🐦替换原理
      • 🐦替换函数
        • 🐔观察与结论
        • 🐔函数命名理解
  • 🐧myshell编写
      • 🔔代码展示
      • 🔔效果展示
  • 🐧myshell_plus
      • 🔔代码展示
      • 🔔效果展示

💐专栏导读

🌸作者简介:花想云 ,在读本科生一枚,C/C++领域新星创作者,新星计划导师,阿里云专家博主,CSDN内容合伙人…致力于 C/C++、Linux 学习。

🌸专栏简介:本文收录于 Linux从入门到精通,本专栏主要内容为本专栏主要内容为Linux的系统性学习,专为小白打造的文章专栏。

🌸相关专栏推荐:C语言初阶系列C语言进阶系列C++系列数据结构与算法

💐文章导读

本章我们将学习一个强大的功能——程序替换。之前我们创建的子进程只能完成简单的一些任务且部分代码继承自父进程。有了程序替换以后,我们可以让子进程轻松的做更多的事情。学会了程序替换,我们可以编写一个简易的shell玩玩了,由此也可以对前几章的内容作复习与巩固~
在这里插入图片描述

🐧程序进程替换

我们一直在提子进程,那么创建子进程的目的是什么呢?无非是想让它帮助我们做某件事情。

fork创建子进程后,子进程执行的是和父进程相同的程序(但有可能执行不同的代码分支)。如果此时我们想要子进程执行一个全新的程序该怎么做呢?这就需要用到程序替换了~

🐦替换原理

当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。

在这里插入图片描述

接下来我们就认识几个程序替换相关的函数,并演示如何操作。

🐦替换函数

一共有六种以exec开头的函数,称exec函数:

   #include <unistd.h> 		
   extern char **environ;

   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[]);
   int execvpe(const char *file, char *const argv[],char *const envp[]);
  • 这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。如果调用出错则返回-1
  • 所以exec函数只有出错的返回值而没有成功的返回值

在这里,我们先以execl为例。

🔔示例1——execl

在代码中,我们尝试在子进程中进行程序替换,替换为ls指令。execl使用时:

  • 第一个参数是程序所在路径;
  • 剩下的参数为执行该程序时想要传递的命令行参数(简述:平时你在命令行中怎么用,就怎么传参);
  • 当确定想要传递的参数都给出后,一定要以NULL结尾。
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
int main()
{
  pid_t id = fork();

  if(id == 0)
  {
    // 子进程
    execl("/bin/ls","ls","-a","-n","-l",NULL);
    printf("程序替换失败\n");
  }
  
  // 父进程
  printf("等待子进程成功,child_id:%d\n",wait(NULL));

  return 0;
}

在这里插入图片描述
如图所示,结果正是我们想要的。

🐔观察与结论

根据对示例1的观察,我们发现:

  • 子进程中,execl替换后剩下的语句未执行(printf);
  • 子进程发生替换并未影响父进程;

由此我们可以得出结论:

  • 因为进程具有独立性,尽管父子进程刚开始用的是同一个代码和数据,但是当程序替换发生后,由于写时拷贝的存在,仅仅只是子进程的代码和数据被替换后的程序覆盖了,并不会影响父进程。
  • 程序替换函数,一旦替换发生,原来的代码在替换的语句执行后,就已经被新程序的代码和数据覆盖了,所以printf并未执行;

🐔函数命名理解

在这里插入图片描述
其实四个函数的功能是类似的,都用于完成程序替换。只不过针对不同的场景,我们可以选择不同的函数。

这些函数根据函数名就大致可以判断如何使用:

  • l (list):表示参数采用列表 ;
  • v (vector) :参数用数组 ;
  • p (path) :有p自动搜索环境变量PATH
  • e (env) :表示自己维护环境变量;

🔔其余函数示例

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
int main()
{
  extern char** environ;
  pid_t id = fork();

  if(id == 0)
  {
    // 子进程
	
	char* const myargv[] = {"ls","-a","-l",NULL};
 	// 带l的,需要跟上路径
    execl("/bin/ls","ls","-a","-n","-l",NULL);
    // 带p的,可以使用环境变量PATH,无需写全路径
    execlp("ls","ls","-a","-l",NULL);
    // 带V的,可以使用自己的参数列表数组
    execvp("ls",myargv);
    // 带e的,需要自己组装环境变量
    execvpe("ls",myargv,environ);
 
    printf("程序替换失败\n");
  }
  
  // 父进程
  printf("等待子进程成功,child_id:%d\n",wait(NULL));

  return 0;
}

有了前几章所讲知识以及在、本章程序替换部分的知识,我们可以试着自己实现一个简易的shell

🐧myshell编写

🔔代码展示

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <assert.h>

#define MAX 1024
#define ARGC 64
#define SEP " "

// 将输入的字符串切割并保存到argv中
int split(char* commandstr,char* argv[])
{
  assert(commandstr);
  assert(argv);

  argv[0] = strtok(commandstr,SEP);
  if(argv[0] == NULL) return -1; // 若为NULL,则重新输入
  int i = 1;
  while(argv[i++] = strtok(NULL,SEP));
  return 0;
}

int main()
{
  while(1)
  {
    char commandstr[MAX] = {0}; // 用于保存用户输入的指令
    char* argv[ARGC] = {NULL};
    
    printf("[hxy@mychaimachine]$ ");
    fflush(stdout);
    char* s = fgets(commandstr,sizeof(commandstr),stdin); // 获取指令
    assert(s);
    (void)s;

    commandstr[strlen(commandstr)-1] = '\0'; // 去掉键盘输入的\n

    int n = split(commandstr,argv); // 切割输入的指令字符串
    if(n!=0) continue;

    pid_t id = fork();
    if(id == 0)
    {
      // 子进程
      execvp(argv[0],argv); // 程序替换
      exit(1);
    }

    int status = 0;
    waitpid(id,&status,0); // 等待子进程
  }
  return 0;
}

🔔效果展示

在这里插入图片描述
如图所示,我们用简短的50行代码写了一个简易的shell(命令行解释器)。其实Linux源码中的内容可远不止这些,而且我们实现的shell所能实现的功能非常少,非常简陋。例如,ls 并没有对不同的文件“上色”。

接下来,我们可以完善上述的代码,继续添加一些小功能。

🐧myshell_plus

上文中的myshell是非常简陋的,有许多指令诸如:cdexportenv等指令并不能正确执行。

就用cd来举例,myshell执行指令其实是交给子进程去做的,子进程的执行结果并不会影响父进程。也就是说,cd指令需要mybash自己去执行。

  • 我们把让bash自己执行的命令叫作内建命令。我们之前学到过的几乎所有关于环境变量的命令都是内建命令

于是,我们可以在创建子进程之前用if做判断,若用户输入的指令为内建命令,则让父进程执行该指令。

🔔代码展示

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <assert.h>

#define MAX 1024
#define ARGC 64
#define SEP " "

int split(char* commandstr,char* argv[])
{
  assert(commandstr);
  assert(argv);

  argv[0] = strtok(commandstr,SEP);
  if(argv[0] == NULL) return -1;
  int i = 1;
  while((argv[i++] = strtok(NULL,SEP)));
  return 0;
}

void showEnv()
{
  extern char** environ;
  for(int i = 0; environ[i]; i++) printf("%d:%s\n",i,environ[i]);
}

int main()
{
  extern int putenv(char* string);
  char myenv[32][256];
  int env_index = 0;
  int exitCode = 0;

  while(1)
  {
    char commandstr[MAX] = {0};
    char* argv[ARGC] = {NULL};
    
    printf("[hxy@mychaimachine]$ ");
    fflush(stdout);
    char* s = fgets(commandstr,sizeof(commandstr),stdin);
    assert(s);
    (void)s;

    commandstr[strlen(commandstr) - 1] = '\0'; // 去掉键盘输入的\n

    int n = split(commandstr,argv); // 切割字符串
    if(n != 0) continue;
    
    if(strcmp(argv[0],"cd") == 0)
    {
      if(argv[1] != NULL) chdir(argv[1]);
      continue;
    }
    else if(strcmp(argv[0],"export") == 0)
    {
      if(argv[1] != NULL)
      {
        strcpy(myenv[env_index],argv[1]); // 用户自己定义的环境变量,需要bash自己来维护
        putenv(myenv[env_index++]);
      }
      continue;
    }
    else if(strcmp(argv[0],"env") == 0)
    {
      showEnv(); // env查看环境变量时,其实看的是父进程bash的变量
      continue;
    }
    else if(strcmp(argv[0],"echo") == 0)
    {
      const char* target_env = NULL;
      if(argv[1][0] == '$')
      {
        if(argv[1][1] == '?')
        {
          printf("%d\n",exitCode);
          continue;
        } 
        else target_env = getenv(argv[1] + 1);

        if(target_env != NULL) printf("%s = %s\n",argv[1] + 1,target_env);
      }
      continue;
    }

    // ls设置颜色选项
    if(strcmp(argv[0],"ls") == 0)
    {
      int pos = 0;
      while(argv[pos] != NULL)
      {
        pos++;
      }
      argv[pos++] = (char*)"--color=auto";
      argv[pos] = NULL;
    }
        

    pid_t id = fork();
    if(id == 0)
    {
      // 子进程
      execvp(argv[0],argv);
      exit(1);
    }

    int status = 0;
    pid_t ret = waitpid(id,&status,0);
    if(ret > 0)
    {
      exitCode = WEXITSTATUS(status); // 获取最近一次进程的退出码
    }
  }
  return 0;
}

🔔效果展示

在这里插入图片描述
本章的内容就到这里了,觉得对你有帮助的话就支持一下博主把~

在这里插入图片描述

点击下方个人名片,交流会更方便哦~
↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓

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

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

相关文章

Vue电商项目--分页器制作

分页器静态组件 分页这个组件&#xff0c;不单单是一个页面用到了。多个页面同时用它,因此我们可以封装成一个全局组件 需要将这个分页结构拆分到components 通用的分页组件Pagination <template><div class"pagination"><button>1</butto…

【C语言】函数规则及入门知识

&#x1f6a9;纸上得来终觉浅&#xff0c; 绝知此事要躬行。 &#x1f31f;主页&#xff1a;June-Frost &#x1f680;专栏&#xff1a;C语言 ⚡注&#xff1a;此篇文章的 部分内容 将根据《高质量 C/C 编程指南》 —— 林锐 进行说明。该部分将用橙色表示。 &#x1f525;该篇…

新手建站:使用腾讯云轻量服务器宝塔面板搭建WP博客教程

腾讯云轻量应用服务器怎么搭建网站&#xff1f;太简单了&#xff0c;轻量服务器选择宝塔Linux镜像&#xff0c;然后在宝塔面板上添加站点&#xff0c;以WordPress建站为例&#xff0c;腾讯云服务器网来详细说下腾讯云轻量应用服务器搭建网站全流程&#xff0c;包括轻量服务器配…

html5视频播放器代码实例(含倍速、清晰度切换、续播)

本文将对视频播放相关的功能进行说明&#xff08;基于云平台&#xff09;&#xff0c;包括初始化播放器、播放器尺寸设置、视频切换、倍速切换、视频预览、自定义视频播放的开始/结束时间、禁止拖拽进度、播放器皮肤、控件按钮以及播放控制等。 图 / html5视频播放器调用效果&a…

java web 基础springboot

1.SprintBootj集成mybaits 连接数据库 pom.xml文件添加依赖 <!-- mysql驱动--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.30</version></dependency><!-- …

学习HCIP的day.09

目录 一、BGP&#xff1a;边界网关路由协议 二、BGP特点&#xff1a; 三、BGP数据包 四、BGP的工作过程 五、名词注解 六、BGP的路由黑洞 七、BGP的防环机制—水平分割 八、BGP的基本配置 一、BGP&#xff1a;边界网关路由协议 是一种动态路由协议&#xff0c;且是…

花果山博客

1&#xff1a;前言 2&#xff1a;项目介绍 3&#xff1a;统一返回结果 4&#xff1a;登录功能实现 前言 简单介绍一个写这个博客的目的。 因为之前学开发都是学完所需的知识点再去做项目&#xff0c;但是这时候在做项目的过程中发现以前学过的全忘了&#xff0c;所以为了减少这…

Vue3导入Element-plus方法

先引入依赖 npm install element-plus --savemain.js中要引入两个依赖 import ElementPlus from element-plus; import "element-plus/dist/index.css";然后 这个东西 我们最好还是挂载vue上 所以 还是 createApp(App).use(ElementPlus)然后 我们可以在组件上试一…

腾讯云轻量服务器镜像安装宝塔Linux面板怎么使用?

腾讯云轻量应用服务器宝塔面板怎么用&#xff1f;轻量应用服务器如何安装宝塔面板&#xff1f;在镜像中选择宝塔Linux面板腾讯云专享版&#xff0c;在轻量服务器防火墙中开启8888端口号&#xff0c;然后远程连接到轻量服务器执行宝塔面板账号密码查询命令&#xff0c;最后登录和…

从零搭建微服务-认证中心(二)

写在最前 如果这个项目让你有所收获&#xff0c;记得 Star 关注哦&#xff0c;这对我是非常不错的鼓励与支持。 源码地址&#xff1a;https://gitee.com/csps/mingyue 文档地址&#xff1a;https://gitee.com/csps/mingyue/wikis 创建新项目 MingYue Idea 创建 maven 项目这…

操作系统第五章——输入输出管理(下)

提示&#xff1a;枕上诗书闲处好&#xff0c;门前风景雨来佳。 文章目录 5.3.1 磁盘的结构知识总览磁盘 磁道 扇区如何从磁盘中读/写数据盘面 柱面磁盘的物理地址磁盘的分类知识回顾 磁盘调度算法知识总览磁盘的读写操作需要的时间先来先服务算法FCFS最短寻找时间优先SSTF扫描算…

SVG图形滤镜

SVG有提供Filter(滤镜)这个东西&#xff0c;可以用来在SVG图形上加入特殊的效果&#xff0c;像是图形模糊化、产生图形阴影、将杂讯加入图形等。以下介绍的是图形模糊化、产生图形阴影这2个滤镜效果。 浏览器对于SVG Filter的支援 SVG : 滤镜 (仅列出部分有使用到的属性) <…

【数据结构】超详细之实现栈

栈的实现步骤 栈的介绍栈的初始化栈的插入(入栈)栈的出栈获取栈顶元素获取栈中有效元素个数检测栈是否为空销毁栈栈元素打印 栈的介绍 栈&#xff1a;一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶&#xf…

快捷转换/互转 Markdown 文档和 TypeScript/TypeDoc 注释

背景 作为文档工具人&#xff0c;经常需要把代码里面的注释转换成语义化的 Markdown 文档&#xff0c;有时也需要进行反向操作。以前是写正则表达式全局匹配&#xff0c;时间长了这种方式也变得繁琐乏味。所以写了脚本来互转&#xff0c;增加一些便捷性。 解决方案 注释转 M…

【C++】初遇C++

认识C C语言是结构化和模块化的语言&#xff0c;适合处理较小规模的程序。对于复杂的问题&#xff0c;规模较大的程序&#xff0c;需要高度的抽象和建模时&#xff0c;C语言则不合适。为了解决软件危机&#xff0c; 20世纪80年代&#xff0c; 计算机界提出了OOP(object orient…

学好网络安全,每年究竟能挣多少钱呢?

薪资的高低&#xff0c;应该是想要转行网络安全的同学最关心的话题了。毕竟薪资是个人水平和自我价值的体现嘛。&#xff08;文末资料&#xff09; 今天就展开谈谈网络安全行业的薪资吧。 先来看张图&#xff0c; 大家在求职时都有一个期望薪资&#xff0c;企业会有一个实际薪…

5月的面试难度有点大....

大家好&#xff0c;最近有不少小伙伴在后台留言&#xff0c;又得准备面试了&#xff0c;不知道从何下手&#xff01; 不论是跳槽涨薪&#xff0c;还是学习提升&#xff01;先给自己定一个小目标&#xff0c;然后再朝着目标去努力就完事儿了&#xff01; 为了帮大家节约时间&a…

R语言实践——使用rWCVP映射多样性

使用rWCVP映射多样性 加载库工作流1. 物种丰富度2. 特有物种丰富度3. 特定区域的物种热力图 加载库 library(rWCVP) library(tidyverse) library(sf) library(gt)工作流 1. 物种丰富度 我们可以使用 wcvp_summary 将所有物种的全球出现数据压缩为每个 WGSRPD 3 级区域的原始…

chatgpt赋能python:Python三角函数角度的介绍

Python三角函数角度的介绍 Python语言为各种计算提供了强大的支持。而Python在数学领域的支持更是非常强大&#xff0c;包括对三角函数角度的计算。在Python中&#xff0c;支持常用的三角函数&#xff0c;例如sin、cos、tan等。这些函数都需要将角度转换为弧度&#xff0c;并且…

车载网络测试 - CANCANFD - 基础篇_01

目录 问题思考&#xff1a; 一、为什么需要总线? 二、什么是CAN总线? 三、为什么是CAN总线? 四、曾经的车用总线 1、SAEJ1850(Class2) 2、SAEJ1708 3、K-Line 4、BEAN 5、 byteflight, K-Bus 6、D2B 五、当前的车用总线 1、CAN 2、LIN 3、FlexRay 4、MOST 六…