linux入门之进程控制(下)进程程序替换,shell运行原理,手写一个mini-shell

news2024/9/30 5:38:36

文章目录

一、进程程序替换

1.替换原理

2.替换函数

3.函数解释

4.命名理解

二、手写一个mini Shell


一、进程程序替换

创建子进程的目的就是为了让子进程执行特定的任务,比如:1.让子进程执行父进程的一部分代码;2.让子进程指向一个全新的程序代码,就是进行程序替换

1.替换原理

用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。(执行程序替换,是整体替换,不能局部替换)。调用exec并不创建新进程,所以调用exec前后该进行的id并未改变站在程序的角度,就是这个程序被加载到内存。exec函数就相当于一个加载器。

#include<stdio.h>
#include<unistd.h>

int main()
{
    printf("begin...");
    printf("begin...");
    printf("begin...");

    //程序的路径 ; 程序的名称 ;要执行程序的选项
    execl("/bin/ls","ls","-a","-l",NULL);
    //执行程序替换,新的代码和数据被加载,后续的代码属于老代码,直接被替换了,没机会执行后续的原代码

    printf("end...");
    printf("end...");

}

这个函数就是实现让一个进程执行另一个在磁盘中的程序。既然我自己写的代码可以加载新的程序,os实现的功能:创建进程的时候,先创建进程数据结构,再加载代码和数据。

int main()
{
    pid_t id = fork();
    if(id == 0)
    {
        //child 
        printf("我是子进程:%d \n",getpid());
        execl("/bin/ls","ls","-a","-l",NULL);
        //execl 返回有值的时候,就是执行失败 比如 加载一个不存在的指令 lsssss
        //执行成功的时候,不会有返回值
        //不用对该函数返回值判断,只要继续向后运行一定是失败的
        //替换成功,则由执行替换程序的时候,退出码由新进程去决定
        执行错误的时候exit(1);
    }

    sleep(5);
    //father
    int status = 0;
    printf("我是父进程:%d\n",getpid());
    waitpid(id,&status,0);
    return 0;
}

程序替换只会影响调用的进程。因为进程具有独立性。os会在子进程执行新程序的时候,需要程序替换,发生写时拷贝。写时拷贝在代码区也可以发生。如果程序替换失败,就执行原来的代码。

2.替换函数

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

1.int execl(const char * path , const char * arg, ...)   //list

//path :要执行程序的路径 执行一个程序需要先找到它,再加载执行它

//arg   :要如何执行  想在命令行如何执行这个命令,将参数逐个传递给它

execl("/bin/ls","ls","-a","-l",NULL)

2.int execv(const char * path,char * const argv[ ]);  // vector

//path:路径

//agrv[ ] :按照数组的方式传参

char * const myargv[ ] = {"ls" ,"-a", "-l","-n",NULL};

execv("/bin/ls",myargv);

3.int execlp(const char * file,const char * arg ...); //在环境变量path中

带p只需要指定程序名即可,系统会自动在环境变量path中进行查找

execlp("ls","ls","-a","-l",NULL) 

//第一个ls是指定程序名

//第二个往后是在命令行怎么执行它 ls -a -l

4.int execvp(const char * file,char * const argv[ ]); //vector path

execvp("ls",myargv);

以上都是执行命令,以下执行我自己写的程序:c -- > c++

5.int execle(const char * path,const char * arg,... char * const envp[ ]);

//char * const envp[ ] 自定义环境变量

5.int execvpe(const char * file, char * const argv[ ],char * const envp[ ]);

6.int execve(const char * filename,char * const argv[ ] ,char * const envp[ ] );

1-5号最终都调用6号,6号是os提供的系统调用,1-5号是对6号做的封装。

//myproc.c
int main()
{
    pid_t id = fork();
    if(id == 0)
    {
        
        execl("./exec/otherproc","otherproc",NULL);
        exit(1);
    }

    //father 
    int status = 0;
    waitpid(id,&status,0);
    return 0;
}
    

//otherproc.cc   c++程序

#include<iostream>

using namespace std;

int main()
{
    for(int i = 0; i<5 ;i++)
    {
        cout<<"I am otherproc,my pid is  " <<getpid() <<endl;
        sleep(1);
    }
    return 0;
}

//先编译otherproc.cc
g++ -o otherproc otherproc.cc

实验结果:子进程运行调用.cc程序,然后父进程获取status

//myproc.c
int main()
{
    pid_t id = fork();
    if(id ==0)
    {
        //child 
        //1.传入新定义的环境变量
        char * const myenv[] = { "MYENV = youcanseeme",NULL};
        execle("./exec/otherproc","otherproc",NULL,myenv);

          //2.传入原来存在的环境变量
      //  extern char ** environ;
      //  execle(" ....."."....",NULL,environ);

        // 3.两个都传入 
        putenv("MYENV = youcanseeme");
        execle("....","....",NULL,environ);
        exit(1);
    }
        
    //father
      ...
    return 0;
}



//otherproc.cc

int main()
{
    for(int i = 0; i<5 ;i++)
    {
        cout<<"I am otherproc,my pid is:  ,MYENV :  "<<getpid()<<" "<<(getenv("MYENV") == NULL?"NULL:getenv("MYENV")<<endl;
    }
    
    return 0;
}




此时传入环境变量,env变成了全新自定义的环境变量。使用execle的时候会覆盖式传入新的环境变量。环境变量具有全局属性,可以被子进程继承。bash是父进程,然后执行execle,以传参的方式传给子进程。

3.函数解释

  • 这些函数如果调用成功,则加载新的程序,从启动代码开始执行,不再返回
  • 如果调用出错返回-1
  • exec函数只有出错的返回值,没有成功的返回值

4.命名理解

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

二、手写一个mini Shell

用下图的时间轴来表示事件的发生次序。其中时间从左向右。shell从用户读入字符串ls,shell建立一个新的进程,然后在那个进程中运行ls程序并等待进程结束。

要写一个shell,需要循环以下过程:

1.获取命令行

2.解析命令行

3.建立一个子进程(fork)

4.替换子进程(execvp)

根据这些思路,接下来实现一个shell,①需要先从命令行获取字符串fgets ②覆盖掉\n ③切割字符串 ④进行进程替换

代码和注释如下

1 #include <stdio.h>
2 #include <stdlib.h>
3 #include<unistd.h>
4 #include<sys/wait.h>
5 #include<string.h>
6 #include<assert.h>
7 #include<sys/types.h>
8 
9 #define MAX 1024
10 #define ARGC 64
11 #define SEP " "
12 


     int split(char * commandstr,char * argv[])
   16 {
   17   assert(commandstr);
   18   assert(argv);
   19   
   20   argv[0] = strtok(command,SEP);
   21   if(argv[0] == NULL)
   22     return -1;
   23   int i = 1;
   24   while(argv[i++] = strtok(NULL,SEP));
   25   return 0;
   26 }                                                                                                                                                                                                       
   27 int main()
   28 {
   29   while(1)
   30   {
          //获取的字符串 均初始化为0 
   31     char commandstr[MAX] = {0};
          //切割之后保存到数组中
          char *argv[ARGC] = {NULL};
   33     printf("[linux@mymachine currpath]# ");
   34     fflush(stdout);
   35     char * s = fgets(commandstr,sizeof(commandstr),stdin);
   36     assert(s);
   37     (void)s;//在release方式发布的时候,去掉了assert,所以s没有被使用,带来了编译警告                 void强制转换之后,什么都没做,但是充当一次使用 
        
          //覆盖掉\n
   38     commandstr[strlen(commandstr)-1] = '\0';
          //切割字符串
   39     int n = split(commandstr,argv);
   40     if(n!=0) continue;
        

           pid_t id = fork();
   44      if(id == 0)
   45      {
              //进行程序替换
   46         execvp(argv[0],argv);
   47         exit(1);
   48       }
   49     int status = 0;
   50     waitpid(id,&status,0);
   51    }
   52 }



其中涉及到了一些c中的库函数,复习笔记如下:

char * strtok(char * str, const char * sep);

  • sep是字符串,定义了用作分隔符的字符集合
  • 第一个参数是要分割的字符串
  • strtok函数找到str中的第一个sep,将其用\0作为结尾,返回指向这个标记的指针
  • strtok中第一个参数为null,函数将在同一个字符串中被保存的位置开始,找到下一个sep
  • 如果找不到sep,就返回null

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

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

相关文章

java项目之高校二手交易平台(ssm+mysql+jsp)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于ssm的高校二手交易平台。技术交流和部署相关看文章末尾&#xff01; 开发环境&#xff1a; 后端&#xff1a; 开发语言&#xff1a;Java 框架…

建站教程:阿里云轻量应用服务器搭建网站流程

阿里云轻量应用服务器怎么使用&#xff1f;阿里云百科分享轻量应用服务器从选配、配置建站环境、轻量服务器应用服务器远程连接、开端口到网站上线全流程&#xff1a; 阿里云轻量应用服务器使用教程 轻量应用服务器很火爆因为成本足够低&#xff0c;阿里云2核2G3M带宽轻量服务…

基于Linux下的C语言项目实战--本地账号管理系统

C语言开发项目实战&#xff1a; C语言是一门通用计算机编程语言&#xff0c;广泛应用于底层开发。C语言的设计目标是提供一种能以简易的方式编译、处理低级存储器、产生少量的机器码以及不需要任何运行环境支持便能运行的编程语言。尽管C语言提供了许多低级处理的功能&#xff…

SpringBoot中间件—封装超时熔断器

需求背景 如果一个服务中有很多涉及需要服务间熔断的地方&#xff0c;就会出现N多下述代码&#xff1a; 1.N个fegnClient接口 FeignClient(name "hello-world-service", fallback HelloWorldFallback.class) public interface HelloWorldService {GetMapping(&q…

U盘写保护怎样去掉?分享3种简单方法!

不知道为什么我的u盘突然就显示被写保护了呢&#xff0c;大家有没有遇到过类似的情况呀&#xff1f;有什么比较好的解决方法可以推荐一下吗&#xff1f; 很多朋友可能对u盘写保护没有什么概念&#xff0c;实际上&#xff0c;u盘写保护可能会阻止我们对其中的文件进行修改或者添…

含生僻字中文校验方式优化

项目姓名校验原本一直是通过utf8正则进行校验&#xff0c;但近期出现的客户生僻字包含双码字&#xff0c;原有校验方式无法实现。需要修改扩大校验范围。 知识点回顾&#xff1a; Unicode是Unicode Standard&#xff08;Unicode标准&#xff09;的简写&#xff1b;Unicode为每…

HNU-操作系统OS-学习感悟

初次接触如此底层的计算机基础课程&#xff0c;我还是很不适应的。 教材用的这本书&#xff0c;实验用的清华大学的ucore实验 好在应试水平没有丢。最后总评94/100。 下面仅从应试角度谈一谈学习的理解 总领 HNU的OS课程平时分给的比较模糊&#xff0c;大致由 作业实验验…

先导式比例溢流阀放大器

EDBW10PL350XY/224、EDBW20PL210XY/724、EDBW30PL105XY/224、EDBW10PL055XY/224先导式比例溢流阀&#xff0c;板式安装&#xff0c;由主阀芯和先导级阀beuec比例放大器组成:先导级阀为比例溢流阀&#xff0c;主阀阅芯可独立调节压力作为安全限压压力&#xff0c;比例先导阀压力…

selenium怎么使用代理IP

什么是selenium Selenium 是一个自动化测试框架&#xff0c;用于测试 Web 应用程序的功能性。它支持多个编程语言&#xff08;如Java&#xff0c;Python&#xff0c;C#等&#xff09;并且可以在操作系统和不同浏览器上运行测试。Selenium 可以模拟用户在浏览器中的操作&#x…

PC端实现滚动分页懒加载

思路 监听列表元素的滚动事件&#xff0c;滚动到底部的时候&#xff0c;加载下一页的数据监听数据加载&#xff0c;判断是否已全部加载结束 实现 监听滚动事件 为列表元素 listBox 绑定 scroll 事件进行监听 <div class"listBox" scroll"watchScroll&qu…

Github点赞120k的Spring全家桶笔记,吃透Offer拿到手软!

Spring框架自诞生以来一直备受开发者青睐&#xff0c;有人亲切的称之为&#xff1a;Spring 全家桶。它包SpringMVC、SpringBoot、Spring Cloud、Spring Data等解决方案。 很多研发人员把spring看作心目中最好的java项目&#xff0c;没有之一。Spring系列包含非常多的项目&…

【LLM】DeepSpeed分布式训练框架

文章目录 一、DeepSpeed介绍1. 分布式背景介绍2. deepspeed介绍 二、deepspeedtransformer代码实战1. 预处理和Json文件2. 训练代码 三、deepspeed加速Bloom lora微调1. 配置文件2. 训练代码 Reference 一、DeepSpeed介绍 1. 分布式背景介绍 分布式计算环境中&#xff0c;主节…

进程间通信方法——命名管道

命名管道 匿名管道应用的一个限制就是只能在具有共同祖先&#xff08;具有亲缘关系&#xff09;的进程间通信。如果我们想在不相关的进程之间交换数据&#xff0c;可以使用FIFO文件来做这项工作&#xff0c;它经常被称为命名管道。&#xff08;命名管道是有文件名的&#xff0…

Vs窗口布局移动窗口vs直接卡死2

(1条消息) Vs窗口布局移动窗口vs直接卡死_vs拖动窗口布局卡死_Ma_Hong_Kai的博客-CSDN博客 由于莫名其妙的更新导致又卡死了&#xff0c;导致最近一年多无法拖动vs的框挺折磨 前一段时间看到一个有意思的命令 搞了搞了 可以拖动了&#xff08;目测应该是微软自己发现这个问…

【图像识别】openCV基础知识

图像处理基础 一、使用OpenCV前要准备的工作1.先导入需要用到的库2.自定义&#xff0c;图片展示函数 二、开始学习常用函数1.生成随机整数①. 函数说明②.代码a. 二维灰度图b. 三维彩色图 ③.代码现象a. 二维灰度图b. 三维彩色图 2.通道的分离与合并①先导入一张图片② 将其RGB…

多元回归预测 | Matlab基于麻雀算法(SSA)优化高斯过程回归(SSA-GPR)的数据回归预测,matlab代码,多变量输入模型

文章目录 效果一览文章概述部分源码参考资料效果一览 文章概述 多元回归预测 | Matlab基于麻雀算法(SSA)优化高斯过程回归(SSA-GPR)的数据回归预测,matlab代码,多变量输入模型 评价指标包括:MAE、RMSE和R2等,代码质量极高,方便学习和替换数据。要求2018版本及以上。 部分源…

合宙Air001震撼来袭!

Air001芯片特性 采用ARM 32位的M0内核&#xff0c;主频可达48MHz&#xff1b; 4K RAM32K Flash&#xff1b; 1.7&#xff5e;5.5V超宽范围供电&#xff0c;USB和电池都能直接供电&#xff1b; 内嵌可配4/8/16/22.12/24MHz的RC振荡器&#xff0c;无需外挂晶振就能48MHz运行…

你连存活到JDK8中著名的Bug都不知道,你怎么敢跳槽涨薪的?

在笔者研究 JDK 源码时&#xff0c;注意到在CopyOnWriteArrayList 和ArrayList 的构造器中都出现了如下 bug 字样 6260652 其实代表的JDK bug 列表中的编号 http://bugs.java.com/bugdatabase/view_bug.do?bug_id6260652 http://bugs.java.com/bugdatabase/view_bug.do?bug…

GBU808-ASEMI薄体整流桥GBU808

编辑&#xff1a;ll GBU808-ASEMI薄体整流桥GBU808 型号&#xff1a;GBU808 品牌&#xff1a;ASEMI 芯片个数&#xff1a;4 封装&#xff1a;GBU-4 恢复时间&#xff1a;≥2000ns 工作温度&#xff1a;-50C~150C 浪涌电流&#xff1a;200A 正向电流&#xff1a;8A 反…

Golang 命令源码文件

Go 语言标准库中专门用于接收和解析命令参数。这个代码包的名字叫 flag。 函数 flag.StringVar 接受 4 个参数。 第 1 个参数是用于存储该命令参数值的地址&#xff0c;具体到这里就是在前面声明的变量 name 的地 址了&#xff0c;由表达式 &name 表示。 第 2 个参数是为…