• 进程程序替换


    目录

    一.替换原理

    二.替换函数

     三.简易shell的制作


    一.替换原理

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

    二.替换函数

    一共有6中替换函数 ,统称为exec函数

    1. #include `
    2. int execl(const char *path, const char *arg, ...);
    3. int execlp(const char *file, const char *arg, ...);
    4. int execle(const char *path, const char *arg, ...,char *const envp[]);
    5. int execv(const char *path, char *const argv[]);
    6. int execvp(const cha *file,char *const argv[]);

    这些函数如果调用成功,则会加载新的程序从启动代码处开始执行。如果调用失败了会返回-1。

    要执行一个全新的程序,首先要找到该程序的位置,以及怎么去执行,可以认为这些就是参数。

    举例子看一下: 

    1.execl:第一个参数是执行参数的路径,第二个参数是可变参数列表,表示你如何执行这个程序,并以NULL结尾。

    1. int main()
    2. {
    3. pid_t id=fork();
    4. if(id==0)
    5. {
    6. printf("我是子进程,我的id=%d\n",getpid());
    7. execl("/usr/bin/ls","ls","-a","-l",NULL);
    8. //execl("./T","T",NULL); //也可运行自己写的程序
    9. printf("我已经执行完毕\n");
    10. }
    11. else
    12. {
    13. int status=0;
    14. int ret=waitpid(id,&status,0);
    15. sleep(2);
    16. if(ret>0)
    17. {
    18. printf("退出信号是%d,退出码是%d\n",(status>>8)&0xFF,status&0xFF);
    19. printf("等待成功\n");
    20. }
    21. }
    22. return 0;
    23. }
    24. [LF@ecs-100710 lesson7]$ ./test
    25. 我是子进程,我的id=20167
    26. total 80
    27. drwxrwxr-x 3 LF LF 4096 Oct 19 15:04 .
    28. drwxrwxr-x 11 LF LF 4096 Oct 10 19:16 ..
    29. -rwxrwxr-x 1 LF LF 8664 Oct 19 15:03 a.out
    30. -rw-rw-r-- 1 LF LF 65 Oct 19 14:40 makefile
    31. -rwxrwxr-x 1 LF LF 8464 Oct 6 10:25 path
    32. -rw-rw-r-- 1 LF LF 186 Oct 6 10:25 path.c
    33. drwxrwxr-x 2 LF LF 4096 Oct 6 11:14 shell
    34. -rwxrwxr-x 1 LF LF 8352 Oct 6 09:38 T
    35. -rw-rw-r-- 1 LF LF 100 Oct 6 09:38 t.c
    36. -rwxrwxr-x 1 LF LF 8664 Oct 19 15:04 test
    37. -rw-rw-r-- 1 LF LF 520 Oct 19 15:04 test.c
    38. -rw-rw-r-- 1 LF LF 763 Oct 19 15:03 tt.c
    39. 退出信号是0,退出码是0
    40. 等待成功

    2.execv:第一个参数是执行参数的路径,第二个参数是一个指针数组,这些指针指向了那些参数(与上面类似)。

    1. char* argv_[]={"ls","-a","-l",NULL};
    2. execv("/usr/bin/ls",argv_);

    execlp(const char *file, const char *arg, ...):第一个参数表示要执行参数的名字,第二个参数是可变参数列表,表示如何执行这个程序,以NULL结尾。

    例如:

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

    int execvp(const char *file,char * const argv[]):与上面类似,第二个参数是指针数组。

    int execle(const char *path, const char *arg, ...,char *const envp[]):第一个参数是执行程序的路径,第二个参数表示如何执行这个程序,以NULL结尾,第三个参数是环境变量。

    例如:先看下传递系统的环境变量:

    1. int main()
    2. {
    3. extern**environ;
    4. pid_t id=fork();
    5. if(id==0)
    6. {
    7. printf("我是子进程\n");
    8. char* const env[]={"PATHMY=hello world",NULL};
    9. execle("./path","path",NULL,environ);
    10. //execle("./path","path",NULL,env);
    11. exit(9);
    12. }
    13. else
    14. {
    15. int status=0;
    16. int ret=waitpid(id,&status,0);
    17. sleep(2);
    18. if(ret>0)
    19. {
    20. printf("退出信号是%d,退出码是%d\n",(status>>8)&0xFF,status&0xFF);
    21. printf("等待成功\n");
    22. }
    23. }
    24. return 0;
    25. }
    1. [LF@ecs-100710 lesson7]$ cat path.c
    2. #include
    3. #include
    4. int main()
    5. {
    6. printf("PATH:%s\n",getenv("PATH"));
    7. printf(".........................\n");
    8. printf("PATHMY:%s\n",getenv("PATHMY"));
    9. return 0;
    10. }

    结果:

     传递自己写的环境变量:execle("./path","path",NULL,env);环境变量是传递了,但是被覆盖了。

     在之前已经学习过了,环境变量是可以被子进程传递下去的,可以用execl函数验证下,

     execl("./path","path",NULL);,用这段代码运行,可以得到一样的结果

    事实上 , 只有 execve 是真正的系统调用 , 其它五个函数最终都调用 execve。

     三.简易shell的制作

    shell:一个命令行解释器,当有命令要执行时,shell会创建一个子进程,让子进程执行命令,而shell只需等待其退出即可。

    过程:

    1. 获取命令行
    2. 解析命令行
    3. 建立一个子进程( fork
    4. 替换子进程( execvp
    5. 父进程等待子进程退出( wait

    选用execvp(const char *file,char * const argv[])函数更加方便,传递程序名,一个指针数组。

    1. #include <stdio.h>
    2. #include <string.h>
    3. #include <stdlib.h>
    4. #include <unistd.h>
    5. #include <sys/types.h>
    6. #include <sys/wait.h>
    7. #include<iostream>
    8. using namespace std;
    9. #define size 100
    10. #define UU " "
    11. int main()
    12. {
    13. char arr1[size];//存储程序名字
    14. char* arr2[size];//程序如何执行
    15. while(true)
    16. {
    17. cout<<"[LF@ecs-100710]#:";
    18. fgets(arr1,size,stdin);//会读取\n,\n后面还有一个\0;
    19. arr1[strlen(arr1)-1]='\0';//将\n置为\0
    20. //printf("%s\n",arr1);
    21. arr2[0]=strtok(arr1," ");//若arr1"ls -l -a",arr2[0]="ls",arr2[1]="-l",arr2[2]="-a"
    22. int index=1; //arr2[3]=NULL;
    23. if(strcmp(arr2[0],"ls")==0)
    24. {
    25. arr2[index++]=(char*)"--color=auto";//添加颜色
    26. }
    27. while(arr2[index]=strtok(NULL,UU))
    28. {
    29. index++;
    30. }
    31. //进行程序替换
    32. pid_t id=fork();
    33. if(id==0)
    34. {
    35. execvp(arr2[0],arr2);
    36. exit(-1);
    37. }
    38. int status=0;
    39. pid_t ret= waitpid(id,&status,0);
    40. if(ret>0)
    41. {
    42. printf("等待子进程成功: sig: %d, code: %d\n", status&0x7F, (status>>8)&0xFF);
    43. }
    44. }
    45. return 0;
    46. }

    结果:

     

     执行cd ..命令或者切换路经的命令时,只是让创建的子进程进行了路径切换,而子进程是运行完毕就结束了,我们希望的是执行cd ..等命令时,是对父进程shell进行操作。由父进程shell执行的命令,叫做内建命令。

    chdir:改变当前进程的路径

    1. if(strcmp(arr2[0],"cd")==0&&arr2[1]!=NULL)
    2. {
    3. chdir(arr2[1]);//更改当前进程的路径
    4. continue;
    5. }

    结果:

    环境变量:

    1.环境变量会被子进程继承下去,所以他会有全局属性
    2.当我们进行程序替换的时候,当前进程的环境变量非但不会被替换,而且是继承父进程的! !因为环境变量是系统的数据

    在shell内部新增自己的环境变量- putenv 接口(自己写的shell里面的环境变量是继承父进程的,在自己写的shell里面增加环境变量,不会影响父进程中的环境变量)

    1. if(strcmp(arr2[0],"export")==0&&arr2[1]!=NULL)
    2. {
    3. putenv(arr2[1]);
    4. continue;
    5. }

    演示:先创建一个程序打印不存在的环境变量

    1. [LF@ecs-100710 lesson2]$ cat test.c
    2. #include
    3. #include
    4. extern char** environ;
    5. int main()
    6. {
    7. printf("%s\n",getenv("AABB"));
    8. return 0;
    9. }

    结果:

     后面运行自己的shell,在自己的shell里面增加环境变量AABB,并运行test程序打印该环境变量。

    结果:

     出错了,并且在自己的shell里面查看环境变量AABB也不存在。

    原因:因为把环境变量放入到 putenv(arr2[1]);,而arr2的内容每次都会重新赋值,所以要将新导入的环境放入一个固定的地方。

    例如:

    1. void putline(char* p)
    2. {
    3. putenv(p);
    4. }
    5. char arr3[size];//保存环境变量
    6. if(strcmp(arr2[0],"export")==0&&arr2[1]!=NULL)
    7. {
    8. strcpy(arr3,arr2[1]);
    9. putline(arr3);//将环境变量导入这个进程中。父进程创建的子进程会继承父进程的环境变量
    10. continue;
    11. }

     结果:

     

    完整代码:

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. using namespace std;
    9. #define size 100
    10. #define UU " "
    11. void putline(char* p)
    12. {
    13. putenv(p);
    14. }
    15. int main()
    16. {
    17. char arr1[size];//存储程序名字
    18. char* arr2[size];//程序如何执行
    19. char arr3[size];//保存环境变量
    20. while(true)
    21. {
    22. cout<<"[LF@ecs-100710]#:";
    23. fgets(arr1,size,stdin);//会读取\n,\n后面有一个\0;
    24. arr1[strlen(arr1)-1]='\0';
    25. //printf("%s\n",arr1);
    26. arr2[0]=strtok(arr1," ");
    27. int index=1;
    28. if(strcmp(arr2[0],"ls")==0)
    29. {
    30. arr2[index++]=(char*)"--color=auto";
    31. }
    32. while(arr2[index]=strtok(NULL,UU))
    33. {
    34. index++;
    35. }
    36. //进行程序替换
    37. if(strcmp(arr2[0],"cd")==0&&arr2[1]!=NULL)
    38. {
    39. chdir(arr2[1]);//更改当前进程的路径
    40. continue;
    41. }
    42. if(strcmp(arr2[0],"export")==0&&arr2[1]!=NULL)
    43. {
    44. strcpy(arr3,arr2[1]);
    45. putline(arr3);//将环境变量导入这个进程中。父进程创建的子进程会继承父进程的环境变量
    46. continue;
    47. }
    48. pid_t id=fork();
    49. if(id==0)
    50. {
    51. execvp(arr2[0],arr2);
    52. exit(-1);
    53. }
    54. int status=0;
    55. pid_t ret= waitpid(id,&status,0);
    56. if(ret>0)
    57. {
    58. printf("等待子进程成功: sig: %d, code: %d\n", status&0x7F, (status>>8)&0xFF);
    59. }
    60. }
    61. return 0;
    62. }

  • 相关阅读:
    如何用jxTMS开发一个功能(二)
    01、RabbitMQ入门
    【KD】2022 计算机学报 深度学习中知识蒸馏研究综述
    新零售进阶|从“人-货-场”到“北极星指标”
    【Java 进阶篇】使用 JDBC 更新数据详解
    TCP可靠性保证总结(非常实用)
    C++初识--------带你从不同的角度理解引用的巧妙之处
    oracle 切换数据库类型报错
    十大排序算法详解-上篇:比较排序算法【python 动态图解】
    那些年我们追过的Devops
  • 原文地址:https://blog.csdn.net/m0_64397669/article/details/127407342