• 进程程序替换


    ✅<1>主页::我的代码爱吃辣
    📃<2>知识讲解:Linux——进程替换
    ☂️<3>开发环境:Centos7
    💬<4>前言:我们创建子进程的目的是什么?想让子进程帮我们执行特定的任务。那么如何让子进程去执行一段新的代码呢?

    一.背景

     二.子进程程序替换

     三.替换函数

    1.execv

     2.execlp

     3.execle

    4.命名理解

    四.实现minishell


    一.背景

    我们创建子进程的目的是什么?想让子进程帮我们执行特定的任务。

    1.让子进程执行父进程的一部分代码

    2.如果子进程指向一个全新的代码呢?这就是进程的程序替换。

    见一见单进程版本进程替换,即父进程指向一个全新的代码:

    隆重介绍一个接口:

    int execl(const char *path, const char *arg, ...);
    1. path:替换程序的路径。
    2. arg:如何执行该程序。
    3. 可变参数:如何执行该程序的参数等。

     测试代码:

    1. #include
    2. #include
    3. int main()
    4. {
    5. printf("--------------------begin-------------\n");
    6. execl("/usr/bin/ls", "ls", "-a", "-l", NULL);
    7. printf("--------------------end---------------\n");
    8. return 0;
    9. }

    注意:

    1. 我们想替换的程序 "ls -a -l"。
    2. ls 的路径:/usr/bin/ls。
    3. "-a" "-l" 时ls命令的参数。
    4. 最后结束要以NULL结尾。

    测试结果:

    注意:

    1. 进程替换以后我们,并没有看到我们源代码里面的最后一个打印。
    2. 原因是程序在替换以后,在后续执行完ls的代码就会退出了,不会回到execl调用后面。

     二.子进程程序替换

    创建好子进程,让子进程调用execl,让子进程去执行替换的程序。

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

    测试代码:

    1. #include
    2. #include
    3. #include
    4. #include
    5. int main()
    6. {
    7. int status;
    8. printf("--------------------begin-------------\n");
    9. pid_t pid = fork();
    10. if (pid == 0)
    11. {
    12. // 我们想替换的程序 "ls -a -l"
    13. // ls 的路径:/usr/bin/ls
    14. //"-a" "-l" 时ls命令的参数
    15. // 最后结束要以NULL结尾
    16. execl("/usr/bin/ls", "ls", "-a", "-l", NULL);
    17. }
    18. waitpid(-1, &status, 0);//阻塞等待子进程退出。
    19. if (WIFEXITED(status))
    20. printf("子进程退出,退出码:%d\n", WEXITSTATUS(status));
    21. else
    22. printf("子进程异常,收到信号%d\n", status & 0x7F);
    23. printf("--------------------end---------------\n");
    24. return 0;
    25. }

    测试结果:

    进程替换原理:

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

     三.替换函数

    其实有六种以exec开头的函数,统称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 char *file, char *const argv[]);
    int execve(const char *path, char *const argv[], char *const envp[]);

    函数解释:

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

     介绍其中几个:

    1.execv

    int execv(const char *path, char *const argv[]);
    1. path:程序所在的路径,
    2. argv:是一个指针数组,数组每一个元素代表我们需要怎么执行程序。

    测试代码:

    1. #include
    2. #include
    3. #include
    4. #include
    5. int main()
    6. {
    7. int status;
    8. printf("--------------------begin-------------\n");
    9. pid_t pid = fork();
    10. if (pid == 0)
    11. {
    12. // 我们想替换的程序 "ls -a -l"
    13. // ls 的路径:/usr/bin/ls
    14. //"-a" "-l" 时ls命令的参数
    15. // 最后结束要以NULL结尾
    16. char *argv[] = {"ls", "-a", "-l", NULL};
    17. execv("/usr/bin/ls", argv);
    18. }
    19. waitpid(-1, &status, 0);
    20. if (WIFEXITED(status))
    21. printf("子进程退出,退出码:%d\n", WEXITSTATUS(status));
    22. else
    23. printf("子进程异常,收到信号%d\n", status & 0x7F);
    24. printf("--------------------end---------------\n");
    25. return 0;
    26. }

    测试结果:

     2.execlp

    int execlp(const char *file, const char *arg, ...);
    1. file:程序名称。
    2. 后续参数:需要怎么执行程序。
    3. 不需要给出程序路径,execlp会自己到环境变量中找对应的程序。

    测试代码:

    1. #include
    2. #include
    3. #include
    4. #include
    5. int main()
    6. {
    7. int status;
    8. printf("--------------------begin-------------\n");
    9. pid_t pid = fork();
    10. if (pid == 0)
    11. {
    12. // 我们想替换的程序 "ls -a -l"
    13. // ls 的路径:/usr/bin/ls
    14. //"-a" "-l" 时ls命令的参数
    15. // 最后结束要以NULL结尾
    16. execlp("ls", "ls", "-a", "-l", NULL);
    17. }
    18. waitpid(-1, &status, 0);
    19. if (WIFEXITED(status))
    20. printf("子进程退出,退出码:%d\n", WEXITSTATUS(status));
    21. else
    22. printf("子进程异常,收到信号%d\n", status & 0x7F);
    23. printf("--------------------end---------------\n");
    24. return 0;
    25. }

    测试结果:

     3.execle

    int execle(const char *path, const char *arg, ...,char *const envp[]);
    1. path:程序路径。
    2. envp:是一个指针数组,用来传输环境变量。
    3. arg:我们如何执行程序。

    测试代码:

    1. #include
    2. #include
    3. #include
    4. #include
    5. int main()
    6. {
    7. extern char **environ;
    8. int status;
    9. printf("--------------------begin-------------\n");
    10. pid_t pid = fork();
    11. if (pid == 0)
    12. {
    13. // 我们想替换的程序 "ls -a -l"
    14. // ls 的路径:/usr/bin/ls
    15. //"-a" "-l" 时ls命令的参数
    16. // 最后结束要以NULL结尾
    17. execle("./newdir/otherproc", "otherproc", NULL, environ);
    18. }
    19. waitpid(-1, &status, 0);
    20. if (WIFEXITED(status))
    21. printf("子进程退出,退出码:%d\n", WEXITSTATUS(status));
    22. else
    23. printf("子进程异常,收到信号%d\n", status & 0x7F);
    24. printf("--------------------end---------------\n");
    25. return 0;
    26. }

    newdir/otherproc.cc

    1. #include
    2. #include
    3. #include
    4. using namespace std;
    5. int main()
    6. {
    7. cout << "hello world"
    8. << "hello:" << getenv("hello") << endl;
    9. cout << "hello world"
    10. << "hello:" << getenv("hello") << endl;
    11. cout << "hello world"
    12. << "hello:" << getenv("hello") << endl;
    13. return 0;
    14. }

    测试结果:

    4.命名理解

    这些函数原型看起来很容易混,但只要掌握了规律就很好记。

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

     exec调用总结:

    1. #include
    2. int main()
    3. {
    4. char *const argv[] = {"ps", "-ef", NULL};
    5. char *const envp[] = {"PATH=/bin:/usr/bin", "TERM=console", NULL};
    6. execl("/bin/ps", "ps", "-ef", NULL);
    7. // 带p的,可以使用环境变量PATH,无需写全路径
    8. execlp("ps", "ps", "-ef", NULL);
    9. // 带e的,需要自己组装环境变量
    10. execle("ps", "ps", "-ef", NULL, envp);
    11. execv("/bin/ps", argv);
    12. // 带p的,可以使用环境变量PATH,无需写全路径
    13. execvp("ps", argv);
    14. // 带e的,需要自己组装环境变量
    15. execve("/bin/ps", argv, envp);
    16. exit(0);
    17. }

    事实上,只有execve是真正的系统调用,其它五个函数最终都调用 execve,所以execve在man手册 第2节,其它函数在man手册第3节。这些函数之间的关系如下图所示。

    四.实现minishell

    用下图的时间轴来表示事件的发生次序。其中时间从左向右。shell由标识为sh的方块代表,它随着时间的流逝从左向右移动。shell从用户读入字符串"ls"。shell建立一个新的进程,然后在那个进程中运行ls程序并等待那个进程结束。

     然后shell读取新的一行输入,建立一个新的进程,在这个进程中运行程序 并等待这个进程结束。
    所以要写一个shell,需要循环以下过程:

    1. 获取命令行
    2. 解析命令行
    3. 建立一个子进程(fork)
    4. 替换子进程(execvp

    代码:

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #define SEP " "
    9. #define MAX 1024
    10. #define MAX_SUB 64
    11. // 分割命令"ls -a -l" --> "ls","-a" "-l"
    12. void split(char buff[], char *subbuff[])
    13. {
    14. assert(buff);
    15. assert(subbuff);
    16. int i = 0;
    17. subbuff[i++] = strtok(buff, SEP);
    18. while (subbuff[i++] = strtok(NULL, SEP)) // 如果没有找到分割,就会返回NULL
    19. ;
    20. }
    21. // 代码测定,打印命令
    22. void debug(char *subbuff[])
    23. {
    24. int i = 0;
    25. while (subbuff[i])
    26. {
    27. printf("%s\n", subbuff[i++]);
    28. }
    29. }
    30. // 显示环境变量
    31. void showenv()
    32. {
    33. extern char **environ;
    34. for (int i = 0; environ[i]; i++)
    35. {
    36. printf("[%d]:%s\n", i, environ[i]);
    37. }
    38. }
    39. int main()
    40. {
    41. int status = 0; // 退出码参数
    42. char myenv[32][512] = {0}; // 用户自定义环境变量
    43. int index_env = 0;
    44. int last_exit = 0;
    45. while (1)
    46. {
    47. char buff[MAX] = {0}; // 存储命令行输入的命令字符串
    48. char *subbuff[MAX_SUB] = {NULL};
    49. printf("wq@[aliyum]$:");
    50. fflush(stdout);
    51. // 1.获得命令字符串
    52. fgets(buff, sizeof(buff), stdin);
    53. // 2.解析命令
    54. // ls -a -l\n\0,strlen=9,index(\n)=8,去除回车。
    55. buff[strlen(buff) - 1] = '\0';
    56. // 分割字符串
    57. split(buff, subbuff);
    58. // 处理内建命令
    59. // 注意:cd,export,env,echo,等命令都是内建命令,即这些命令不能创建子进程执行,只能bash自己执行
    60. if (strcmp(subbuff[0], "cd") == 0)
    61. {
    62. if (subbuff[1] != NULL)
    63. chdir(subbuff[1]);
    64. continue;
    65. }
    66. else if (strcmp(subbuff[0], "export") == 0)
    67. {
    68. if (subbuff[1] != NULL)
    69. {
    70. // 这里不能将subbuff[1]直接导入环境变量,因为环境变量表存储的都是指针,必须使用一个单独空间
    71. strcpy(myenv[index_env], subbuff[1]);
    72. putenv(myenv[index_env++]);
    73. }
    74. continue;
    75. }
    76. else if (strcmp(subbuff[0], "env") == 0)
    77. {
    78. showenv();
    79. continue;
    80. }
    81. else if (strcmp(subbuff[0], "echo") == 0)
    82. {
    83. // echo $PATH
    84. if (subbuff[1][0] == '$')
    85. {
    86. if (subbuff[1][1] == '?') // echo $?//最近一次推出吗
    87. {
    88. printf("%d\n", last_exit);
    89. }
    90. else // 提取环境变量
    91. {
    92. // PATH
    93. char *subenv = subbuff[1] + 1;
    94. char *get_env = getenv(subenv);
    95. if (get_env != NULL)
    96. {
    97. printf("%s=%s\n", subenv, get_env);
    98. }
    99. }
    100. }
    101. continue;
    102. }
    103. if (strcmp(subbuff[0], "ls") == 0)
    104. {
    105. int comm_index = 0;
    106. while (subbuff[comm_index])
    107. {
    108. comm_index++;
    109. }
    110. // 增加高亮
    111. subbuff[comm_index] = "--color=auto";
    112. }
    113. // 3.创建子进程
    114. pid_t pid = fork();
    115. assert(pid >= 0);
    116. if (pid == 0) // 子进程
    117. {
    118. extern char **environ;
    119. // 4. 程序替换
    120. execve(subbuff[0], subbuff, environ);
    121. }
    122. // // 测试
    123. // debug(subbuff);
    124. waitpid(pid, &status, 0);
    125. if (WIFEXITED(status))
    126. {
    127. // 子进程退出设置退出码
    128. last_exit = WEXITSTATUS(status);
    129. }
    130. }
    131. return 0;
    132. }

  • 相关阅读:
    mysql跨库关联查询(dblink)
    秋招日寄9.10(备战秋招的第三天)
    flutter ios Exception : No Impeller Context is Available
    SystemVerilog学习(4)——自定义结构
    webpack相关面试题
    系列学习 SpringCloud-Alibaba 框架之第 3 篇 —— Nacos mysql支持、搭建集群
    JavaScript面试题6
    第13章 并发编程高阶(一)
    【C++优先级队列priority_queue基础】基本使用,模拟实现,堆
    MATLAB进行特征选择
  • 原文地址:https://blog.csdn.net/qq_63943454/article/details/132914635