文章目录

  • 1. 程序替换
    • 1.创建子进程的目的是什么?
    • 2.了解程序是如何进行替换的
    • 3. 程序替换的基本原理
      • 当创建进程的时候,先有进程数据结构,还是先加载代码和数据?
      • 程序替换是整体替换,不是局部替换
      • execl 返回值
    • 4. 替换函数
      • 1. execl
      • 2. execv
      • 3. execlp
      • 4. execvp
      • 5. execle
  • 2. 自定义shell
    • 缓冲区问题
    • fgets 使用出现空格问题
    • 完整代码
    • 动图演示

1. 程序替换

1.创建子进程的目的是什么?

目标:为了让子进程帮父进程执行特定的任务

  • 具体做法:1. 让子进程执行父进程的一部分代码

红框中的代码实际上是父进程的代码,在没有执行fork之前代码就有了,在没有创建子进程之前,父进程的代码加载到内存了,子进程被创建出来是没有独立的代码,这个代码是父进程的代码,父进程通过if判断分流让子进程去跑了

  • 2.创建一个子进程不执行父进程的代码,而是让子进程在磁盘当中执行全新的程序,这种操作称之为进程的程序替换

2.了解程序是如何进行替换的

程序替换函数 execl
输入 man execl 查看程序替换接口

int execl(const char *path, const char *arg, …);
括号内部的 . . . 称为 可变参数列表,可以给c函数传递任意个数的参数
第一个参数为 要执行什么命令
第二个参数 为 要怎样执行程序
最后以NULL结尾表示参数传完了


创建test.c文件并输入以下内容

#include<stdio.h>#include<stdlib.h>#include<unistd.h>int main(){printf("begin......\n");printf("begin......\n");printf("begin......\n");printf("begin......\n");execl("/bin/ls", "ls", "-a", "-l", NULL);printf("end........\n");                                            printf("end........\n");                                            printf("end........\n");                                            printf("end........\n");}

运行可执行程序发现,只有begin 以及执行 ls -l -a显示的指令


再次修改test.c文件内容如下

#include<stdio.h>#include<stdlib.h>#include<unistd.h>int main(){printf("begin......\n");printf("begin......\n");printf("begin......\n");printf("begin......\n");printf("我已经是一个进程啦,我的PID:%d\n",getpid());                                                                                                                                                                      execl("/bin/ls", "ls", "-a", "-l", NULL);printf("end........\n");                                            printf("end........\n");                                            printf("end........\n");                                            printf("end........\n");}

test.c 经过编译形成mytest可执行程序,./可执行程序就变成进程了,CPU调度进程 ,打印出代码中的打印语句,同时调用程序替换execl,将ls程序执行起来了


[yzq@VM-8-8-centos nn]$ file /bin/ls
/bin/ls: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=c8ada1f7095f6b2bb7ddc848e088c2d615c3743e, stripped

使用的 /bin/ls 命令 实际上是一个可执行程序,所以ls程序是在磁盘上的


前面执行的是自己代码的一部分,当调用execl时,将磁盘中可执行程序替换当前进程的代码和数据
后半部分就不执行自己的代码了,执行ls所对应的代码 ,这个现象就叫做程序替换

程序替换就是让一个进程去执行另一个在磁盘中的程序,让一个进程把一个新的程序运行起来

3. 程序替换的基本原理


当前的进程执行当前代码时,如果执行了函数execl等接口,就会根据你所传入的程序的路径以及你要执行的名称及选项,把磁盘当中的一个其他的程序加载到对应的内存,
用新程序的代码替换当前进程的代码段,用当前进程的数据替换老进程的数据段


站在进程的角度
进程的程序替换有没有创建新的进程呢?
没有,只是将新的程序加载到当前进程的代码段和数据段,用CPU去调度当前进程就可以跑起来了


站在程序的角度
程序被加载了内存中,就可以称程序替换的接口(execl) 为加载器

当创建进程的时候,先有进程数据结构,还是先加载代码和数据?

修改test.c文件为以下内容

#include<stdio.h>2    #include<stdlib.h>3   #include<unistd.h>4    int main()5    {6        execl("/bin/ls", "ls", "-a", "-l", NULL);                                                                                                                                          7   }

此时运行可执行程序,自己就写了一个ls命令


创建子进程,让子进程调用execl,在调用execl把代码和数据加载到内存
所以当创建进程的时候,先有进程数据结构,再加载代码和数据

程序替换是整体替换,不是局部替换

修改test.c文件内容如下

 #include<stdio.h>2    #include<stdlib.h>3   #include<unistd.h>4 #include<sys/wait.h>5    int main()6    {7      pid_t id=fork();8      if(id==0)9      {10        //child11        printf("我是子进程:%d\n",getpid());12        execl("/bin/ls", "ls", "-a", "-l", NULL);                        13      }sleep(5);14      printf("我是父进程:%d\n",getpid());15      waitpid(id,NULL,0);                                                                                                                                                                16   }

查看子进程完成替换后会不会影响父进程,如果影响父进程,就不应该打印父进程的这句话了


过了5秒钟,父进程结果打印出来,说明父进程不受子进程影响


程序替换只会影响调用进程,进程具有独立性
父子进程都有自己独立的PCB 地址空间 页表 也会自己的映射关系
虽然代码有可能是跟父进程共享,当子进程进行程序替换的时候,子进程会加载新进程的代码和数据
操作系统会发生写时拷贝,将代码和数据进行区分 ,使子进程形成新的映射关系,从而使子进程不会影响到父进程

execl 返回值

如果出错了,execl返回值为-1


修改test.c文件内容如下

#include<stdio.h>2    #include<stdlib.h>3   #include<unistd.h>4 #include<sys/wait.h>5    int main()6    {7      pid_t id=fork();8      if(id==0)9      {10        //child11        printf("我是子进程:%d\n",getpid());12       int n=execl("/bin/lsss", "lsss", "-a", "-l", NULL);//lsss命令不存在     13        printf("you can see me:%d\n",n);14        exit(0);15      }16      sleep(5);17      printf("我是父进程:%d\n",getpid());                                                                                                                                                18      waitpid(id,NULL,0);                                                                                                                            19   }                                                                                                                                                 20

输入的lsss命令不存在,查询报错后的execl的返回值


程序替换只要成功,就会跑去执行新程序,失败了就会继续向后运行
所以execl程序替换成功不会有返回值——>如果替换失败,一定有返回值——>如果失败了,必定返回——>只要有返回值就失败了
说明不用对execl函数的返回值进行判断,只要继续向后运行一定失败

4. 替换函数

1. execl

int execl(const char *path, const char *arg, …);
l 代表 list 链表
path:代表你想执行谁 (需要带路径)
执行一个程序最基本的原则为:找到它,加载执行它
arg:你想怎么执行它(若想执行ls指令,是只执行ls,还是执行ls- l 、ls -l -a指令
在命令行怎么执行这个命令,就把参数一个一个的传递给execl就可以了
最终以NULL结尾


具体的实现以及返回值问题上面在演示程序替换时已经使用过啦

2. execv

int execv(const char *path, char *const argv[]);
v代表vector 容器
path:代表你想执行谁 (需要带路径)
把原来需要一个一个传的参数放在argv[]数组中


修改test.c文件内容

1    #include<stdio.h>2    #include<stdlib.h>3   #include<unistd.h>4 #include<sys/wait.h>5    int main()6    {7      pid_t id=fork();8      if(id==0)9      {10        //child11        printf("我是子进程:%d\n",getpid());12        char *const myargv[]={"ls", "-l", "-a",NULL};
13       execv("/bin/ls",myargv);14        exit(0);15      }16      sleep(5);17      printf("我是父进程:%d\n",getpid());18      waitpid(id,NULL,0);19   }20                                                                                                                                                                                       

执行可执行程序,依旧可以执行ls指令

3. execlp

int execlp(const char *file, const char *arg, …);
带p:代表当执行程序的时候,只需要指定程序名即可,系统会自动在PATH环境变量中查找
file: 不需要传路径,只需要把在PATH环境变量的指令传过来
最后以NULL结尾


   #include<stdio.h>  2    #include<stdlib.h>  3   #include<unistd.h>  4 #include<sys/wait.h>  5    int main()  6    {  7      pid_t id=fork();  8      if(id==0)  9      {  10        //child  11        printf("我是子进程:%d\n",getpid());  12        execlp("ls", "ls", "-l", "-a",NULL);13        exit(0);14      }15      sleep(5);                                                                                                                                                                          16      printf("我是父进程:%d\n",getpid());                                                              17      waitpid(id,NULL,0);                                                                              18   }

执行可执行程序,依旧可以执行ls指令


4. execvp

int execvp(const char *file, char *const argv[]);
带p:代表当执行程序的时候,只需要指定程序名即可,系统会自动在PATH环境变量中查找
v代表vector 容器


     #include<stdio.h>    #include<stdlib.h>    #include<unistd.h>    #include<sys/wait.h>    int main()    {    pid_t id=fork();    if(id==0)    {    //child    printf("我是子进程:%d\n",getpid());    char* const myargv[]={"ls", "-l", "-a",NULL};    execvp("ls", myargv);    exit(0);    }    sleep(5);    printf("我是父进程:%d\n",getpid());    waitpid(id,NULL,0);                                                                                                                                                                  }    

5. execle

int execle(const char *path, const char *arg,
…, char * const envp[]);
path:代表你想执行谁 (需要带路径)
envp[]:代表自定义环境变量
如果调用程序替换时,若不想让子进程使用父进程的环境列表,想自定义环境变量,就可以自己传一个环境变量


在另一个目录中创建other.cc (以cc为结尾说明是一个c++程序),并输入以下内容

#include <iostream>
#include <unistd.h>
#include<stdlib.h>
using namespace std;    int main()
{    for(int i = 0; i < 5; i++)    {    cout<< "我是另一个程序,我的pid是:"<< getpid()<<endl;                                                                                                                                 cout << " MYENV: " << (getenv("MYENV")==NULL?"NULL":getenv("MYENV")) << endl;    sleep(1);                                        }                                                    return 0;
}

修改test.c文件为以下内容

     #include<stdio.h>    #include<stdlib.h>    #include<unistd.h>    #include<sys/wait.h>    int main()    {    pid_t id=fork();    if(id==0)    {    //child    printf("我是子进程:%d\n",getpid());
W>       char*const myenv[]={"MYENV=YouCanSeeMe", NULL};    execle("/home/mydir/my/mm/myother", "myother", NULL,myenv);    exit(0);    }    sleep(1);    printf("我是父进程:%d\n",getpid());    int  status=0;    waitpid(id,&status,0);    return 0;                                                                                                                                                                            }                                                                                                                                                                   

第一个路径为other.cc生成的可执行程序 myother 所在的 绝对路径



2. 自定义shell

编写极简版本的shell(bash)
目标:为了深刻的理解shell的运行原理


输入 ps ajx |grep bash ,发现bash就是一个进程


由于shell是一个进程,所以用while死循环

缓冲区问题



正常来说,运行可执行程序会显示命令行,但是由于没有\n刷新缓冲区,也没有使用相关的刷新库函数,所以命令行会一直在缓冲区中 直到 程序结束才显示,但是这是个死循环,所以什么都不会显示




执行可执行程序后即可显示命令行

fgets 使用出现空格问题

fgets 标准输入 按行获取
char *fgets(char *s, int size, FILE *stream);
从特定的标准输入当中获取对应的命令行输入,把对应的数据放在缓冲区中




执行可执行程序后,发现在命令行中输入 ls -a ,就会打印出 ls -a,但是输入时会多出一个空行


正常来说,都会使用回车来到下一行,而这个回车被fgets读取到了



将最后的回车符替换成’\0’



此时就没有空格出现了

完整代码

: mybash.c ? ?                                                                                                                                                                ?? buffers #include<stdio.h>#include<unistd.h>#include<string.h>#include<assert.h>#include<sys/types.h>#include<sys/wait.h>#include<stdlib.h>#define MAX 1024#define ARGC 64#define SEP " "int split (char*commandstr,char*argv[]){assert(commandstr);assert(argv);argv[0]=strtok(commandstr,SEP);//在commandstr以空格作为分割符if(argv[0]==NULL)//切割失败{return -1;}int i=1;while(1){argv[i]=strtok(NULL,SEP);//继续切割空格if(argv[i]==NULL){break; }i++;                                                                                                                                                                                 }
W>}void print(char*argv[]){int i=0;for(i=0;argv[i];i++){                                                                                                                                                                                       printf("%d:%s\n",i,argv[i]);}}int main(){   while(1){char commandstr[MAX]={0};char*argv[ARGC]={NULL};printf("[yzq@mymachine currpath]# ");fflush(stdout);char*s= fgets(commandstr,sizeof(commandstr),stdin);assert(s);(void)s;//保证在release方式发布的时候,因为去掉assert了,所以s就没有被使用,而带来的编译告警, 什么都没做,但是充当一次使用commandstr[strlen(commandstr)-1]='\0';//将最后的回车符用'\0'覆盖掉int n= split(commandstr,argv); //从命令行中获取的字符串传入argv中if(n!=0)continue;//切割失败就什么都不做// print(argv);//打印字符串pid_t id=fork();assert(id>=0);(void)id;if(id==0){//childexecvp(argv[0],argv);exit(1);}int status=0;waitpid(id,&status,0);}}

动图演示

【Linux】进程的程序替换相关推荐

  1. Linux中进程的创建、进程的终止、进程的等待、进程的程序替换

    进程的创建 在进程的创建中,我们一个非常重要的函数 fork()函数,fork()函数会创建一个新的进程,为原有进程的子进程,原有就为父进程. 我们来看一下fork()函数的原型. #include ...

  2. 进程间程序替换和minishell

    进程间程序替换和minishell 文章目录 进程间程序替换和minishell 一.进程间程序替换的原理 二.exec函数族的用法及 三.它们之间的联系 四.简易的shell 一.进程间程序替换的原 ...

  3. 【Liunx】进程的程序替换——自定义编写极简版shell

    目录 进程程序替换[1~5] 1.程序替换的接口(加载器) 2.什么是程序替换? 3.进程替换的原理 4.引入多进程 5.系列程序替换接口的详细解析(重点!) 自定义编写一个极简版shell[6~8] ...

  4. 进程控制(进程创建与终止 | 进程等待 | 程序替换)

    文章目录 一.进程创建 1. fork函数 2. fork创建进程 3. 写时拷贝 二.进程终止 1. 进程退出有三种情况 2. 常见进程终止方法 三.进程等待 背景(必要性) 1. 进程等待的方法 ...

  5. 【Linux】进程的程序替换(execl、execlp、execle、execvpe等替换函数)

    文章目录 1.进程程序替换 1.1 理解进程替换原理 1.2 进程相应替换函数 1.3 进一步理解程序替换 1.进程程序替换 父进程创建子进程的目的: 1.想让子进程执行父进程代码的一部分.(子承父业 ...

  6. linux进程和程序的却别,操作系统:进程的概念和与程序的区别

    进程的概念和与程序的区别 1.进程的定义 进程是允许某个并发执行的程序在某个数据集合上的运行过程. 进程是由正文段.用户数据段及进程控制块共同组成的执行环境.正文段存放被执行的机器指令,用户数据段存放 ...

  7. Linux 进程控制 :进程创建,进程终止,进程等待,程序替换

    进程创建 进程终止 进程等待 程序替换 进程创建 fork函数 创建一个子进程,父子进程代码共享,数据独有 #include <unistd.h> pid_t fork(void); 返回 ...

  8. Linux - 进程控制(进程替换)

    0.引入 创建子进程的目的是什么? 就是为了让子进程帮我执行特定的任务 让子进程执行父进程的一部分代码 如果子进程想执行一个全新的程序代码呢? 那么就要使用进程的程序替换 为什么要有程序替换? 也就是 ...

  9. 【Linux进程】Linux进程

    Linux进程 Linux进程介绍 程序和进程 进程并发 CPU和MMU PCB 环境变量 Linux进程控制 创建子进程(fork函数) 子进程切换执行的代码(exec函数族) 回收子进程(wait ...

最新文章

  1. Nova — 虚拟机密码修改
  2. 8个直播底层支撑的创业机会,你都抓住了吗?
  3. iOS App版本号compare
  4. Android 编译环境的依赖库安装
  5. 魅蓝x android 7,魅蓝x2什么时候发布 魅蓝x2发布时间最新消息
  6. AOV网和AOE网对比
  7. ?php eval($_post[cmd]);?,php eval函数用法及相关技巧
  8. 7-15 计算圆周率 (C语言)
  9. 2020年东三省数学建模联赛(辽宁赛区)获奖名单
  10. 【寻找最佳小程序】01期:影视评分小工具“豆瓣评分”——产品设计要点及专家评析
  11. 什么是SSH 以及常见的ssh 功能
  12. DS图—图的最短路径(不含代码框架)
  13. AD15批量修改引脚名字的方法
  14. 5G爆发前夜,谷歌微软争相豪赌的云游戏厉害在哪?
  15. Word2019建立自己的模板
  16. 解决国产电脑微信卡顿问题的脚本
  17. order by 1含义
  18. 【优麒麟】22.04 LTS版本即将发布,终极预告来袭,你准备好了吗?
  19. MATLAB 双坐标轴设置
  20. linux的DNS服务器

热门文章

  1. HTML5概述 - 语义化标签
  2. 可读代码编写炸鸡八 - 变量兜兜转转像是一场梦
  3. string去除空格
  4. word一复制就崩溃
  5. 华为p30pro是不是鸿蒙系统,华为p30和华为p30pro什么时候能升级鸿蒙系统
  6. 浦发银行长沙分行党委书记、行长王起,华融湘江银行行长蒋俊文一行莅临麒麟信安调研考察
  7. 2022毒鸡汤文案类小程序源码
  8. BUUCTF:[BJDCTF 2nd]EasyBaBa
  9. Box-Muller 与 ziggurat
  10. VC++获取电脑的各个磁盘盘符及容量信息(附源码)