Linux下“进程控制”相关内容整理分析

1.进程创建

1.1fork

1.1.1fork函数

linux下创建进程的方式:

  1. ./可执行程序,
  2. 调用fork,创建成功子进程给父进程返回子进程id,给子进程返回0,当然还有创建失败的情况

进程调用fork,当控制转移到内核中的fork代码后,内核做:
分配新的内存块和内核数据结构给子进程
将父进程部分数据结构内容拷贝至子进程
添加子进程到系统进程列表当中
fork返回,开始调度器调度

1.1.2fork写实拷贝

在这里插入图片描述

1.1.3fork常规用法

一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子进程来处理请求。
一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数

1.1.4fork调用失败的原因

系统中有太多的进程
实际用户的进程数超过了限制

创建进程是有成本的

1.2进程终止

程序退出的三种情况

  1. 代码跑完了,结果是对的
  2. 代码跑完了,结果是不对的
  3. 代码出现异常,程序崩溃,退出码也变得没有意义了
  1. 进程卡住了,没有响应了,不算退出。属于操作系统操作不过来
  2. 什么main函数要return 0;意义是什么?
    2.1这个叫做进程退出码,这个是用来衡量我们进程退出的情况
    2.2可以通过这个进程return 返回的值,来判定这个进程执行的结果
    2.3echo $? 用来查看进程的退出码
    2.4用0表示成功

在这里插入图片描述
在这里插入图片描述
echo $? 输出最近一次进程退出时的退出码
此时我们接下来继续执行这个就会发现不是123?为什么?
在这里插入图片描述
此时输出的时echo这个命令的退出码。
在这里插入图片描述
从上图中可以得出,当ls使用错误的时候,退出码是2
正确使用时,是0

总结:
我们用退出码来表示这个程序是否正常运行并结束
0:success
!0:failed
当代码不正确的运行结束后,我们想知道为什么不正确,有多种可能
就可以用不同的数字来表示错误
在这里插入图片描述
在这里插入图片描述
下面有很多就不截完了,一共0-133个

  1. main函数return,代表进程退出,非main函数呢?函数返回
  2. exit在任意位置调用都代表进程退出
    在这里插入图片描述
    在这里插入图片描述
    exit(EXIT_SUCCESS)和return都可以达到终止程序的目的
    exit和return本身就会要求系统进行缓冲区刷新
    _exit终止程序,强制终止进程,不要进行进程的后续收尾工作,比如:刷新缓冲区
    缓冲区:用户级缓冲区
    在这里插入图片描述

问题:进程退出,OS层面做了什么?
进程退出,系统层面少了一个进程,系统就要free PCB,mm_struct,页表和各种映射关系,代码+数据曾经申请的

2. 进程等待

2.1相关概念

进程等待是什么?
在这里插入图片描述
进程等待:让父进程fork之后,需要通过wait/waitpid等待子进程退出

为什么要父进程等待呢?

  1. 通过获取子进程退出的信息,能够得知子进程执行结果
  2. 可以保证:时序问题,子进程先退出,父进程后退出
  3. 进程退出的时候,进程会先进入僵尸状态,会造成内存泄露的问题,需要通过父进程wait,释放该子进程占用的资源!
  4. 当进程一旦变成僵尸状态,那就刀枪不入,“杀人不眨眼”的kill -9 也无能为力,因为谁也没有办法
    杀死一个已经死去的进程。
    当一个进程进入僵尸,那么这个进程就已经死掉了。

2.2进程等待方法

代码一:wait小测试

#include<stdio.h>                                                                                                                                                                                                
#include<sys/types.h>               
#include<sys/wait.h>                
#include<stdlib.h>                  
#include<unistd.h>                  
#include<string.h>                  
                                                                   
int main()                         
{                                  
  pid_t id = fork();               
                                   
  if(id == 0)                      
  {                               
    int cnt = 5;                   
    while(cnt)                     
    {                              
      printf("child[%d] is running :cnt is : %d\n",id,cnt);    
      cnt--;                                                                                                                                                                              
      sleep(1);                                                                                                                                                                           
    }                                                                                                                                                                                     
    exit(0);                                                                                                                                                                              
  }                                                                                                                                                                                       
                                                                                                                                                                                          
  pid_t ret = wait(NULL);                                                                                                                                                                 
  if(ret>0)                                                                                                                                                                               
    printf("father wait:%d, success\n",ret);                                                                                                                                              
  else                                                                                                                                                                                    
  {                                                                                                                                                                                       
    printf("father wait failed!\n");                                                                                                                                                      
  }                                                                                                                                                                                       
  return 0;                                                                                                                                                                               
}   

监视:
监视指令:
while :; do ps ajx | head -1 && ps ajx | grep a.out | grep -v grep; sleep 1;echo"#############################################################"; done
在这里插入图片描述


代码二:wait执行过程中进程状态深入体现

#include<stdio.h>                                                                                                                                                                                                #include<sys/types.h>                                                                                                                                                                
#include<sys/wait.h>                                                                                                                                                                 
#include<stdlib.h>                                                                                                                                                                   
#include<unistd.h>                                                                                                                                                                   
#include<string.h>                                                                                                                                                                   
                                                                                                                                                                                     
int main()                                                                                                                                                                           
{                                                                                                                                                                                    
  pid_t id = fork();                                                                                                                                                                 
                                                                                                                                                                                     
  if(id == 0)                                                                                                                                                                        
  {                                                                                                                                                                                  
      int cnt = 5;                                                                                                                                                                   
      while(cnt)                                                                                                                                                                     
      {                                                                                                                                                                              
            printf("child[%d] is running :cnt is : %d\n",id,cnt);                                                                                                                    
            cnt--;                                                                                                                                                                   
            sleep(1);                                                                                                                                                                               
          }                                                                                                                                                                                         
      exit(0);                                                                                                                                                                                      
    }                                                                                                                                                                                           
  sleep(10);                                                                                                                                                                                    
  printf("father wait begin!\n");                                                                                                                                                    
  pid_t ret = wait(NULL);                                                                                                                                                            
  if(ret>0)                                                                                                                                                                                   
    printf("father wait:%d, success\n",ret);                                                                                                                                                  
  else                                                                                                                                                                                          
      printf("father wait failed!\n");                                                                                                                                                            sleep(10);                                                                                                                                                                                                       return 0;                                                                                                                                                    
}  

代码和下图分析:
子进程执行五秒,
父进程先等待10秒,前五秒子进程正常运行,后五秒子进程变成了僵尸状态,
father开始等待
僵尸状态此时没了
父进程在等待10秒 (此时的进程两个变一个,此时只剩下父进程一个)
在这里插入图片描述
即:wait是可以回收僵尸进程的


代码三:waitpid
代码和代码二类似

#include<stdio.h>    
#include<stdlib.h>    
#include<string.h>    
#include<unistd.h>    
#include<sys/types.h>    
#include<sys/wait.h>    
    
int main()    
{    
  pid_t id = fork();    
  if(id == 0)    
  {    
    int cnt = 5;    
    while(cnt)    
    {    
      printf("child[%d] is running :cnt is :%d\n",getpid(),cnt);                                      
      cnt--;    
      sleep(1);    
    }    
    exit(0);    
  }    
  sleep(10);    
  printf("father wait begin!\n");    
  pid_t ret = waitpid(id,NULL,0);    
  if(ret>0)    
    printf("father wait:%d,success\n",ret);    
  else    
    printf("father wait failed!\n");    
  sleep(10);    
  return 0;    
}    

返回值:
如果成功,就会返回你等待的那个子进程,那个子进程退出了,我们返回的就是那个子进程的id
如果失败了,就是-1
在这里插入图片描述
在这里插入图片描述
子进程是11314等待的也是,,所以没毛病

2.3进程等待中"waitpid"处status参数

pid_t ret = waitpid(id,NULL,0)//等待指定一个进程
pid_t ret = waitpid(-1,NULL,0)//等待任意一个子进程,等价于wait
pid_ t waitpid(pid_t pid, int *status, int options);//第二个参数是输出性参数

将我们的测试代码中一部分不需要的删除

#include<stdio.h>    
#include<stdlib.h>    
#include<string.h>    
#include<unistd.h>    
#include<sys/types.h>    
#include<sys/wait.h>    
    
int main()    
{    
  pid_t id = fork();    
  if(id == 0)    
  {    
    int cnt = 3;    
    while(cnt)    
    {    
      printf("child[%d] is running :cnt is :%d\n",getpid(),cnt);    
      cnt--;    
      sleep(1);    
    }    
    exit(0); //第一次这里是0,第二次随便传入一个值  
  }    
  printf("father wait begin!\n");    
  int status = 0;    //我们探究的就是这个值
  pid_t ret = waitpid(id,&status,0);    
  if(ret>0)    
    printf("father wait:%d,success,status:%d\n",ret,status);    
  else    
    printf("father wait failed!\n");    
  return 0;                                                                                                                                                                                                      
}  

在这里插入图片描述
我们可以推出:
父进程拿到什么status结果,一定和子进程如何退出强相关!!!
子进程退出无非三种结果, 则我们可以通过status反馈出子进程退出的情况

status构成:
在这里插入图片描述
获取退出码和终止信号:

#include<stdio.h>    
#include<stdlib.h>    
#include<string.h>    
#include<unistd.h>    
#include<sys/types.h>    
#include<sys/wait.h>    
    
int main()    
{    
  pid_t id = fork();    
  if(id == 0)    
  {    
    int cnt = 3;    
    while(cnt)    
    {    
      printf("child[%d] is running :cnt is :%d\n",getpid(),cnt);    
      cnt--;    
      sleep(1);    
    }    
    exit(11);    
  }    
  printf("father wait begin!\n");    
  int status = 0;    
  pid_t ret = waitpid(id,&status,0);    
  if(ret>0)    
    printf("father wait:%d,success,status exit code :%d,status exit signal:%d\n",ret,(status>>8)&0xFF,status&0x7F);    
  else                                                                                                                                                                                                           
    printf("father wait failed!\n");                                                                                                                           
  return 0;                                                                                                                                                    
}                                                                                                                                                              
~ 

在这里插入图片描述
上图表示:我们的代码是正常退出的
11是我们设置的,0是代表没有接受到异常终止信号

关于异常终止信号我们可以:
kill -2 子进程
这行指令的意思是,我们让子进程直接退出,退出的原因是2
那么我们执行上面的代码时(在子进程进行的时候我们kill)
最后显示的异常终止信号就是2
当我们写的代码出现问题时:1/0
这个明显是错误的
那么我们最终显示的就是浮点数错误:8号信号SIGFPE

2.4重新解读单个进程的pid和ppid

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
可以得出:
bash是命令行启动的所有进程的父进程!
bash一定是通过wait方式得到子进程的退出结果(退出码)
所以我们能看到echo $?能够查到子进程的退出码

2.5进程等待中waitpid的第三个参数options

阻塞本质:进程的PCB被放入了等待队列,并将进程的状态改为S状态
返回的本质:进程的PCB等待队列拿到运行队列,从而被CPU调度

看到某些应用或者OS本身,卡住了长时间不动,应用或者程序hang住了
WNOHANG:非阻塞

  1. 子进程根本就没有退出
  2. 子进程退出,waitpid(调用成功or失败)

基于非阻塞的轮询方案

#include<stdio.h>    
#include<unistd.h>    
#include<sys/types.h>    
#include<sys/wait.h>    
#include<stdlib.h>    
    
    
int main()    
{    
  pid_t id = fork();    
  if(id == 0)    
  {    
    int cnt = 3;                                                                                                                                                                                                 
    while(cnt--)    
    {    
      printf("child[%d] is running ;cnt is:%d\n",getpid(),cnt);    
      sleep(1);    
    }    
    exit(1);    
  }    
    
  int status = 0;    
  while(1){    
    pid_t ret = waitpid(id,&status,WNOHANG);    
    if(ret == 0)    
    {    
      //子进程没有退出,但是waitpid等待是成功的,需要父进程重复进行等待    
      printf("do father thing!\n");    
    }    
    else if(ret>0)    
    {    
      //子进程推出了,waitpid也成功了,获取到了对应的结果    
      printf("father wait:%d,success,status exit code:%d ,status exit signal:%d\n",ret,(status>>8)&0xFF,status&0x7F);    
    }    
    else{    
      //等待失败,    
      perror("waitpid");    
      break;    
    }    

在这里插入图片描述

3. 进程程序替换, 微型shell,重新认识shell运行原理

3.1简单了解程序替换

引入:
目前我们创建子进程的目的是什么:
if else 让子进程执行父进程代码的一部分!
如果我让子进程执行一个全新的程序呢?

进程不变,仅仅替换当前进程的代码和数据的技术,叫做进程的程序替换
程序本身就是一个文件!!!
文件=程序代码+程序数据

问题:程序在进行程序替换的时候有没有创建新的进程?
没有,只是把我们老程序的壳子不变,新程序的文件替换进来

#include <unistd.h>`
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 execve(const char *path, char *const argv[], char *const envp[]);
#include<stdio.h>    
#include<unistd.h>    
#include<sys/types.h>    
#include<sys/wait.h>    
#include<stdlib.h>    
    
int main()    
{    
  printf("i am a process! pid: %d\n",getpid());    
    
  execl("/usr/bin/ls","ls","-a","-l",NULL);    
  //ececl执行程序替换,                                                                                                                                                                                          
    
  printf("Hello sakeww!!!!!\n");    
  printf("Hello sakeww!!!!!\n");    
  printf("Hello sakeww!!!!!\n");    
  printf("Hello sakeww!!!!!\n");    
  printf("Hello sakeww!!!!!\n");    
  printf("Hello sakeww!!!!!\n");    
  printf("Hello sakeww!!!!!\n");    
    
  return 0;    
}   

在这里插入图片描述
问题:
程序替换的本质是不是就是吧特定的进程代码+数据,加载到特定进程的上下文中?
我们知道C/C++程序要运行,必须要先加载到内存中
如何加载?(就是讲程序和数据拷贝到内存中)加载器!
加载器-> exec*程序替换函数!!!

#include<stdio.h>    
#include<unistd.h>    
#include<sys/types.h>    
#include<sys/wait.h>    
#include<stdlib.h>    
    
int main()    
{    
  pid_t id = fork();    
  if(id == 0)    
  {    
    printf("i am a process! pid: %d\n",getpid());    
    sleep(3);    
    execl("/usr/bin/ls","ls","-a","-l",NULL);    
    //ececl执行程序替换,    
    
    printf("Hello sakeww!!!!!\n");    
    printf("Hello sakeww!!!!!\n");    
    printf("Hello sakeww!!!!!\n");    
    printf("Hello sakeww!!!!!\n");    
    printf("Hello sakeww!!!!!\n");    
    printf("Hello sakeww!!!!!\n");    
    printf("Hello sakeww!!!!!\n");    
  }    
    
  while(1)    
  {    
    printf("i am a father\n");    
    sleep(1);                                                                                                                                                                                                    
  }    
    
  waitpid(id,NULL,0);    
  printf("wait success!\n");       
  return 0;    
} 

在这里插入图片描述
问题:
为什么父进程没有受影响呢?
进程具有独立性!
但是我们又知道:父子代码是共享的吗?
进程程序替换会更改代码区的代码,也要发生写时拷贝!

问题:
只要进程的程序替换成功,就不会执行后续代码,意味着exec函数,成功的时候,不需要返回值检测?
换一种问法:
只要exec
返回了,就一定时因为调用失败了?
在这里插入图片描述

程序替换失败时:
在这里插入图片描述

在这里插入图片描述

总计:
1.为什么要进行程序替换?
想让子进程去执行一个全新的程序
2.什么是程序替换?原理:
用新的代码和数据替换 原始的代码和数据
3.进程的程序替换使用–怎么办?? --阶段一
a.现象
b.fork()
c.exec* 返回值

3.2基本程序替换函数的使用

#include <unistd.h>`
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 execve(const char *path, char *const argv[], char *const envp[]);

int execl(const char *path, const char *arg, …);
path->你要执行的目标程序的全路径,所在路径/文件名。
… -> 可变参数链表
从第二个参数开始,要执行的目标程序,在命令行怎么执行,这里参数就怎么一个一个的传递
必须以NULL作为参数的结束

即:ececl("/usr/bin/ls","ls",-a","-l",NULL);

在vim中查询“execl”相关信息
在这里插入图片描述

3.4样例测试

ececl("/usr/bin/ls","ls",-a","-l",NULL)

在这里插入图片描述
在这里插入图片描述

内容解释:

./myexec

变成一个进程,在内部就会执行被替换的代码

printf("执行结束!\n);

没有被执行,是因为:
我们进行了程序替换,就叫做替换所有的代码
只要程序执行exec返回了必定出错了!


帮助理解:

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 execve(const char *path, char *const argv[], char *const envp[]);
int execvpe(const char *file, char *const argv[],char *const envp[]);

这些函数原型看起来很容易混,但只要掌握了规律就很好记。
l(list) : 表示参数采用列表
列表:一个一个的传参,
v(vector) : 参数用数组
char *const argv[]–>指针数组:放你在命令行传的一个一个的参数
p(path) : 有p自动搜索环境变量PATH
e(env) : 表示自己维护环境变量
在这里插入图片描述


int execv(const char *path, char *const argv[]);

在这里插入图片描述
在这里插入图片描述

int execv(const char *path, char *const argv[]);
int execl(const char *path, const char *arg, ...);

这两个无任何效果上的区别,
第二个是可变参数列表
第一个是数组,里面的元素可以由自己定
argv类似我们的main函数的参数列表


int execlp(const char *file, const char *arg, ...);

因为l:所以传参都是列表形式
const char *file,只要告诉这个程序的名字即可,不用告诉这个程序在哪里
可以在环境变量PATH帮你搜索这个程序

在这里插入图片描述
在这里插入图片描述


int execle(const char *path, const char *arg, ...,char *const envp[]);

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


在c中执行py程序
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.5接口总结

所有的接口,看起来是没有太大区别的,只有一个就是参数的不同!

为什么会由这么多接口?
是为了满足不同的应用场景

区别图解:
在这里插入图片描述

3.6模拟实现一个简单shell

#include<stdio.h>                                                                                    
  #include<unistd.h>    
  #include<stdlib.h>    
  #include<sys/wait.h>    
  #include<string.h>    
      
  #define NUM 128    
  #define CMD_NUM 64    
      
  int main()    
  {    
    char command[NUM];    
    for(;;){    
      char *argv[CMD_NUM] = {NULL};    
      //1.打印提示符    
      command[0] = 0;//用这种方式,可以做到O(1)时间复杂度,清空字符串    
      printf("[who@myhostname mydir]# ");    
      fflush(stdout);    
          
      //2.获取命令字符串    
      fgets(command,NUM,stdin);    
      command[strlen(command)-1] = '\0';//我们的输入中,最后会以回车结尾,导致换行两次    
        
      //3.解析命令字符串,解析成char *argc[];    
      //strtok();    
      const char *sep = " ";    
      argv[0] = strtok(command,sep);    
      int i = 1;    
      while(argv[i] = strtok(NULL,sep))    
      {    
        i++;    
      }    
      
      //4.检测命令是否是需要shell本身执行的,内建命令    
      if(strcmp(argv[0],"cd") == 0)    
      {    
        if(argv[1] != NULL)    
          chdir(argv[1]);    
        continue;
      }
  
      //5.执行第三方命令
      if(fork() == 0)
      {
        execvp(argv[0],argv);
        exit(1);//上面一行执行成功就不会执行本行代码
      }
      
      waitpid(-1,NULL,0);
    }
  }   

  • 19
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 13
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值