0
点赞
收藏
分享

微信扫一扫

Linux中对进程的理解

水墨_青花 2022-03-13 阅读 98
linux

文章目录

1 再次谈谈进程的概念

我们都知道,进程只不过是一个被加载到内存的一个可执行程序罢了,但是仅仅这么理解是不够的,进程,绝对不是就是一个被加载到内存的可执行程序;
再次期间,我们需要理解,程序是谁把它加载到内存的?答案是操作系统;
么程序被操作系统加载到内存时候,是否需要对进程进行管理呢
?答案也是肯定的,当然需要管理。
那么进程一旦被操作系统管理,那么操作系统又是如何管理进程的呢?答案是:通过描述进程的各种属性,然后对该属性进行管理,从而达到管理该进程的目的;


通过代码来看看进程的样子:
在这里插入图片描述


2 进程的PCB(task_struck)中到底有什么东西,如何理解这些属性

task_ struct内容分类

  1. 标示符: 描述本进程的唯一标示符,用来区别其他进程。
  2. 状态: 任务状态,退出代码,退出信号等。
  3. 优先级: 相对于其他进程的优先级。
  4. 程序计数器: 程序中即将被执行的下一条指令的地址。
  5. 内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
  6. 上下文数据: 进程执行时处理器的寄存器中的数据。
  7. I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
  8. 记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
  9. 其他信息

  • 首先进程的属性,标识符是什么意思,每个进程都有一个唯一标识符,很明显标识符类似一种身份的东西,它就代表进程的身份;我们可以通过代码来获得这个进程的表示符;
#include<stdio.h>
#include<unistd.h>
int main()
{
  while(1)
  {
    
    printf("本进程的标识符getpid = %d\n本进程的父进程的getppid = %d\n",getpid(),getppid());
    sleep(1);
  }

  return 0;
}

在这里插入图片描述


查看进程的方式:通过命令:ps -ajx | grep myproc,其中myproc为进程的名字;
通过man 2 getpid查看 getpid的相关属性和getppid的相关属性
在这里插入图片描述


  • 如何理解tast_struct的属性:任务状态,退出代码,退出信号等。
    这里说一说这个退出代码:比如我们写的main函数,进程返回0时候, 这个0就是退出代码,可以通过echo $?查看; echo $? 会输出离该命令最近运行程序的退出状态码,当然你的命令也是属于程序,当执行命令后,也可以退出状态码;
    再说这个,任务状态又是什么,呃呃,其实一两句话很难说清楚,就是一个进程在内存中是有多个状态的,比如就绪状态,,等待状态呀,运行状态;这个就好比我们人,在去面试的时候,也有等待状态呀,面试时候的状态呀,什么准备面试的状态呀这样;反转我们得知道,这个状态可以表示当时程序处于什么样的状态,那么该进程就得做什么样的事就可以了;

  • 优先级也很好理解:优先级也就是每个进程被执行的优先程度,看看哪个进程被cpu调度而已,我们可以通过一些代码策略改变进程的优先级,但是一般每个进程都会有一个时间片,时间片的意思是:该进程被cpu执行的时间,一旦该进程能够在时间片执行结束,那么该进程就结束,不可以在一个时间片内结束,那么该进程就被操作系统调度,离开cpu,让另一个进程来获得cpu的使用权,也就是操作系统调度了另一进程给cpu执行;按道理来说每个进程的时间片都是相等的,也就是说cpu执行每个进程的时间都是公平的,但是我们可以通过一些策略修改进程的执行顺序,也就是说,优先级;

  • 程序计数器: 程序中即将被执行的下一条指令的地址。理解这个程序计数器,我们知道程序是有一种执行状态得,顺序执行,跳转执行,本质执行指令,都是cpu调度得结果,cpu怎么知道如何执行你的代码指令呢?就是通过这个程序计数器来判断,这个程序计数器就是一个寄存器,存放该进程下一条要执行的指令;

  • 内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针;
    要知道,我们的进程是包括,进程的代码和相关数据+该进程的PCB信息,那么进程的PCB信息是如何和进程的代码联系起来的呢?那就是通过这个内存指针,这个内存指针会执行进程的代码和相关数据;然后OS通过管理PCB,进而就会管理到PCB中的内存指针,由于有内存指针指向了代码和相关数据,那么也就管理到了进程的代码和相关数据;

  • [ ]上下文数据: 进程执行时处理器的寄存器中的数据。
    上下文信息,说到这个,我都非常激动,因为这个是理解进程的核心概念属性;上下文数据,上下文信息都可以这么叫,那到底什么是上下文信息呢?
    我们知道程序在运行时候,是需要把一些临时数据存放在寄存器中的,这些临时数据是运行程序的关键数据,我们也知道,每个进程都会有一个时间片,当该时间片被用完了,OS就会让该进程离开CPU,进入让另一个进程来使用CPU,但是我们知道,如果一个进程离开CPU前,什么都没有留下来的话(也就是相关的数据:比如该进程在执行过程中,突然到一半时候,时间片用完,但是数据还没有被处理完全,这个时候这个数据没有被留下来,没有被保存),此时等下次再次调度该进程,由于什么之前离开的相关数据都没有被保存下来,CPU执行该进程就不可以恢复到上次没有执行完的哪个步骤了;类似于在一个学习,你说你要去当兵,但是你离开学校前,并没有和学习说明情况,没有把你的学籍保留,没有告知学校你的课程没有上完,此时你冒然离开;学校不知道你离开的事实,就把你当作旷课处理,等你当兵回来时候,发现自己被开除了;就和进程的道理一样;
    所以说:上下文信息:就是一个进程被调度时候,需要保存的临时信息;
    并且这个信息有保存信息,和恢复信息的操作;保存信息的意思是:进程离开CPU 时候,该进程的寄存器相关属性信息被保存在了PCB中,恢复信息是:进程获得CPU时候前,需要恢复原来离开CPU时候保存下来的信息,这样才可以继续使用CPU,被调度执行;

为什么上下文信息需要被保存切换呢?


其他的信息就不怎么谈了,我们要知道PCB就是一个进程的控制块,被OS所管理,OS管理进程控制块,也就是PCB,通过一些数据结构的方式管理,比如链表啥的,管理了PCB也就管理了进程的代码,因为PCB属性里有一个内存指针,指向了进程代码和相关数据;
OS不直接管理进程的代码,而是通过进程的PCB间接管理进程的相关代码和数据;


3 查看进程的方式

  1. ps ajx | grep 进程 :查看进程相关信息;
    在这里插入图片描述

如果需要配合相关的详细信息:
2. ps ajx | head -1 && ps ajx | grep 进程,这个命令会多了一些进程相关信息的解释;
在这里插入图片描述
3.通过文件查看文件 /porc查看进程,这个目录是Linux带给我们的一个查看进程的一个目录
在这里插入图片描述
其中数字都是一些进程的ID号;
可以通过ls -l /proc/进程id号查看你的进程的详细信息属性;


4 创建进程的方式

  1. 第一种创建进程的方式:就是指向我们的命令,这就是创建进程了,只不过指向命令时候进程一下子就执行完了;比如:执行ls命令,这就是创建进程了,执行结果就是显示文件名倍;
  2. 第二种就是我们写好的程序通过./a.out方式调用,也是在创建进程;
  3. 通过fork函数来创建子进程

5 理解fork创建子进程

fork函数创建子进程,它可以达到一个效果:创建成功后,fork之后的代码会执行两次:
在这里插入图片描述


其实fork之后就是创建出一个子进程,它的父进程就是main函数;而main函数的父进程就是bash进程,bash也就是命令行解释器;
在这里插入图片描述


该如何理解我们fork创建子进程这个说法?

在这里插入图片描述


fork之前的代码会被共享嘛?


fork之后的子进程数据也是共享的嘛?


fork之后的子进程是和父进程相互独立?


6 fork的返回值意义

我们说,假如在父进程创建了一个子进程,让父子进程都做一样的事情有意义嘛?其实是没意义的,既然我父进程本来不fork都可以完成的是,为什么还要fork一个新的子进程来做和父进程一样的事情;
所以说我们fork是有返回值的,这个返回值的意义就是:
可以让父子进程做不一样的是:

fork一次调用,两次返回:
在父进程返回子进程的pid,在子进程返回0;


在这里插入图片描述


代码验证以下:一次调用,两次返回
在这里插入图片描述


如何理解fork一次调用两次返回?
在这里插入图片描述



如何理解fork返回是两个不同的pid,也就是返回值不一样呢?


如何理解给父进程返回子进程的id,给子进程返回0呢?


7 fork的基本使用

#include<iostream>    
#include<unistd.h>                                                 
    
int main()    
    
{    
  pid_t pid = fork();    
    
  if(pid == 0){    
    //child process    
    while(true){    
    
      std::cout<<"i am a child pid = "<<getpid()<<"parent pid = "
      <<getppid()<<"fork ret = "<< pid <<std::endl;        
      sleep(1); //子进程睡一秒,为了让cpu调度执行父进程    
    }    
  }    
  else if(pid >0){    
    //parent process            
    while(true){    
      std::cout<<"i am parent pid = "<<getpid()<<"parent pid = "
      <<getppid()<<"fork ret = "<< pid <<std::endl;        
      sleep(1);//父进程睡1秒,为了让cpu调度执行子进程    
      
    }    
  }    
  else{  
     //error
  }
  sleep(1);
  return 0;
  


执行的结果如下;
在这里插入图片描述


以后我们就可以通过fork创建子进程,通过if else 分流实现在不同的进程执行不同逻辑的代码


8 Linux的进程状态理解

为什么我们要强调Linux系统下的进程状态呢?
我们平时在操作系统书本上看的进程状态,它们都不是具体的状态,也就是说:操作系统书上的进程状态更加一般化,它适用于任何操作系统上的进程状态解释;
往往我们理解操作系统书本上的进程状态都比较吃力,是因为没有一个具体的操作系统去理解进程转台到底是什么意思:


这是操作系统书上一般化的进程状态的解释:
在这里插入图片描述


我们可以看看我们Linux内核对进程状态是如何解释的:

*
* The task state array is a strange "bitmap" of
* reasons to sleep. Thus "running" is zero, and
* you can test for combinations of others with
* simple bit tests.
*/
static const char * const task_state_array[] = {
"R (running)", /* 0 */
"S (sleeping)", /* 1 */
"D (disk sleep)", /* 2 */
"T (stopped)", /* 4 */
"t (tracing stop)", /* 8 */
"X (dead)", /* 16 */
"Z (zombie)", /* 32 */
};

首先我们可以问问问自己进程状态的信息存在哪里呢?

进程的状态的意义是什么?

我们一个一个来理解具体的进程状态:
首先是进程状态R


处于运行态的进程一定会被CPU调度嘛?


代码验证一下R状态:

#include<iostream>
#include<unistd.h>

int main()
{
	while(true);
	return 0;
}

在这里插入图片描述


对于S和D状态:


比如:在我们进程为了读取磁盘信息时候,发现磁盘没有信息,在访问网卡资源时候,发现网卡还没有资源,在等待键盘输入字符时候,我们还没有按下键盘字符,此时的进程都会被放入一个等待队列中,等待该条件触发,才可以被调度;
换句话说:不是所有的进程都是为了等待CPU资源的,而我们的等待队列的进程就不是为了等待CPU资源,而是等待其他外设的资源,因为外设的资源太慢了,不可以让该进程在运行态的队列等待cpu资源,只能让它到放到另一个队列:即等待队列中等待外设资源;
当我们外设资源等到,也就是比如说磁盘又内容可以读时候,我们操作系统就会把该等待磁盘资源的进程,从等待队列插入到运行队列中,等待CPU调度;

也就是说:进程的状态不是一成不变的,而是会更具它需要完成某种任务功能而发生改变的;


在这里插入图片描述


S状态和D状态的理解


R状态和S状态之间的联系


T状态:


t状态:


X状态


9 僵尸进程

Z状态:


僵尸进程的状态演示:
我们控制让子进程先退出,父进程不退出,观察子进程的状态:

#include<unistd.h>    
    
int main()    
    
{    
  pid_t pid = fork();    
    
  if(pid == 0){    
  //child process    
    
    std::cout<<"i am a child pid = "<<getpid()<<"parent pid = "<<getppid()<<"fork ret = "<< pid <<std::endl;        
    return 1;    
}    
  else if(pid >0){    
  //parent process    
    
  while(true){    
    std::cout<<"i am parent pid = "<<getpid()<<"parent pid = "<<getppid()<<"fork ret = "<< pid <<std::endl;        
    sleep(1);//父进程睡1秒,为了让cpu调度执行子进程    
  }    
 }                                                                                      
 else{    
  //error    
 }    
  sleep(1);    
  return 0; 

在这里插入图片描述


僵尸进程的危害:


对于如何避免这种状态的产生之后会分析。


10 孤儿进程

孤儿进程:也就是父进程先退出了,子进程还没退出,此时这个子进程就会成为孤儿进程;
父进程退出了,没有回收子进程,那么子进程就会被1号进程领养,也就是说:1号进程成为孤儿进程的父进程;
1号进程就是操作系统,孤儿进程自然而然会给1号进程回收;


我们也来代码演示一下:
让父进程先退出,子进程继续运行;


#include<iostream>                                                                                                   
#include<unistd.h>

int main()

{
  pid_t pid = fork();

  if(pid == 0){
  //child process

  sleep(30); //子进程睡30秒,让父进程执行它的代码,让他退出,为了让子进程形成孤儿进程
    std::cout<<"i am a child pid = "<<getpid()<<"parent pid = "<<getppid()<<"fork ret = "<< pid <<std::endl;
  }
  else if(pid >0){
  //parent process

    std::cout<<"i am parent pid = "<<getpid()<<"parent pid = "<<getppid()<<"fork ret = "<< pid <<std::endl;
  }
 else{
  //error
 }
  sleep(1);
  return 0;
}

在这里插入图片描述


举报

相关推荐

0 条评论