CPU调度(进程调度)策略

本文介绍了CPU调度在操作系统中的重要性,详细讨论了调度算法的设计目标,包括周转时间、响应时间和吞吐量。探讨了FCFS、SJF、轮转调度(RR)和优先级调度四种常见算法,并分析了它们的优缺点。以Linux 0.11的schedule()函数为例,解释了counter在调度中的双重角色,确保响应时间并近似实现短作业优先。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

CPU调度

CPU调度是多道程序操作系统的基础。通过在进程之间切换CPU,操作系统可以提高计算机的吞吐率。

每当CPU空闲时,操作系统必须按照一定的策略从就绪队列当中选择一个进程来执行。
调度的对象:进程或线程。其方式与原则是一样的。所以经常以进程来说明。
那么说CPU调度<=>进程调度

调度算法的设计目标

面对客户:调度算法的设计目标应该是用户满意。
面对进程:CPU调度的目标应该是进程满意。

而让进程满意的关键就是时间,我们要做的事:

  • 尽快结束任务:周转时间(从任务进入到任务 )短
  • 用户操作尽快响应:响应时间(从操作发生到响应)短
  • 系统内耗时间少:吞吐量(完成的任务量)大

一个系统中存在多个任务,会存在调度的矛盾。

  • 吞吐量和响应时间有矛盾
    响应时间小=>切换次数多=>系统内耗大=>吞吐量小

  • 前台任务和后台任务的关注点不同
    前台任务关注响应时间,后台任务关注周转时间

  • IO约束型程序和CPU约束型程序各有特点
    CPU约束型程序以计算为主,CPU区间会较多,还会有少量长的CPU区间。如gcc。
    I/O约束型程序以I/O为主,但配合I/O处理会有大量短的CPU区间。如word。

所以调度算法的设计需要折中,综合。

各种调度算法

这里写图片描述

(1)First Come,First Served(FCFS)先来先服务

这里写图片描述

平均周转时间:
(10+39+42+49+61)/5=40.2

(2)SJF短作业优先

这里写图片描述

平均周转时间:
(10+13+42+49+61)/5=35

短作业优先:如果存在i < j,而pi>pj,交换pi,pj。

短作业优先的周转时间是最小的。
如果调度结果为p1,p2,…,pn,则周转时间为:
p1+(p1+p2)+(p1+p2+p3)+…=∑(n+1-i)pi=n*p1+(n-1)*p2+…

p1乘的最大,如果按短作业优先来的话,调度的结果是p1最小,p2次之…
所以短作业优先的周转时间是最小的。

(3)round robin,RR轮转调度

按短作业优先:

这里写图片描述

但是此时就有一个问题了,P2用户的操作,要等前面所有的任务都执行完了,才会响应。这样响应时间就不能保证了。

所以有了轮转调度,按时间片来轮转调度。

这里写图片描述

时间片T=10
假设有n个,那么这里最长的响应时间为nT,这样响应时间就可以保证了。

对于轮转调度,时间片大,响应时间太长;时间片小,吞吐量小。折中的话,时间片10~100ms,切换时间0.1~1ms(1%)。

(4)优先级调度

系统中存在多种任务,比如Word关心响应时间,gcc关心周转时间,这两类任务同时存在怎么办呢。
直观的想法是:定义前台任务和后台任务队列,前台RR,后台SJF,在前台任务没有时才调度后台任务。

这里写图片描述

但是这样的绝对优先级调度问题很大啊,如果前台一直有任务,后台就一直不能被调度了。所以后台任务优先级必须动态升高。

但是后台任务(用SJF调度)一旦执行,前台的响应时间又不能保证了(后台任务 一般是CPU区间很长的任务,CPU在很长一段时间都不会被让出)。

所以说后台任务也要有时间片,如果前后台都单纯的用时间片,这又退化到了RR,后台任务的SJF又该怎么体现呢。

所以说我们的调度算法既要有轮转调度为核心,又要在轮转调度的基础上增加一些优先级,而这个优先级又要考虑到短作业优先,又要考虑到前台的任务先做。

CPU调度实例–schedule函数

Linux 0.11中的schedule()

void Schedule(void)
{
   ...
   while(1)
   {
      c=-1;
      next=0;
      i=NR_TASKS;
      p=&task[NR_TASKS];//把数组中的最后一个给p
      while(--i)
      {     
         if(!*--p)
         {
             continue;
         }
         //TASK_RUNNING代表就绪
         if((*p)->state==TASK_RUNNING && (*p)->counter>c)
         {
            c=(*p)->counter;
            next=i;
         }
      }//找到最大的counter,counter本身作为时间片,但又作为优先级
      if(c) break;//找到就跳出

      //如果就绪态的进程counter都用完了,那么执行下面的代码
      for(p=&LAST_TASK;p>&FIRST_TASK;--p)
      {
         if(*p)
         {
            //(*p)->priority代表counter的初值
            (*p)->counter=((*p)->counter>>1)+(*p)->priority;
         }
      }
   }
   switch_to(next);
}
for(p=&LAST_TASK;p>&FIRST_TASK;--p)
{
    if(*p)
    {
      (*p)->counter=((*p)->counter>>1)+(*p)->priority;//①
    }
 }

对于这段代码,如果是就绪态的进程进来,此时就绪进程的counter都为0了,执行①处代码,相当于又重置回复了进程的counter。而如果是阻塞态的进程进来,当前counter/2后,又加上初值肯定比之前大,counter又作为优先级,一旦进程由阻塞态变成就绪态,那么它的优先级就会很高了。

下面具体的说一下counter的作用:

counter的作用:时间片

counter是典型的时间片,完成轮转调度,保证响应时间

void do_timer(...) //kernel/sched.c
{
   if(--current->counter>0) return;
   current->counter=0;
   schedule();//时间片用完了,开始切换
}

_timer_interrupt: //kernel/system_call.s
 ...
 call _do_timer

void sched_init(void)
{
   set_intr_gate(0x20,&timer_interrupt);
}

counter的另一个作用:优先级

//找到counter最大的任务调度,counter代表优先级
while(--i)
{     
   if(!*--p)
   {
      continue;
   }
   if((*p)->state==TASK_RUNNING && (*p)->counter>c)
   {
      c=(*p)->counter;
      next=i;
   }
}
//counter代表的优先级进行动态调整
for(p=&LAST_TASK;p>&FIRST_TASK;--p)
{
    if(*p)
    {
      (*p)->counter=((*p)->counter>>1)+(*p)->priority;//①
    }
 }

在I/O的进程的那些优先级会升高,而且I/O时间越长的,优先级越高,因为每次c=0了,都要执行counter/2+初值。
经过I/O进程的counter一定会比只执行CPU进程的counter大,所以I/O约束型任务的优先级就上去了,而I/O约束型正是前台进程的特征。即认为经过了I/O就具有前台进程的特征,那么优先级就比只执行CPU的后台进程高。在阻塞队列待得越久,优先级就越高。

综上整理counter的作用

  • counter保证了响应时间的界

  • 经过IO后,counter就会变大,IO时间越长,counter越大,照顾了IO进程,变相的照顾了前台进程

  • 后台进程一直按照counter轮转,近似SJF调度

  • 每个进程只用维护一个counter变量,简单、高效
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值