第四章 进程调度
进程调度程序:确保进程能有效工作的一个内核子程序
决定将哪个进程投入运行,何时运行已经运行多长时间
进程调度程序可看做在可运行态进程之间分配有限的处理器时间资源的内核子系统
原则:只要有可以执行的进程,那么就总会有进程正在执行
基本工作:在一组处于可运行状态的进程中选择一个来执行
4.1 多任务
多任务操作系统:能同时并发地交互执行多个进程的操作系统
多任务系统可划分成两类:非抢占式多任务,抢占式多任务
抢占:强制的挂起动作
进程的时间片:分配给每个可运行进程的处理器时间段
在非抢占式多任务模式下,除非进程自己主动停止运行,否则它会一直执行
让步:进程主动挂起的操作
缺点:调度程序无法对每个进程该执行多长时间做出统一规定
Unix一开始就采用的是抢占式的多任务
4.2 Linux的进程调度
O(1)调度器-->反转楼梯最后期限调度算法-->完全公平调度算法CFS
4.3 策略
4.3.1 I/O消耗型和处理器消耗型的进程
I/O消耗型:进程的大部分时间用来提交I/O请求或是等待I/O请求,但它等待的I/O请求最后总会阻塞
处理器消耗型:进程把时间大多用在执行代码上,除非被抢占,否则它们通常都一直不停地运行。极端例子:无限循环地执行
进程可以同时展示这两种行为
平衡量:进程响应速度(响应时间短),最大系统利用率(高吞吐量)
Unix系统的调度程序更倾向于I/O消耗型程序
4.3.2 进程优先级
基于优先级的调度:优先级高的进程先运行,低的后运行,相同优先级的进程按轮转方式进行调度
两种不同优先级范围
nice值:范围-20到+19,默认值为0,越大的nice值意味着更低的优先级,低nice值得进程可以获得更多的处理器时间
实时优先级:其值可配置,默认情况下变化范围0到99,越高的实时优先级数值意味着进程优先级越高
4.3.3 时间片
时间片:表明进程在被抢占前所能持续运行的时间
时间片过长导致系统交互的响应表现欠佳
时间片过短明显增大进程切换带来的处理器耗时
I/O消耗型不需要长的时间片,处理器消耗型的进程则希望越长越好
4.4 Linux调度算法
4.4.1 调度器类
Linux调度器是以模块方式提供的,目的:允许不同类型的进程可以有针对性嘚选择调度算法
调度器类,允许多种不同的可动态添加的调度算法并存。
每个调度器都有一个优先级,基础的调度器代码定义在kernel/sched.c文件中,按照优先级顺序遍历调度类
完全公平调度(CFS)是一个针对普通进程的调度类,在Linux中称为SCHED_NORMAL,定义在文件kernel/sched_fair.c
4.4.2 Unix系统中的进程调度
概念:进程优先级和时间片
时间片:进程运行多少时间
实质问题:分配绝对的时间片引发的固定的切换频率,给公平性造成了很大变数
CFS采用的方法是对时间片分配方式进行根本性的重新设计:完全摒弃时间片而是分配给进程一个处理器使用比重
4.4.3 公平调度
出发点:进程调度的效果应如同系统具备一个理想中的完美多任务处理器
每个进程将能获得1/n的处理器时间——n是指可运行进程的数量
给予n个进程中每个进程同样多的运行时间
首先要确保系统性能不受损失
CFS的做法是允许每个进程运行一段时间、循环轮转、运行最少的进程作为下一个运行进程
越高的nice值进程获得最低的处理器使用权重
为了计算准确的时间片,CFS为完美多任务中的无限小调度周期的近似值设立了一个目标——目标延迟,越小的调度周期将带来越好的交互性
当可运行任务数量趋于无限时,它们各自所获得的处理器使用比和时间片都将趋于0,CFS为此引入每个进程获得的时间片底线,称为最小粒度,默认情况下这个值是1ms,确保切换消耗被限制在一定范围内
任何进程所获得的处理器时间是由它自己和其他所有可运行进程nice值得相对差值决定的,nice值对时间片的作用不再是算术加权,而是几何加权
任何nice值对应的绝对时间不再是一个绝对值,而是处理器的使用比
CFS称为公平调度器是因为它确保给每个进程公平的处理器使用比
4.5 Linux调度的实现
位于文件kernel/sched_fair.c
四个组成部分:时间记账,进程选择,调度器入口,睡眠和唤醒
4.5.1时间记账
所有的调度器都必须对进程运行时间做记账,当一个进程的时间片被减少到0时,它就会被另一个尚未减到0的时间片可运行进程抢占
1.调度器实体结构
需要确保每个进程只在公平分配给它的处理器时间内运行
CFS使用调度器实体结构来追踪进程运行记账
调度器实体结构作为一个名为se的成员变量,嵌入在进程描述符struct task_struct内
2.虚拟实时
vruntime变量存放进程的虚拟运行时间,该运行时间的计算是经过了所有可运行进程总数的标准化
虚拟时间以ns为单位
CFS使用vruntime变量来记录一个程序到底运行了多长时间以及它还应该再运行多久
定义在kernel/sched_fair.c文件中的update_current()函数实现了该记账功能
4.5.2 进程选择
CFS调度算法的核心:选择具有最小vruntime的任务
CFS使用红黑树来组织可运行进程队列,并利用其迅速找到最小vruntime值得进程
红黑树称为rbtree,它是一个自平衡二叉搜索树
红黑树是一种以树节点形式储存的数据,这些数据都会对应一个键值
可以通过这些键值来快速检索节点上的数据
1.挑选下一个任务
2.向树中加入进程
3.从树中删除进程
4.5.3 调度器入口
进程调度的主要入口点是函数schedule,它定义在文件kernel/sched.c
调用进程调度器的入口:选择哪个进程可以运行,何时将其投入运行
调用pick_next_task(),它以优先级为序,从高到低,依次检查每一个调度类,并且从最高优先级的调度中,选择最高优先级的进程
4.5.4 睡眠和唤醒
休眠的进程处于一个特殊的不可执行状态
进程把自己标记成休眠状态,从可执行红黑树中移动,放入等待队列,然后调用schedule()选择和执行一个其他进程。唤醒的过程刚好相反:进程被设置为可执行状态,然后再从等待队列中移到可执行红黑树中
1.等待队列
2.唤醒
4.6 抢占和上下文切换
从一个可执行进程切换到另一个可执行进程,定义在kernel/sched.c中的context_switch()函数负责处理
调用声明在<asm/mmu_context.h>中的switch_mm(),该函数负责把虚拟内存从上一个进程映射切换到新进程中
调用声明在<asm/system.h>中的switch_to(),该函数负责从上一个进程的处理器状态切换到新进程的处理器状态
4.6.1 用户抢占
内核无论是在中断处理程序还是在系统调用后返回,都会检查need_resched标志,如果它被设置了,那么,内核会选择一个其他进程投入运行
用户抢占在一下情况时产生:
从系统调返回用户空间内
从中断处理程序返回用户空间时
4.6.2 内核抢占
只要没有锁,内核就能抢占
锁是非抢占区域的标志
内核抢占会发生在:
中断处理程序正在执行,且返回内核空间之前
内核代码再一次具有可抢占性的时候
如果内核中的认为显式地调用schedule()
如果内核中的认为阻塞