这一章我们主要介绍进程的状态,了解进程的状态有助于我们分析进程在系统中的各种作用等。进程的调度算法,进程的调度算法可以让我们看到内核是如何设计出一套注重效率并且兼顾公平的算法,内核如何为不同的进程分配资源等,帮助我们加深对于进程相关知识的了解。
进程的状态
首先我们要知道进程虽然是占有CPU资源的可是就像人不能一直工作一样,进程也不能一直占用CPU资源,同时我们还要考虑并发问题等,具体原因有:
1.进程可能需要等待某种外部条件的满足,在满足条件之气那无法继续执行,在这种情况下占用CPU是对资源的浪费
2.linux是多用户多任务的操作系统,可能同时存在多个可以运行的进程,进程个数可能远远多于CPU个数,一个进程始终占有CPU对于其他进程来说不公平
3.linux支持软实时,实时进程的优先级高于普通进程,实时进程之间也有优先级的差别,软实时进程进入可运行状态的时候,可能会发生抢占,抢占当前运行的进程。
接下来我们具体说一下进程的状态,进程的状态一共有七种:
1 | 可运行状态 |
进程调度
设计原则
进程调度是任何一个现代擦欧总系统都要解决的问题,也是我们这篇博客的重点,首先我们要从知道只有TASK_RUNNING状态的进程才可以进入调度队列被调度。
Linux是多任务的操作系统,多任务操作系统可以分为两类:抢占式和非抢占式,在非抢占式的操作系统中只有一个进程自己主动退出让出CPU的使用权其他进程才可以使用CPU,所以非抢占式也被叫做合作型多任务。
可是对于操作系统设计进程调度来说,合作型多任务没有优先级的概念,很多需要立即执行的进程反而长时间陷入等待,所以大多数操作系统是抢占式的,Linux也不例外,抢占式多任务由操作系统来决定进程调度,对于操作系统来说,面对不同类型的进程设计一个全面考虑的调度算法是很不容易的。
他要做到以下的几点:
1 | 公平:每一个进程都可以获得调度的机会,不能出现长时间不被调度的情况 |
那么进程调度具体是干了些什么呢,其实说白了就是挑选下一个执行的进程,如果下一个被调度的进程和当前进程不是一个进程,就执行上下文切换。
Linux是抢占式内核,从内核2.6开始不仅支持用户态抢占,也支持内核态抢占,可抢占内核的优势在于何以保证系统的响应时间,当高优先级任务一旦就绪,总能及时得到CPU的控制权,但是很明显,内核抢占不能随意发生,某些情况下不能发生内核抢占,为了可以确定什么时候可以发生抢占,内核为每一个进程引入了一个preempt_count计数器,数值为0表示可以发生抢占,数值为1代表不能发生抢占。
preempt_count设置了很重要的一个标志位,即PREEMAT_ACTIVE,该标志为用来标记是否正在发生内核抢占,设置了之后就代表preempt_count不再为0,同时不允许再次抢占,PREEMAT_ACTIVE有一个很重要的作用就是设置了这个标志位的进程那么即使不处于RUNNING状态内核也不能将他从运行队列中剔除。
因为有一种情况是当前占用CPU的进程刚刚将自己设置为睡眠状态并且打算等待信号,可是这个时候发生了抢占,这个时候他就已经不是运行状态了,按道理说要将它从运行队列中剔除,可是如果我们真的剔除了那么他将永远睡眠再也不会醒来。正确的做法是将他再次放入运行队列,他就还有机会操作CPU资源就还有机会被唤醒。
调度类
在选择下一个占用CPU的进程之前,内核会先根据调度类来更些一些数据,用来保证下一个调度的优先级最高。linux下一共有下面几种调度类:
1 | 1.stop_sched_class 停止类 |
这四种调度类是根据优先级顺序排布的,停止类具有最高的优先级,与之对应的空闲类有最低的优先级,挑选下一个进程的时候就先从停止类里面选择,没有的话从实时类选,以此类推。
优先级最高的停止类进程,主要用户多个CPU之间的负载均衡和CPU的热拔插,他所作的事情就是停止正在运行的CPU,以进行任务的迁移或拔插CPU,优先级最低的空闲类负责将CPU置于停机状态,直到中断将其唤醒,这两种类都是为了实现CPU调度性能的功能类,真正和应用层有关系的是实时类和完全公平调度类。
普通进程的优先级
除非Linux用在一些特殊领域,不然Linux上的进程都是完全公平调度类,Linux是多任务系统,系统不能让一个进程始终占据CPU资源,那么如何给每一个进程分配多大的时间片就是一个很值得思考的问题。
Linux实现完全公平调度类使用了一种动态时间片的算法,他给每一个进程分配使用CPU的时间比例,进程调度设计上,有一个很重要的指标是调度延迟,也就是保证每一个可运行的进程都至少运行一次的时间间隔。
比如我们调度延迟是20毫秒,正在运行的进程一共两个,那么每一个进程运行的时间片就是10毫秒,调度延迟可以让每一个进程都有机会占用CPU进程,可是产生了新的问题,要是我们的调度延迟是10毫秒但是正在运行的进程100个的话,那么根据调度延迟来说每一个进程只能分0.1毫秒的时间片,这显然什么都干不了。
为了应对这种情况,完全公平调度提供了另一种调度方法,调度最小颗粒,这个就是说任意进程所运行的时间片都有一个基准的最小值,不能小于这个最小值,对于这两种调度方法,进程规定了一个最大活动进程数目,最大活动数目等于调度延迟除以调度颗粒。
也就是说当当前进程个数小于最大活动数的时候,分得的时间片大于最小颗粒,就用延迟调度,当进程个数太多导致大于最大活动数的时候,分得的时间片小于调度最小颗粒,就按照调度最小颗粒的值来指定时间片。
到目前为止,我们所有的讨论都基于运行对立额上所有的进程都有相同的优先级,可是事实并非如此,有些任务的优先级高,理应获得更多的运行时间,考虑到这种情况,完全公平调度又引入了优先级的概念。
完全公平调度是通过引入调度权重来实现优先级的进程之间按照权重的比例,分配CPU时间,分配给进程的运行时间=调度周期*进程权重/运行队列所有进程权重之和。
Linux下每一个进程都有一个nice值,该值的取值范围是[-20,19]nice值越高代表优先级越低,默认的优先级是0. weight=1024/(1.25^nice_value)。
普通进程的组调度
1 | 假如一个运行队列上一共有四个进程,这四个进程优先级相同,那么根据调度延迟以及调度最小颗粒,每一个进程分25%的时间片,可是如果其中三个进程是A组的,最后一个进程是B组的那么就会造成A组分得了大量的时间片而B组分的很少的情况,针对这种情况,内核先实现组间平衡,再实现组内平衡,也就是说B组的进程最后可以分得50%的时间片而A组的三个进程各自分得16.7%的时间片。 |
实时进程的调度
对于普通进程来说,完全公平调度已经可以实现足够好的性能以及响应体验了,但是对于实时性要求更高的进程来说还是不够,严格来说实时系统可以分为两类,硬实时进程和软实时进程,硬实时进程对于响应时间的要求非常严格,必须保证在一定的时间内完成超出相应事件就会失败并且有很严重的后果,比如军用武器系统,航空航天系统里面就很多硬实时进程。
软实时是硬实时的弱化版本,虽然也要求响应时间,可是超出了规定时间并不会有很严重的后果,比如视频处理,撑死影响用户体验,发生视频丢帧什么的(虽然我觉得视频丢帧也超级重要)。
实时调度策略与优先级
Linux针对实时调度类也提供了两种调度策略,先进先出与时间片轮转策略,无论你使用哪一种策略都会高于前面的公平调度进程。
在linux中一共提供了140个优先级等级,0到99都是属于实时调度类的,100到139是属于完全公平调度类的,完全公平调度类的初始值就是120,所以我们知道不管如何调整nice值也不会使它跨类。
内核为这99个等级维护了99个队列,当要选下一个进程的时候就挨个去这些队列里边找,同时内核还用一个位图来表示哪个队列有运行的程序。
先进先出的调度策略没有时间片的概念,只要没有更高优先级的进程就绪,那么就会一直执行当前最高优先级的进程,除非自动放弃CPU资源或者进程终止。 时间片轮转的调度策略就是在当前相同优先级的优先级队列中所有进程平分时间片。