《Linux内核设计与实现》笔记——进程调度

来源:互联网 发布:会计电算软件 编辑:程序博客网 时间:2024/05/20 13:40

进程调度的基本概念

  • 抢占式多任务:
    • 由调度程序来决定什么时候停止一个进程的运行,以便其他进程能够得到执行机会。这个强制的挂起动作称为抢占。进程在被抢占之前能够运行的时间是预先设置好的,叫做时间片(就是分配给每个可运行进程的处理器时间段)
  • 非抢占式多任务:
    • 除非进程自己主动停止运行,否则会一直执行。进程主动挂起自己的操作称为让步(yeild)

I/O消耗型:进程大部分时间用来提交I/O请求或是等待I/O请求。
处理器消耗型:把时间大多用在执行代码上,除非被抢占,通常会一直执行。

调度策略需要在两个矛盾的目标中寻找平衡 : 进程响应时间迅速和最大系统利用率/响应时间短,吞吐量高。

Linux采用两种不同的优先级范围

第一种:使用nice值,-20~+19,默认为0,值越大优先级越低。Linux系统中nice代表时间片的比例。第二种:实时优先级,其值可配置,默认是0~99。和nice值相反,这个值越高,优先级越高。任何实时进程的优先级都高于普通的进程。实时优先级和nice优先级处于互不相交的两个范畴。

O(1) 调度

从就绪队列选择下一个执行进程的时间为常数,在几十颗CPU时表现良好,但在几百颗CPU时,表现不佳

数据结构

struct prio_array_t{    int nr_active;//本进程组中的进程数    struct list_head queue[MAX_PRIO];//优先级为索引的hash表,140级    bitmap[BITMAP_SIZE];/*加速hash表访问的位图*/}
  • prio_array_t中nr_actice表示本组进程组中的进程数
  • queue则相当于优先级队列,queue的索引就是优先级,同优先级的进程位于queue[i]所指向的链表中
  • Bitmap用于加速queue的访问,可用于迅速查找第一个不为空的链表置。
    其他相关变量
    timestamp_last_tick,pre_cpu_load,记录CPU当前状态,可用于负载平衡。

prio_array_t* active, *expired, arrays[2];
arrays二元数组是两类就绪队列的容器。
每个CPU维护自己的就绪队列

主要思路

active,expired分别指向一个队列
一般情况下active中的进程一旦用完自己的时间片,就被转移到expired中,并设置好新的初始时间片。
当active为空时,表示当前所有进程时间片都消耗完了,此时active和expired进行一次对调,重新开始下一轮的时间片递减过程。
而如果进程是交互式进程,调度器会让其保持在active队列上,以提高响应速度,但这个措施不能使其他就绪队列等待时间过长,当expired队列中进程已经等待足够长(时间限制)的时间,即使交互进程也要转移,排空active。

O(1)

选择最佳候选进程只需要在active中选择优先级最高的链表中的第一项,这个操作不超过140次,为O(1).

time_slice时间片的计算

值在变化,代表进程运行时间片剩余大小

  • 基准值与静态优先级(2.4的nice值)相关。
    • MIN_TIMESLICE + ((MAX_TIMESLICE - MIN_TIMESLICE) * (MAX_PRIO-1 - (p)->static_prio) / (MAX_USER_PRIO-1))
  • time_slice时间片的变化:
    • 创建时和父进程平分时间,运行过程中递减,归0后,按static_prio重新赋予基准值。
    • 进程的退出时(sched_exit()),根据first_time_slice判断自己是否重新分配过时间片,如果从未重新分配,将自己的剩余时间片还给父进程(不超过MAX_SLICE),如果已经用完就没有必要返还了。

时间片的递减和重置在时钟中断sched_tick()中进行。

优先级的计算分散到各个时候:

由 effect_prio() 函数完成
非实时进程的优先级取决于静态优先级(static_prio)和进程的sleep_avg 值两个因素。
实时进程的优先级实际上是在 setscheduler() 中设置的,设置后就不再改变

非实时进程的动态优先级为

  • 【进程睡眠的bonus – 静态优先级】限制在MAX_RT_PRIO和MAX_PRIO sleep_avg 范围是 0~MAX_SLEEP_AVG
  • 经过以下公式转MAX_BONUS/2~MAX_BONUS/2 之间
    (NS_TO_JIFFIES((p)->sleep_avg) *MAX_BONUS / MAX_SLEEP_AVG) - MAX_BONUS/2

优先级计算时机:不再集中在调度器选择候选进程的时候进行了,只要进程状态发生改变,核心就有可能计算并设置进程的动态优先级

完全公平调度 CFS: 普通进程的调度类

出发点基于一个简单的理念
进程的调度效果应该如同系统具备一个理想的完美多任务处理器。系统中有n个可运行进程,每个进程应该能获得1/n的处理器时间,就好像n个进程同时执行,每个进程拥有处理器1/n的处理能力。CFS为完美调度中的无限小调度周期的近似值设立了目标,称为“目标延迟”
越小的调度周期交互性越好,也更接近完美多任务。但是进程切换代价高,系统吞吐量低。每个进程获取的时间片有个底线,称为最小粒度
CFS做法是:允许每个进程运行一段时间,循环轮转,选择运行最少的进程作为下一个运行进程。CFS在所有可运行进程总数基础上计算一个进程应该运行多久,而不是依靠nice值来计算时间片。CFS中nice值作为进程获得的处理器运行比的权重:越高的nice,获得更低的CPU使用权重;越低的nice,获得更高的CPU使用权重。

  • 时间记账

使用sched_entity结构体进行记账;vruntime存放进程的虚拟执行时间(ns,根据进程总数标准化过),记录程序到底运行了多长时间以及还需要运行多长时间。

  • 进程选择

使用红黑树组织可运行进程队列,以vruntime为键,选择最小vrumtime的进程。
添加进程,删除进程,就是红黑树的插入,删除操作,不过这里缓存了最左叶子节点,提高选择最小vrumtime的进程的速度。

  • 调度器入口

schedule()函数调用pick_next_task函数,以优先级为序,从高到低,依次检查每一个调度类,从最高优先级的调度类中,选择最高优先级的进程。

  • 睡眠和唤醒

睡眠:进程标记为休眠状态,从可执行红黑树中移出,放入等待队列,调用schedule选择和执行一个其他进程。
唤醒:进程杯设置为可执行状态,从等待队列移到可执行红黑树中。

上下文切换

从一个可执行进程切换到另一个可执行进程。

<asm/mmu-contex.h> switch_mm()把虚拟内存从行一个进程切换到新进程
<asm/system.h> switch_to()从上一个处理器状态切换到新处理器状态,保存恢复栈寄存器信息

调用schedule的时机

  • 某个进程应该被抢占,scheduer_tick设置need_resched;
  • 一个优先级高的进程进入可执行状态,try_wake_up,设置need_resched;
  • 从内核返回用户空间或从中断返回,内核检查need_resched.如果已经设置,内核会在继续执行之前调用调度程序。

抢占

(被抢占不一定是睡眠,不一定是在进程上下文)
用户抢占:从系统调用返回用户空间;从中断处理程序返回用户空间
内核抢占:内核中的任务显示调用schedule;内核中的任务睡眠;中断处理程序正在执行,返回到内核空间之前;内核代码具有可抢占性的时候(SMP中,没有锁,就是可以抢占的标志)

实时调度

SCHED_FIFO:不使用时间片,先入先出;有更高优先级的SCHED_FIFO,SCHED_RR进程到来时发生抢占
SCHED_RR:带有时间片的,实时轮流调度。时间片耗尽时,同以优先级的其他进程被轮流调度。

参考

《Linux内核设计与实现》
https://oakbytes.wordpress.com/linux-scheduler/

3 0
原创粉丝点击