进程调度 之 完全公平调度
从调度器说起
内中调度器分为周期性调度器和主调度器,他们是调度工作的主体,而更为详细的调度根据策略的不同交给不同的调度类,比如交给公平调度类;
周期性调度器:
周期调度器按照一定的频率周期性的运行,除了进行调度相关的数据统计之外,还会激活具体进程调度类的周期性调度方法;
在完全公平调度类的周期性调度方法中,首先更新虚拟时间,然后检查是否进程的运行时间已经超过了延迟周期中的时间(时间比重为:权重越大的进程运行时间越长),如果超过了,则发出重新调度请求;
主调度器:
如果要将cpu分配给与当前活动进程不同的另外一个进程,那么需要直接调用主调度函数(schedule());该函数调用调度类的方法完成下面操作,将当前运行的进程从就绪队列移除,从就绪队列选择下一个将要运行的进程,进行进程上下文的切换;
在完全公平调度中,主要涉及以下几个核心的概念:
vruntime && min_runtime的计算,以及它们与红黑树中节点的关系;
对于新建进程和睡眠进程的处理;
vruntime–调度实体的虚拟运行时间
curr->vruntime += delta_exec * (NICE_0_LOAD/curr->load.weight)
其中delta_exec为两次更新负荷统计量的时间差;
NICE_0_LOAD位nice级别为0的进程对应的权重,根据定义,nice为0的进程虚拟时间和物理时间是相等的;
curr->load.weight为进程权重,越重要的进程优先级越高,nice值越小,权重越大;
由上述条件可知,优先级高的进程,其每次增加的虚拟时间越小;
vruntime在运行时稳定的增加,它在红黑树中的位置也不断的右移;因为越重要的进程vruntime增加越慢,所以它们向右移动的速度越慢,这样其被调用的机会就越大;
min_vruntime–队列的最小虚拟运行时间
进程的虚拟运行时间vruntime与公平调度就绪队列的最小虚拟运行时间min_vruntime之间的差值,作为就绪队列中红黑树节点的key;
该值的计算:
如果有进程在红黑树中准备调度,内核获取其vruntime,即所有节点中最小的vruntime值,然后取其值与当前调度进程vruntime的最小者;如果树中没有待调度进程,那么使用当前进程的vruntime;
为了保证队列中的min_vruntime是单调递增的,内核会取上述vruntime与队列当前的min_vruntime的较大值;也就是,队列中的min_vruntime只有被树上的某个节点的vruntime超出时才更新;
注意:因为树中的键值是进程的vruntime与队列的min_vruntime的差值,上述计算过程保证了不出现负值;
唤醒抢占
当进程被唤醒时需要检查是否唤醒进程可以抢占当前的进程,这个检查过程是不涉及调度器的;内核需要确保每个进程在在被抢占之前已经运行了某一小段时间限额,这样能够避免抢占发生频繁到导致的进程上下文切换时间过多;但如果新进程的虚拟运行时间加上上述限额时间之后,仍然小于当前执行进程的虚拟运行时间,则仍然可以抢占,需要重新调度;
新创建进程
可以通过参数sysctl_sched_child_runs_first参数控制新创建的子进程应该在父进程之前运行;通常是有益的,特别是在子进程随后会调用exec系统调用的情况下;
如果该参数设置,则要求子进程的虚拟运行时间需要小于父进程的虚拟运行时间,这样子进程才能优先于父进程运行;如果父进程的虚拟运行时间比子进程的虚拟运行时间小,则二者进行交换;然后将子进程加入到就绪队列,并请求重调度;
睡眠进程
进程进入睡眠,其vruntime值保持不变,因为每个队列的min_vruntime同时会增加,那么睡眠想来之后,在红黑树中的位置会更靠左,因为其key变得更小了;