【Linux】18.Linux历程控制(2)

火影  论坛元老 | 2025-1-24 18:56:38 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 1019|帖子 1019|积分 3057


3. 历程程序替换

3.1 单历程版 – 看看程序替换

makefile
  1. mycommand:mycommand.c
  2.         gcc -o $@ $^ -std=c99
  3. .PHONY:clean
  4. clean:
  5.         rm -f mycommand
复制代码
mycommand.c
  1. #include <stdio.h>
  2. #include <unistd.h>// 提供execl, getpid等函数
  3. #include <stdlib.h>
  4. int main(){
  5.     // 打印exec调用前的进程信息
  6.     // getpid(): 获取当前进程ID
  7.     // getppid(): 获取父进程ID
  8.     printf("before: I am a process, pid:%d, ppid:%d\n",getpid(),getppid());
  9.    
  10.     //这类方法的标准写法
  11.     // execl函数执行新程序
  12.     // 参数1 "/usr/bin/ls": 要执行的程序的完整路径
  13.     // 参数2 "ls": 程序名称(argv[0])
  14.     // 参数3 "-a": 显示所有文件(包括隐藏文件)
  15.     // 参数4 "-l": 使用长格式显示
  16.     // 参数5 NULL: 参数列表结束标志
  17.     execl("/usr/bin/ls","ls", "-a", "-l", NULL);
  18.    
  19.     // 如果exec执行成功,这行代码永远不会被执行
  20.     // 因为原程序的内容已被ls程序替换
  21.     printf("after: I am a process, pid:%d, ppid:%d\n",getpid(),getppid());
  22.     return 0;
  23. }
复制代码
打印效果:
  1. ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson17$ ./mycommand
  2. before: I am a process, pid:261495, ppid:261085
  3. total 36
  4. drwxrwxr-x  2 ydk_108 ydk_108  4096 Jan 20 22:26 .
  5. drwxrwxr-x 13 ydk_108 ydk_108  4096 Jan 20 22:14 ..
  6. -rw-rw-r--  1 ydk_108 ydk_108    82 Jan 20 22:16 makefile
  7. -rwxrwxr-x  1 ydk_108 ydk_108 16832 Jan 20 22:26 mycommand
  8. -rw-rw-r--  1 ydk_108 ydk_108   895 Jan 20 22:26 mycommand.c
  9. ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson17$
复制代码
这个程序被ls替换了。

一开始这个历程有自己的数据段和代码段,ls有自己的数据段和代码段,但是这里ls直接把mycommand的历程的代码段和数据段在内存中覆盖了,进而把页表中的虚拟地址也更改了。
这个就叫做历程替换,也就是历程替换的基本原理。

3.2 替换原理

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


3.3 替换函数

其实有六种以exec开头的函数,统称exec函数:(这六个都是库函数调用接口,他们的区别只是传参的不同)
  1. #include <unistd.h>
  2. int execl(const char *path, const char *arg, ...);// 第一个参数是完整路径,第二个参数通常是程序名
  3. int execlp(const char *file, const char *arg, ...);//execlp自己会在默认的PATH系统的环境变量里面找,所以可以不带路径
  4. int execle(const char *path, const char *arg, ...,char *const envp[]);// e 表示可以传递环境变量,最后一个参数是环境变量数组
  5. int execv(const char *path, char *const argv[]);// v 表示参数以数组形式传递,比execl更适合参数数量不确定的情况
  6. int execvp(const char *file, char *const argv[]);// 结合了v(数组)和p(PATH搜索)的特点
  7. int execvpe(const char *file, char *const argv[], char *const envp[]);// 结合了v(数组)、p(PATH搜索)和e(环境变量)的特点
复制代码
  这个和上面6个不一样,这个是体系调用接口,上面6个是库函数调用接口。
  int execve(const char *path, char *const argv[], char *const envp[]);
  函数解释

   

  • 这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。
  • 如果调用出错则返回-1
  • 以是exec函数只有出错的返回值而没有成功的返回值。
    要执行一个程序起首就要找到这个程序,以是这内里全部的exec函数的第一个参数都是帮我们绝对如何找到这个程序的。
  找到这个程序后,要告诉体系如何执行这个程序。要不要涵盖选项?涵盖哪些?
    如果exec*能够实现体系程序,那么可以实现我们自己的程序吗?
  可以的。
  
定名理解

这些函数原型看起来很容易混,但只要把握了规律就很好记。 (l和v不会同时出现)
   

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


3.4 多历程版 – 验证各种程序替换接口

  1. #include <stdio.h>
  2. #include <unistd.h>// 提供execl, getpid等函数
  3. #include <stdlib.h>
  4. #include <sys/types.h>
  5. #include <sys/wait.h>
  6. int main(){
  7.     pid_t id =fork();
  8.     if(id == 0){
  9.         //child
  10.         printf("before: I am a process, pid:%d, ppid:%d\n",getpid(),getppid());
  11.         sleep(5);
  12.             execl("/usr/bin/ls","ls", "-a", "-l", NULL);
  13.             printf("after: I am a process, pid:%d, ppid:%d\n",getpid(),getppid());
  14.         exit(0);
  15.     }
  16.     //father
  17.     pid_t ret = waitpid(id, NULL, 0);
  18.     if(ret > 0){
  19.         printf("wait success, father pid:%d, ret id:%d\n",getpid(), ret);
  20.     }
  21.     sleep(5);
  22.     return 0;
  23. }
复制代码
运行效果:
  1. ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson17$ ./mycommand
  2. before: I am a process, pid:261679, ppid:261678
  3. total 36
  4. drwxrwxr-x  2 ydk_108 ydk_108  4096 Jan 20 22:54 .
  5. drwxrwxr-x 13 ydk_108 ydk_108  4096 Jan 20 22:14 ..
  6. -rw-rw-r--  1 ydk_108 ydk_108    82 Jan 20 22:16 makefile
  7. -rwxrwxr-x  1 ydk_108 ydk_108 17008 Jan 20 22:54 mycommand
  8. -rw-rw-r--  1 ydk_108 ydk_108   628 Jan 20 22:54 mycommand.c
  9. wait success, father pid:261678, ret id:261679
  10. ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson17$
复制代码
通过下令查看:
  1. ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson17$ while :; do ps ajx |head -1 && ps ajx |grep mycommand |grep -v grep; sleep 1;echo"----------";done
  2.    PPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
  3. echo----------: command not found
  4.    PPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
  5. echo----------: command not found
  6.    PPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
  7. 261085  261678  261678  261085 pts/0     261678 S+    1001   0:00 ./mycommand
  8. 261678  261679  261678  261085 pts/0     261678 S+    1001   0:00 ./mycommand
  9. echo----------: command not found
  10.    PPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
  11. 261085  261678  261678  261085 pts/0     261678 S+    1001   0:00 ./mycommand
  12. 261678  261679  261678  261085 pts/0     261678 S+    1001   0:00 ./mycommand
  13. echo----------: command not found
  14.    PPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
  15. 261085  261678  261678  261085 pts/0     261678 S+    1001   0:00 ./mycommand
  16. 261678  261679  261678  261085 pts/0     261678 S+    1001   0:00 ./mycommand
  17. echo----------: command not found
  18.    PPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
  19. 261085  261678  261678  261085 pts/0     261678 S+    1001   0:00 ./mycommand
  20. 261678  261679  261678  261085 pts/0     261678 S+    1001   0:00 ./mycommand
  21. echo----------: command not found
  22.    PPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
  23. 261085  261678  261678  261085 pts/0     261678 S+    1001   0:00 ./mycommand
  24. 261678  261679  261678  261085 pts/0     261678 S+    1001   0:00 ./mycommand
  25. echo----------: command not found
  26.    PPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
  27. 261085  261678  261678  261085 pts/0     261678 S+    1001   0:00 ./mycommand
  28. echo----------: command not found
  29.    PPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
  30. 261085  261678  261678  261085 pts/0     261678 S+    1001   0:00 ./mycommand
  31. echo----------: command not found
  32.    PPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
  33. 261085  261678  261678  261085 pts/0     261678 S+    1001   0:00 ./mycommand
  34. echo----------: command not found
  35.    PPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
  36. 261085  261678  261678  261085 pts/0     261678 S+    1001   0:00 ./mycommand
  37. echo----------: command not found
  38.    PPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
  39. echo----------: command not found
  40.    PPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND
  41. echo----------: command not found
  42. ^C
复制代码
  程序替换有没有创建新的子历程?
  没有,子历程的pid没有变。不创建新历程,只进行历程的程序代码和数据的替换结构。
    增补知识:
  

  • 为什么after后面的代码没有执行呢?
    由于程序替换后,after的printf的代码已经是老程序的代码了,被ls覆盖掉了。
  • 程序替换成功后,exit之后的代码不会执行,那么如果替换失败呢?(比方路径写错,下令不存在)
    那么程序就会继承今后走。以是exit*函数只有失败有返回值,没有成功的返回值。
  • 我们的CPU怎么知道新程序应该进入的入口地址呢?
    Linux中形成的可执行程序是有格式的,EIF,是有可执行程序的表头的,可执行程序的入口就在表中。
  • 实际上不但是可以调用ls下令,还可以调用自己写的C程序,python程序,shell脚本,Java程序。但是无论是我们的可执行程序照旧脚本,为什么能够跨语言调用呢?
    由于全部的语言运行起来,本质上都是历程。
  • 环境变量是什么时间给历程的呢?
    环境变量实际上也是数据。当我们创建子历程的时间,环境变量就已经被子历程继承下去了。以是程序替换中,环境变量信息不会被子历程替换。
  • 如果我想给子历程传递环境变量,应该怎么传递呢?

    • 新增环境变量
      直接给bash添加环境变量
      在父历程中使用putenv()添加环境变量
    • 彻底替换环境变量
      使用execle的时间把环境变量参数换成自己的

  
3.5 自定义shell

touch.sh
  1. #!/usr/bin/bash
  2. echo "hello 1"
  3. echo "hello 1"
  4. echo "hello 1"
  5. echo "hello 1"
  6. echo "hello 1"
  7. ls -a -l
复制代码
运行:bash touch.sh
  1. ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson17$ bash touch.sh
  2. hello 1
  3. hello 1
  4. hello 1
  5. hello 1
  6. hello 1
  7. total 40
  8. drwxrwxr-x  2 ydk_108 ydk_108  4096 Jan 21 00:00 .
  9. drwxrwxr-x 13 ydk_108 ydk_108  4096 Jan 20 22:14 ..
  10. -rw-rw-r--  1 ydk_108 ydk_108    82 Jan 20 22:16 makefile
  11. -rwxrwxr-x  1 ydk_108 ydk_108 17008 Jan 20 22:54 mycommand
  12. -rw-rw-r--  1 ydk_108 ydk_108   628 Jan 20 22:54 mycommand.c
  13. -rw-rw-r--  1 ydk_108 ydk_108   101 Jan 21 00:00 touch.sh
  14. ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson17$
复制代码

下令行交互的shell代码:
  1. // 包含必要的头文件
  2. #include <stdio.h>      // 标准输入输出
  3. #include <stdlib.h>     // 标准库函数
  4. #include <assert.h>     // 断言
  5. #include <string.h>     // 字符串操作
  6. #include <unistd.h>     // UNIX标准函数
  7. #include <sys/types.h>  // 系统数据类型
  8. #include <sys/wait.h>   // 进程等待
  9. // 定义shell提示符的格式
  10. #define LEFT "["        // 左边界符号
  11. #define RIGHT "]"       // 右边界符号
  12. #define LABLE "#"      // 提示符标签
  13. #define DELIM " \t"    // 命令分隔符(空格和制表符)
  14. #define LINE_SIZE 1024  // 命令行最大长度
  15. #define ARGC_SIZE 32   // 命令参数最大个数
  16. #define EXIT_CODE 44   // 子进程退出码
  17. // 全局变量定义
  18. int lastcode = 0;      // 上一条命令的返回值
  19. int quit = 0;          // 退出标志
  20. extern char **environ; // 环境变量数组
  21. char commandline[LINE_SIZE];  // 存储命令行
  22. char *argv[ARGC_SIZE];        // 存储解析后的命令参数
  23. char pwd[LINE_SIZE];          // 存储当前工作目录
  24. // 自定义环境变量存储空间
  25. char myenv[LINE_SIZE];
  26. // 获取当前用户名
  27. const char *getusername(){
  28.     return getenv("USER");
  29. }
  30. // 获取主机名
  31. const char *gethostname(){
  32.     return getenv("HOSTNAME");
  33. }
  34. // 获取当前工作目录
  35. void getpwd(){
  36.     getcwd(pwd, sizeof(pwd));
  37. }
  38. // 实现shell交互界面
  39. void Interact(char *cline, int size){
  40.     getpwd();
  41.     // 打印提示符,格式为 [用户名@主机名 当前目录]#
  42.     printf(LEFT"%s@%s %s"RIGHT""LABLE" ", getusername(), gethostname(), pwd);
  43.     // 读取用户输入
  44.     char *s = fgets(cline, size, stdin);
  45.     assert(s);
  46.     (void)s;
  47.    
  48.     // 去掉输入字符串末尾的换行符
  49.     cline[strlen(cline)-1] = '\0';
  50. }
  51. // 解析命令行字符串为参数数组
  52. int splistring(char cline[], char *_argv[]){
  53.     int i = 0;
  54.     // 获取第一个参数(命令名)
  55.     argv[i++] = strtok(cline, DELIM);
  56.     // 获取后续参数
  57.     while(_argv[i++] = strtok(NULL, DELIM));
  58.     return i-1;  // 返回参数个数
  59. }
  60. // 执行外部命令
  61. void NormalExcute(char *_argv[]){
  62.     pid_t id = fork();
  63.     if(id < 0){
  64.         perror("fork");
  65.         return;
  66.     }
  67.     else if(id == 0){  // 子进程
  68.         // 使用execvp执行命令
  69.         execvp(_argv[0], _argv);
  70.         exit(EXIT_CODE);
  71.     }
  72.     else{  // 父进程
  73.         int status = 0;
  74.         // 等待子进程结束
  75.         pid_t rid = waitpid(id, &status, 0);
  76.         if(rid == id){
  77.             // 保存命令执行结果
  78.             lastcode = WEXITSTATUS(status);
  79.         }
  80.     }
  81. }
  82. // 处理内建命令
  83. int buildCommand(char *_argv[], int _argc){
  84.     // cd命令
  85.     if(_argc == 2 && strcmp(_argv[0],"cd")==0){
  86.         chdir(argv[1]);  // 改变当前目录
  87.         getpwd();        // 更新pwd
  88.         sprintf(getenv("PWD"),"%s", pwd);  // 更新PWD环境变量
  89.         return 1;
  90.     }
  91.     // export命令:设置环境变量
  92.     else if(_argc == 2 && strcmp(_argv[0],"export")==0){
  93.         strcpy(myenv, _argv[1]);
  94.         putenv(myenv);
  95.         return 1;
  96.     }
  97.     // echo命令:显示文本或变量值
  98.     else if(_argc == 2 && strcmp(_argv[0],"echo")==0){
  99.         if(strcmp(_argv[1],"$?")==0){  // 显示上一条命令的返回值
  100.             printf("%d\n",lastcode);
  101.             lastcode=0;
  102.         }
  103.         else if(*_argv[1]=='$'){  // 显示环境变量的值
  104.             char *val = getenv(_argv[1]+1);
  105.             if(val) printf("%s\n",val);
  106.         }
  107.         else{  // 显示普通文本
  108.             printf("%s\n",_argv[1]);
  109.         }
  110.         return 1;
  111.     }
  112.    
  113.     // 为ls命令添加颜色支持
  114.     if(strcmp(_argv[0],"ls")==0){
  115.         _argv[_argc++] = "--color";
  116.         _argv[_argc] = NULL;
  117.     }
  118.    
  119.     return 0;  // 不是内建命令
  120. }
  121. int main(){
  122.     while(!quit){
  123.         //1.
  124.         //2.交互问题,获取命令行
  125.         Interact(commandline, sizeof(commandline));
  126.         //3.子串分割的问题,解析命令行
  127.         int argc = splistring(commandline, argv);
  128.         if(argc == 0) continue;
  129.         
  130.         //4.指令的判断,判断是不是内建命令
  131.         //debug
  132.         //for(int i=0;argv[i];i++){
  133.         //    printf("[%d]: %s\n",i,argv[i]);
  134.         //}
  135.         //内键命令本质上就是shell内部的一个函数。
  136.         int n = buildCommand(argv, argc);
  137.         
  138.         
  139.         //5.普通命令的执行
  140.         // 如果不是内建命令,则作为外部命令执行
  141.         if(!n) NormalExcute(argv);
  142.     }
  143.     return 0;
  144. }
复制代码
打印效果:
  1. ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson17$ ./myshell
  2. [ydk_108@(null) /home/ydk_108/108/lesson17]# ls -a
  3. .  ..  makefile  mycommand  mycommand.c  myshell  myshell.c  touch.sh
  4. [ydk_108@(null) /home/ydk_108/108/lesson17]# pwd
  5. /home/ydk_108/108/lesson17
  6. [ydk_108@(null) /home/ydk_108/108/lesson17]#
  7. Segmentation fault
  8. ydk_108@iZuf68hz06p6s2809gl3i1Z:~/108/lesson17$
复制代码
以是,当我们进行登陆的时间,体系就是要启动一个shell历程。
我们shell本身的环境变量是从哪里来的?
当用户登录的时间,shell会读取目次用户下的.bash_profile文件,内里生存了导入环境变量的方式。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

火影

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表