【Linux】进程控制——终

目录

前言

一、进程替换细节

二、进程替换相关函数

三、简单的shell程序


前言

前面我们说明了进程等待及其必要性,一般导致进程阻塞的都是系统接口所引起的,进程阻塞本质是进程阻塞在系统函数的内部,不被调度,那么在用户看来就是卡住了。

同时我们又讲了程序替换,它的本质是,没有创建新的进程,只是将替换的程序加载到内存,然后改变被替换进程的页表的映射关系,同时需要我们使用wait/waitpid来等待子进程,也就是说,子进程先执行完,父进程等待子进程,然后父进程执行它的后续代码然后退出。


一、进程替换细节

我们先思考一个问题,为什么要创建子进程?

如果创建子进程,那么我们替换掉的进程就是子进程而不是父进程,从而不会影响父进程,使父进程能够聚焦在读取数据,解析数据,指派进程执行代码等功能。

我们前面说过,创建子进程,父子进程的代码共享,数据写时拷贝,可如果子进程被替换了呢?

父子进程的代码是否还会共享?答案是十分显然的,父子进程的代码不会共享了,子进程代码写时拷贝,从而父子进程在代码和数据层面全部分离。

二、进程替换相关函数

Linux下exec系列函数能够进行进程替换

#include <unistd.h>
extern char **environ;
 
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg,..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],char *const envp[]);

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

l(list) : 表示参数采用列表

v(vector) : 参数用数组

p(path) : 有p自动搜索环境变量PATH e(env) : 表示自己维护环境变量

这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回

如果调用出错则返回-1

所以exec函数只有出错的返回值而没有成功的返回值

我们还知道,进程替换成功之后,exec系列函数将整个进程的代码全部替换,所以就不能够接收到exec系列函数的返回值了,如果还能够执行exec后面的函数就说明进程替换失败,我们直接exit就可以了

调用举例:

  #include <stdio.h>    
  #include <stdlib.h>    
  #include <unistd.h>    
  #include <sys/wait.h>    
      
  #define NUM 16    
      
  const char* const path = "./mycmd";    
      
  int main()    
  {    
W>    char* const _argv[] = {    
          (char*)"ls",    
          (char*)"-l",    
          (char*)"-s",    
          (char*)"-i",    
          NULL    
          };    
      
      pid_t id = fork();    
      if(id < 0)    
      {    
          perror("fork");    
          exit(1);    
      }    
      else if(id == 0)    
      {    
          printf("我是子进程\n");    
          printf("程序开始运行\n");    
          printf("进程开始替换\n");    
          execl("./test.sh", "./test.sh", NULL);    
          //execlp("bash", "bash", "./test.sh", NULL);    
          //execl(path, "./mycmd", "-a", NULL);    
          //etcxeclp("ls", "ls", "-a", "-l", "-s", NULL);                                                                                                                                  
          // execvp("ls", _argv);    
          //execlp("ls", "-a", "-l", "-s", NULL);    
          //execl("/usr/bin/ls", "ls", "-a", "-l", "-s", NULL);    
          //execv("/usr/bin/ls", _argv);    
          exit(1);    
          exit(1);
  
          printf("程序运行结束\n");
      }
      else 
      {
          printf("我是父进程\n");
          int status = 0;                                                                                                                                                                  
          pid_t ret = waitpid(-1, &status, 0);
          if(ret > 0)
          {
              printf("父进程等待子进程成功,子进程退出码:%d\n", WEXITSTATUS(status));
          }
      }
  
      return 0;
  }

我们要执行一个程序的步骤首先是去寻找程序,接下来是执行它

所以exec系列函数第一个参数都是先找到程序,所以要不是填路径,要不就直接填入程序名称

,接下来的参数,总结一句话就是,命令行上是怎么写的,接下来就填入什么

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

我们可以替换的程序不仅仅局限于C/C++编写的程序,像Python,JAVA,PHP等等都是可以调用的,前面我们举了一个例子

execlp("bash", "bash", "./test.sh", NULL);  

这就调用了一个bash程序test.sh

#! /usr/bin/bash                                                                                                                                                                           
    
    
echo "hello shell!"    
echo "hello shell!"    
echo "hello shell!"    
echo "hello shell!"    
echo "hello shell!"    
echo "hello shell!"    
echo "hello shell!"   

其实exec系列一共有7个,虽然我们在man中只看到了6个

 

最后一个是系统调用

前面6个是将最后一个系统调用进行封装,来给我们提供的不同调用方式,他们在本质上是完全没有区别


这里还有一个问题exec系列函数与想要替换程序的main函数的关系?

exec系列函数将环境变量和命令等通过命令行参数和环境变量传给替换的进程argc argv env等等

exec函数本质就是加载器,将你的程序加载到内存中 


三、简单的shell程序

经过前面的铺垫,我们发现我们可以写一个简单的shell练一练手

shell运行原理:通过让子进程执行命令,父进程等待解析命令

shell应该是只要运行起来就不会停止,所以他应该是一个死循环

其次每次都会有一个提示符[ww@VM-8-14-centos shell]$

所以二话不说应该先打印提示信息

同时提示信息不能够换行,因为我们每次输入命令时都没有换行,前面我们又说明了行缓冲区的存在,所以我们要手动刷新行缓冲区

其次是读取我们输入的指令,我们可以使用fgets函数

接下来就是将读取的一整个字符串分离,可以借助stctok函数解决

接下俩就是创建子进程,让子进程执行我们所输入的指令

这就是最基本的思路

我们可以进一步优化,例如针对ls命令,可以让他带上颜色,只要识别到ls命令就自动给它插入--color=auto就可以自动显示颜色了

我们还可以针对于cd命令进行特殊处理,我们的cd命令是将父进程的工作目录切换,而不是对子进程,所以就算我们让子进程干这件事,也不会切换父进程的工作目录的

这种命令称之为,内置命令

内置命令:让父进程自己执行的命令

cd我们可以借助chdir来实现

 

  #include <stdio.h>
  #include <stdlib.h>
  #include <string.h>
  #include <unistd.h>
  #include <sys/wait.h>
      
  #define NUM 1024    
  #define SIZE 32    
  #define SEP " "    
      
  char cmd_line[NUM];    
  char* g_argv[SIZE];    
      
  int main()    
  {    
      //shell程序是永不退出的    
      while(1)    
      {    
          //打印提示信息 [ww@VM-8-14-centos shell]$    
          printf("[root@localhost myshell]# ");    
          fflush(stdout);    
          memset(cmd_line, '\0', sizeof cmd_line);    
          if(fgets(cmd_line, sizeof cmd_line, stdin) == NULL)    
          {    
              continue;    
          }    
          cmd_line[strlen(cmd_line)-1] = '\0';    
          //分解指令    
          g_argv[0] = strtok(cmd_line, SEP);    
          int index = 1;    
          if(strcmp(g_argv[0], "ls") == 0)    
          {    
              g_argv[index++] = "--color=auto";    
          }                                                                                                                                                                                
      
          if(strcmp(g_argv[0], "ll") == 0)
          {    
              g_argv[0] = "ls";    
              g_argv[index++] = "-l";
              g_argv[index++] = "--color=auto";
          }
  
          while(g_argv[index++] = strtok(NULL, SEP));
  
          if(strcmp(g_argv[0], "cd") == 0)
          {
              if(g_argv[1] != NULL)
              {
                  chdir(g_argv[1]);
              }
              continue;
          }
  
          //创建子进程,执行指令
          pid_t id = fork();
          if(id < 0)
          {
              perror("fork");
              exit(1);
          }
          else if(id == 0)
          {
              printf("下面功能由子进程进行\n");
              execvp(g_argv[0], g_argv);
              exit(1);
          }
          else 
          {
              int status = 0;
              pid_t ret = waitpid(-1, &status, 0);
              if(ret > 0)
              {
                  printf("成功等待子进程,子进程退出码为: %d\n", WEXITSTATUS(status));                                                                                                     
              }
          }
     }
  
      return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值