- 前言
- 一、调度算法
- 1.FCFS(First Come, First Served)
- 2.SJF(Shortest Job First)
- 3.RR(Round Robin)
- 4.折中方案
- 二、Schedule()
- 1.counter(时间片)
- 2.counter(优先级)
- 总结
提示:以下是本篇文章正文内容
一、调度算法设计调度算法的目的:
1.面对用户,目的是让用户满意
2.面对进程:CPU调度的目标是进程满意
进程满意:
尽快结束任务:周转时间(从任务进入到任务结束)短
响应用户操作快:响应时间(从操作发生到响应)短
系统内耗时间少:吞吐量(完成的任务量)
总原则:系统专注于任务执行,又能合理调配任务
1.FCFS(First Come, First Served)
先来先服务算法(FCFS——First Come First Serve):
按照进程就绪的先后顺序使用CPU。 特点:非抢占,公平,实现简单,长进程后面的短进程需要等很长时间,不利于用户体验
三个进程的处理时间分别为12,3,3,分两种进程到达顺序讨论 优点:调度算法简单
缺点: 1.平均等待时间波动较大,短的进程可能排在长的进程后面得到执行
2.I/O资源和CPU资源利用率较低,CPU密集型进程会导致I/O设备闲置,I/O密集型进程会导致CPU闲置
2.SJF(Shortest Job First)最短作业优先(SJF——Shortest Job First): 具有最短完成时间的进程优先执行,非抢占
如果调度结果是 P1,P2,…,Pn,则平均周转时间为: P1 + P2 + P3 + … + Pn = ∑(n + 1 - i) * Pi P1的周转时间是 P1 P2的周转时间是 P1 + P2 …… Pn的周转时间中含有 n*P1 + (n - 1) * P2 + … P1 计算的次数最多,需要把最短的任务放在前边
所以,这种方式平均周转时间最小。
3.RR(Round Robin)时间片轮转调度算法(Round Robin——RR): 每个进程被分配一个时间片,允许该进程在该时间段运行,如果在时间片结束时该进程还在运行,则剥夺CPU并分配给另一个进程,如果该进程在时间片结RR束前阻塞或结束,则CPU立即进行切换。
特点:公平;有利于交互式计算,响应时间快;由于进程切换,时间片轮转算法要花费较高的开销;对进程表中不同进程的大小差异较大的有利,而对进程都是相同大小的不利。
时间片设计需要避免的两点:
1.时间片太大,等待的时间过长,极限的情况下退化成为FCFS算法
2.时间片太小,反应过于迅速,产生大量的上下文切换,会影响到系统的吞吐量
4.折中方案我们可以设置优先级,设置前台任务和后台任务,前台任务优先级高,后台任务优先级低,定义前台任务队列和后台任务队列,只有前台任务没有了才调度后台任务
但是,如果一直有前台任务,后台任务一直得不到执行(优先级低的任务一直得不到执行)
所以,任务的优先级要动态调整 一般后台任务 耗时比较长,一旦后台任务转到前台执行,可能耗时很长,一直不释放CPU,前台任务的响应时间又没法保证,前后台任务都要设置时间片,后台任务转到前台,执行一段,要释放CPU,让其他任务执行
折中方案:短任务优先(减少周转时间)、以 轮转调度为核心,要设置优先级
二、Schedule()schedule() 的目的是找到下一个任务 next,切换到下一个任务
源码:
// 任务0是一个闲置(idle)任务,只有当其他任务没有运行时才调用它
// 它不能被杀死,也不能睡眠。任务0中的状态信息“state”是从来不用的
void schedule(void)
{
int i,next,c;
struct task_struct ** p; // 任务结构体的指针的指针
/* check alarm, wake up any interruptible tasks that have got a signal */
// 检查 alarm(进程的报警定时值),唤醒任何已得到信号的可中断任务
把p初始化为指向最后一个进程的地址的指针,逆向扫描所有进程,并跳过空指针
for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)
if (*p) { //*p 指向当前进程的指针
//jiffies是系统从开机算起的滴答数(每10ms/滴答)
//判断定时器是否到期,如果到期需要在信号位图中置SIGALARM位,并且将定时器清0
if ((*p)->alarm && (*p)->alarm signal |= (1signal & ~(_BLOCKABLE & (*p)->blocked)) &&
(*p)->state==TASK_INTERRUPTIBLE)
(*p)->state=TASK_RUNNING; // 置为就绪可以执行状态
}
/* this is the scheduler proper: */
// 进程的调度
// 检查就绪的任务,判断下一个运行的任务。
while (1) {
c = -1; //从最后一个任务开始遍历任务数组
next = 0;
i = NR_TASKS;
p = &task[NR_TASKS];
//对就绪任务按照时间片进行排序
//比较每个就绪状态任务的counter值(任务运行时间的递减滴答计数)
//哪一个值大,运行的时间还不长,next就指向哪一个任务号
while (--i) {
if (!*--p)
continue;
if ((*p)->state == TASK_RUNNING && (*p)->counter > c) //counter这里是时间片
//判断是就绪态,并且 counter>-1,就给c和next赋值,遍历找到最大的counter
c = (*p)->counter, next = i;
}
//如果比较得出有counter值不等于0的结果,或者系统中没有一个可运行的任务存在
//则跳出循环,执行任务切换操作
if (c) break;
//如果所有任务的时间片都为0,那么重新计算各个任务的时间片,计算原则是根据优先级进行计算
//更新每个任务的counter值,然后再回到开始处比较
//计算方法:counter = counter/2 + priority
for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)
if (*p)
(*p)->counter = ((*p)->counter >> 1) + //counter这里代表优先级
(*p)->priority;
}
//把当前任务指针current指向任务号为next的任务,并切换到该任务中运行
//若系统中没有其他可运行的任务,则next=0,所以调度去指向空闲任务
switch_to(next); // 任务切换,切换到任务号为next的任务,并允许
}
1.counter(时间片)
counter是典型的时间片, 所以是轮转调度, 保证了响应 do_timer 中 counter 减到0,就schedule
counter代表的优先级可以动态调整 阻塞的进程再就绪以后优先级高于非阻塞进程
提示:这里对文章进行总结:
调度函数的核心处理部分:这是根据进程的时间片和优先权调度机制,来选择随后要执行的任务。
它首先循环检查任务数组中的所有任务,根据每个就绪态任务剩余执行时间的值counter,选取该值最大的一个任务,并利用switch _(0)函数切换到该任务。
若所有就绪态任务的该值都等于零,表示此刻所有任务的时间片都已经运行完,于是就根据任务的优先权值pitority,重置每个任务的运行时间片值counter,再重新执行循环检查所有任务的执行时间片值。