第一次作业:基于Linux 2.6 的源码 分析进程模型

1.前言

本文的内容是基于Linux 2.6的源码,深入分析进程模型。

  • 什么是进程
  • 操作系统是怎么组织进程的
  • 进程状态如何转换
  • 进程是如何调度的
  • 谈谈自己对该操作系统进程模型的看法

2.什么是进程

  进程是处于执行期的程序以及它所包含的所有资源的总称,包括虚拟处理器,虚拟空间,寄存器,堆栈,全局数据段等。

 Windows10进程如图所示

3.操作系统是如何组织进程的

 

一、描述进程——PCB

进程信息被放在一个叫做进程控制块的结构中,可以理解为进程属性的集合,称之为:PCB,在Linux下,PCB是一个叫做task_struct的结构体,这个结构体里面存放了进程的有关信息。

task_struct结构体的内容分类:

①标识符(PID):描述本进程的唯一的标识符,用来区别其他进程

获取pid的方法有很多,最推荐的一种就是通过系统调用getpid()来获取进程的pid,

②状态:任务状态,退出码,退出信号等

状态分类:R运行状态(runing)  S睡眠状态(sleeping) D磁盘休眠状态(Disk sleeping) T停止状态(stopped) X死亡状态(dead) Z僵尸状态(zombie) 

③优先级:相对于其他进程的优先级

PRI:进程可执行的优先级,值越小优先级越高

NI:代表nice值,表示进程可被执行的优先级的修正数值

④程序计数器:程序中即将被执行的下一条指令的地址

⑤内存指针:包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针

⑥上下文数据:进程执行时处理器的寄存器中的数据

⑦I/O状态信息:包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表

⑧记账信息:可能包括处理器的时间总和,使用的时钟数总和,时间限制,记账号等

⑨其他信息

二、组织进程

因为进程需要不断的关闭和开启,和数据结构中链表的结构很相似,所以运行在系统的进程都以task_struct链表的形式存在内核里面,此时我们就把进程组织起来了。

三、查看进程

现在进程已经被描述和组织起来,那么有些方法可以让我们查看进程呢?

①进程信息可以被  /proc  文件查看; 比如:要查看PID(进程的标识符)为1的进程,就需要查看  /proc/1  这个文件夹

②通过系统调用获取进程标识符:

 

#include<stdio.h>  
#include<sys/types.h>  
#include<unistd.h>  
int main(){  
      printf("pid:%d\n",getpid());  
      printf("ppid:%d\n",getppid());  
      return 0;  
 }  

 

getppid()函数是获取父进程的标识符,getpid()函数是获取子进程的标识符

四、创建进程

通过系统调用创建进程,也就是使用fork()函数

#include<stdio.h>  
#include<sys/types.h>  
#include<unistd.h>  
int main(){  
       pid_t ret = fork();  
     printf("hello proc:%d,ret = %d\n",getpid(),ret);  
       return 0;  
  }  

五、进程的状态:

R运行状态(running):表明进程要么在运行中,要么在运行队列里面

S睡眠状态(sleeping):意味着进程在等待事件的完成,这个睡眠也叫做可中断睡眠

D磁盘休眠状态(Disk sleep):这个状态的睡眠通常在等待I/O的结束,也叫做不可中断睡眠

T停止状态(stopped):可以发送SIGSTOP信号给进程,让进程停止。也可以发送SIGCONT信号让进程继续运行。

X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里面看到这个状态

Z僵尸状态(zombie):子进程退出,父进程没有读取子进程的退出码,子进程进入Z状态

僵尸进程:

#include<stdio.h>  
#include<sys/types.h>  
#include<unistd.h>  
#include<stdlib.h>  
int main(){  
      pid_t id = fork();  
      if(id<0){  
          perror("fork failed!\n");  
          return 1;  
      }  
      else if (id>0){//这个循环里面,我们让父进程sleep30s  
          printf("father pid:%d\n",getpid());  
          sleep(30);  
      }  
      else {//这个循环里面,我们让子进程sleep5s,之后退出  
          printf("child pid:%d\n",getpid());  
          sleep(5);  
          exit(EXIT_SUCCESS);  
      }  
      return 0;  
}  

 

僵尸进程的危害:

①因为进程的状态必须被维护,退出状态也属于进程的基本信息,所以就保存在PCB中,在Z状态中,子进程不退出,PCB就要一直被维护

②子进程进入僵尸状态之后会一直占用内存,造成内存的资源浪费

③内存的泄漏

4.进程状态如何转换

1. Linux进程状态有:

TASK_RUNNING : 就绪态或者运行态,进程就绪可以运行,但是不一定正在占有CPU,对应进程状态的R

TASK_INTERRUPTIBLE:睡眠态,但是进程处于浅度睡眠,可以响应信号,一般是进程主动sleep进入的状态,对应进程状态S

TASK_UNINTERRUPTIBLE:睡眠态,深度睡眠,不响应信号,典型场景是进程获取信号量阻塞,对应进程状态D

TASK_ZOMBIE:僵尸态,进程已退出或者结束,但是父进程还不知道,没有回收时的状态,对应进程状态Z

TASK_STOPED:停止,调试状态,对应进程状态T

2. 进程调度时机:

进程调度会引起进程状态转换,由上图可知如下情况会触发调度,进程终止或进程睡眠时主动exit或sleep释放CPU;浅度睡眠的进程被CFS调度选中唤醒,深度睡眠进程由于信号量,锁等的释放而被唤醒;进程收到信号量等;还有一种最常见的中断,异常。

5.进程是如何调度的

Linux进程调度的目标

    1.高效性:高效意味着在相同的时间下要完成更多的任务。调度程序会被频繁的执行,所以调度程序要尽可能的高效;

    2.加强交互性能:在系统相当的负载下,也要保证系统的响应时间;

    3.保证公平和避免饥渴;

    4.SMP调度:调度程序必须支持多处理系统;

    5.软实时调度:系统必须有效的调用实时进程,但不保证一定满足其要求;

Linux进程优先级

进程提供了两种优先级,一种是普通的进程优先级,第二个是实时优先级。前者适用SCHED_NORMAL调度策略,后者可选SCHED_FIFO或SCHED_RR调度策略。任何时候,实时进程的优先级都高于普通进程,实时进程只会被更高级的实时进程抢占,同级实时进程之间是按照FIFO(一次机会做完)或者RR(多次轮转)规则调度的。

实时进程的调度

  实时进程,只有静态优先级,因为内核不会再根据休眠等因素对其静态优先级做调整,其范围在0~MAX_RT_PRIO-1间。默认MAX_RT_PRIO配置为100,也即,默认的实时优先级范围是0~99。而nice值,影响的是优先级在MAX_RT_PRIO~MAX_RT_PRIO+40范围内的进程。

  不同与普通进程,系统调度时,实时优先级高的进程总是先于优先级低的进程执行。知道实时优先级高的实时进程无法执行。实时进程总是被认为处于活动状态。如果有数个 优先级相同的实时进程,那么系统就会按照进程出现在队列上的顺序选择进程。假设当前CPU运行的实时进程A的优先级为a,而此时有个优先级为b的实时进程B进入可运行状态,那么只要b<a,系统将中断A的执行,而优先执行B,直到B无法执行(无论A,B为何种实时进程)。

   不同调度策略的实时进程只有在相同优先级时才有可比性:

   1. 对于FIFO的进程,意味着只有当前进程执行完毕才会轮到其他进程执行。由此可见相当霸道。

   2. 对于RR的进程。一旦时间片消耗完毕,则会将该进程置于队列的末尾,然后运行其他相同优先级的进程,如果没有其他相同优先级的进程,则该进程会继续执行。

   ,对于实时进程,高优先级的进程就是大爷。它执行到没法执行了,才轮到低优先级的进程执行。等级制度相当森严啊。

非实时进程调度

Linux对普通的进程,根据动态优先级进行调度。而动态优先级是由静态优先级(static_prio)调整而来。Linux下,静态优先级是用户不可见的,隐藏在内核中。而内核提供给用户一个可以影响静态优先级的接口,那就是nice值,两者关系如下:

  static_prio=MAX_RT_PRIO +nice+ 20

  nice值的范围是-20~19,因而静态优先级范围在100~139之间。nice数值越大就使得static_prio越大,最终进程优先级就越低。

  ps -el 命令执行结果:NI列显示的每个进程的nice值,PRI是进程的优先级(如果是实时进程就是静态优先级,如果是非实时进程,就是动态优先级)  

  而进程的时间片就是完全依赖 static_prio 定制的,见下图,摘自《深入理解linux内核》,

  

   我们前面也说了,系统调度时,还会考虑其他因素,因而会计算出一个叫进程动态优先级的东西,根据此来实施调度。因为,不仅要考虑静态优先级,也要考虑进程的属性。例如如果进程属于交互式进程,那么可以适当的调高它的优先级,使得界面反应地更加迅速,从而使用户得到更好的体验。Linux2.6 在这方面有了较大的提高。Linux2.6认为,交互式进程可以从平均睡眠时间这样一个measurement进行判断。进程过去的睡眠时间越多,则越有可能属于交互式进程。则系统调度时,会给该进程更多的奖励(bonus),以便该进程有更多的机会能够执行。奖励(bonus)从0到10不等。

  系统会严格按照动态优先级高低的顺序安排进程执行。动态优先级高的进程进入非运行状态,或者时间片消耗完毕才会轮到动态优先级较低的进程执行。动态优先级的计算主要考虑两个因素:静态优先级,进程的平均睡眠时间也即bonus。计算公式如下,

     dynamic_prio = max (100, min (static_prio - bonus + 5, 139))

  在调度时,Linux2.6 使用了一个小小的trick,就是算法中经典的空间换时间的思想,使得计算最优进程能够在O(1)的时间内完成。

Linux进程状态机

 

 

6.谈谈自己对操作系统进程模型的看法

进程是操作系统提供的最古老的也是最重要的抽象概念之一,它们将一个单独的CPU变换成多个虚拟CPU。没有进程的抽象,现代计算将不复存在。所以进程非常的重要 ,所以要好好地学习进程,以更进一步的学习操作系统的后面的知识。

 

posted @ 2018-04-27 23:29  JMUZJX  阅读(288)  评论(0编辑  收藏  举报