进程调度
进程调度:是指进程调度程序,主要负责决定将哪个进程投入运行,何时运行以及运行多长时间;可以认为是在可运行态进程之间分配有限的处理器时间的内核子系统
调度程序:最大限度利用处理器时间(可理解为最大程度上减少cpu的空闲时间),当cpu数少于待运行进程数时,这时会有一些进程等待,调度程序需要选择一个来执行(基本工作)
一、多任务
多任务操作系统:同时并发地交互执行多个进程的操作系统
多任务系统的分类(所有unix变体包括Linux提供抢占式的多任务模式):
非抢占式多任务:指的是除非进程自己主动停止运行,否则它会一直执行;特殊情况进程主动挂起给其他进程让步
抢占式多任务:由调度程序来决定何时停止进程的运行,一便调度其他进程。(抢占和进程时间片)
- 抢占:指在调度过程中强制的挂起动作
- 进程时间片:分配给每个可运行进程的处理器时间段,有效管理时间片可以避免个别进程独占系统资源(现代操作系统都采用动态时间片计算方式)
- 让步:缺点调度程序无法做统一规定,如果一个绝不让步的悬挂进行会使 系统崩溃
二、策略
策略:指调度程序在何时让什么程序运行,主要需要平衡响应时间短和搞吞吐量
2.1、I/O消耗型和处理器消耗型的进程
进程可划分为:I/O消耗型和处理器消耗型
区别在于:
a、前者主要的时间消耗在I/O请求(阻塞),即便不读写磁盘,也是在等待和用户交互操作(鼠标等)
b、后者主要是进程的主要时间在执行代码上,除非被抢占否则一直运行
Linux中更倾向于优先调度I/O消耗性进程(缩短响应时间)
2.2、进程优先级
Linux中的进程优先级有两种:
a、nice值(-20到19)默认是0,越大优先级越低,nice值代表时间片的比例,可以通过ps -el命令查看系统中进程列表,NI列为nice值
b、实时优先级(0到99闭区间)越高优先级越高,任何实施进程的优先级都高于普通进程,通过ps -eo state,uid,pid,ppid,rtprio,time,comm查看位于PRTPRIO列
图片中显示的-表示该进程不是实时进程
2.3、时间片
在Linux的CFS调度器中没有直接分配时间片给进程,它将处理器的使用比划分给了进程。这样进程获得的时间片和系统负载密切相关,比例也受到nice值的影响(作为权重)
CFS:完全公平调度
在Linux的CFS调度下抢占时机取决于新的可运行程序消耗了多少处理器使用比
三、Linux调度算法
3.1、调度器类
Linux调度器是以模块方式提供,允许多种不同的可动态添加的调度算法并存,调度属于自己范畴的进程(对进程分类然后找专门的算法进行调度),调度器有优先级
3.2、公平调度
CFS的理念:进程调度的效果应该如同系统具备一个理想中完美多任务处理器,即每个进程可以获得1/n的处理器时间(n进程数)。(理想状态下可以在单位时间内每个进程运行的时间相同)
现实中CFS的做法:运行每个进程运行一段时间、循环轮转、选择运行时间最少的进程作为下一个运行进程,不采用分配进程时间片,在所有可运行进程的总数上计算出每个进程应该运行多久,而不是依靠nice值计算时间片。
CFS中nice值作为进程获得处理器运行比的权重(越高获得的越少)
CFS中为了防止进程之间的频繁切换,设定时间片的最小粒度为1ms
四、Linux调度的实现
Linux中CFS实现的四个组成:
- 时间记账
- 进程选择
- 调度器入口
- 睡眠和唤醒
4.1、时间记账
所有调度器都必须对进程运行时间做记账,当每次系统时钟节拍发生时,时间片会被减少一个节拍周期,知道为0时,就会被一个未减到0的时间片可运行进程抢占。
虚拟实时:
在调度器的结构体struct_sched_entity中vruntime变量存放进程的虚拟运行时间
虚拟运行时间(单位ns):(花在运行上的时间)计算的是经过了所有可运行进程总数的标准化(被加权的即nice加权的),vruntime和定时器节拍不再相关
CFS使用vruntime来记录一个程序运行了多久以及还能再运行多久
4.2、进程选择
CFS算法的核心:选择就有最小vruntime的任务
CFS使用红黑树(rbtree)来组织可运行进程队列,并利用其找到最小vruntime值的进程
Linux中的红黑树:自平衡二叉搜索树, 节点的键值是可运行进程的虚拟运行时间,左分支是键值小于当前节点的键值,右分支是键值大于当前节点的键值
挑选下一个任务:选择最小vruntime值的进程
向树中加入进程:和当前节点比较,左子树是键值小的,右子树是键值大的
从树中删除进程:删除动作发生再进程堵塞(不可运行态)或终止(结束运行)
4.3、睡眠和唤醒
休眠(阻塞):进程处于一个特殊的不可执行状态,通过等待队列进行处理。
等待队列:由等待某些事件发生的进程组成的简单链表。
进程将自己加入等待队列的步骤:
- 调用宏DEFINE_WAIT()创建一个等待队列的项;
- 调用add_wait_queue()把自己加入到队列中;
- 调用prepare_to_wait()方法将进程的状态变更为TASK_INTERRUPTIBLE或TASK_UNINTERRUPTIBLE
- 如果状态是TASK_INTERRUPTIBLE,则信号唤醒进程
- 进程被唤醒时,再次检查条件
- 当条件满足后,进程将自己设置为TASK_RUNNING并调用finish_wait()把自己移出等待队列
五、抢占和上下文切换
上下文切换:从一个可执行进程切换到另一个可执行进程