在操作系统中,CPU调度算法决定了如何将有限的CPU时间分配给等待执行的进程。调度算法对系统性能、响应时间以及整体用户体验有着直接的影响。在众多的调度算法中,短作业优先(Shortest Job First, SJF) 是一种以最小化平均等待时间为目标的经典算法。SJF算法可以分为非抢占式和抢占式两种形式,本文将详细讨论它们的特点、优点以及缺点。
什么是短作业优先算法?
短作业优先(SJF)是一种调度策略,它选择执行时间最短的进程优先进行调度。这种算法的核心理念是:短的任务先执行,从而减少整体的平均等待时间。
根据SJF的行为方式,调度可以分为两种类型:
-
非抢占式短作业优先
-
抢占式短作业优先(也称为最短剩余时间优先)
非抢占式短作业优先算法
在非抢占式短作业优先(Non-Preemptive SJF)中,一旦CPU分配给某个进程,直到该进程完成,CPU才会被释放。调度器每次选择就绪队列中预计执行时间最短的进程来执行。
工作机制
-
当一个新进程进入就绪队列时,调度器将查看所有进程的预计执行时间。
-
选择预计执行时间最短的进程,并将CPU分配给它。
-
一旦一个进程开始执行,除非该进程自己完成,否则不会被其他进程中断。
优点
-
减少平均等待时间:由于优先执行短任务,较短的进程可以快速完成,从而降低整体的等待时间。
-
实现简单:非抢占式调度机制相对简单,不需要额外的进程切换,适合某些特定的场景。
缺点
-
长任务可能被长期推迟:如果有源源不断的新短任务进入就绪队列,那么长任务可能会长时间得不到执行,甚至可能导致饥饿(starvation)。
-
无法处理突发事件:一旦分配CPU,不会有中断机制处理紧急任务。
举例
假设有四个进程P1、P2、P3、P4,预计执行时间分别为6、8、7、3个时间单位,它们都在时间点0进入系统。
进程 | 执行时间(单位) |
---|---|
P1 | 6 |
P2 | 8 |
P3 | 7 |
P4 | 3 |
-
调度顺序为:P4 -> P1 -> P3 -> P2
-
平均等待时间为:(0 + 3 + 9 + 16) / 4 = 7 个时间单位
抢占式短作业优先算法(最短剩余时间优先)
在抢占式短作业优先(Preemptive SJF)中,又称为最短剩余时间优先(Shortest Remaining Time First, SRTF),每当一个新进程到达时,调度器会比较新进程的执行时间与当前进程的剩余执行时间,如果新进程的执行时间更短,则会抢占当前的进程。
工作机制
-
初始化时,将所有进程的剩余时间设为其初始的执行时间。
-
当前时间为0,找到最早到达且剩余执行时间最短的进程执行。
-
每当有新进程到达时,调度器会重新检查所有未完成进程的剩余时间,选择剩余时间最短的进程执行。如果新进程的剩余时间更短,则会抢占当前的进程。
-
当一个进程的剩余时间变为0时,该进程完成,记录其完成时间。
-
重复上述步骤,直到所有进程都完成。
优点
-
提高系统响应速度:对于短任务可以尽快完成,提高了系统的响应能力。
-
较低的平均等待时间:抢占式SJF能更好地降低平均等待时间,因为它随时确保执行剩余时间最短的任务。
缺点
-
高开销:频繁的进程切换可能导致上下文切换的开销增加,影响整体性能。
-
饥饿问题:与非抢占式SJF类似,长任务在有大量短任务不断进入的情况下可能长时间得不到执行。
举例
假设有四个进程P1、P2、P3、P4,预计执行时间分别为6、8、7、3个时间单位。它们进入系统的时间点为:
进程 | 到达时间 | 执行时间(单位) |
P1 | 0 | 6 |
P2 | 1 | 8 |
P3 | 2 | 7 |
P4 | 3 | 3 |
根据SRTF调度:
-
时间0到1:P1开始执行,剩余时间为5。
-
时间1到3:P2到达,但P1的剩余时间更短,P1继续执行。
-
时间3:P4到达,预计执行时间为3,P1被抢占,P4开始执行。
-
时间3到6:P4执行完成。
-
时间6到7:P1继续执行,剩余时间变为4。
-
时间7到10:P3到达并执行,剩余时间为4。
-
时间10以后继续进行剩余进程的调度。
比较与应用场景
-
非抢占式SJF适用于那些需要减少调度开销、不要求高实时性的场景,比如批处理系统。
-
抢占式SJF适合对响应时间要求较高的系统,比如交互式系统和某些实时操作系统。
结论
短作业优先(SJF)是一种有效的调度算法,尤其适合在任务执行时间已知的情况下,它能够最小化平均等待时间。然而,无论是抢占式还是非抢占式SJF,都会面临饥饿问题。为了解决这个问题,操作系统中常常引入老化(Aging)机制来确保长时间未执行的进程能得到执行机会。
对于理解操作系统中的进程调度,SJF是一个重要的起点。它展示了调度算法如何通过对任务长度的不同优先级分配来优化系统性能。希望这篇文章能帮助你更好地理解短作业优先调度算法的工作机制与应用场景。
C语言实现
以下是两种短作业优先调度算法的C语言实现。
非抢占式短作业优先(Non-Preemptive SJF)
#include <stdio.h>
void sjf_non_preemptive(int n, int burst_times[]) {
int wait_times[n], turnaround_times[n];
int total_wait = 0, total_turnaround = 0;
int completed[n];
// 初始化
for (int i = 0; i < n; i++) {
completed[i] = 0;
}
for (int i = 0; i < n; i++) {
int min_index = -1;
for (int j = 0; j < n; j++) {
if (!completed[j] && (min_index == -1 || burst_times[j] < burst_times[min_index])) {
min_index = j;
}
}
completed[min_index] = 1;
wait_times[min_index] = total_wait;
total_wait += burst_times[min_index];
turnaround_times[min_index] = wait_times[min_index] + burst_times[min_index];
total_turnaround += turnaround_times[min_index];
}
printf("Process\tBurst Time\tWait Time\tTurnaround Time\n");
for (int i = 0; i < n; i++) {
printf("P%d\t\t%d\t\t%d\t\t%d\n", i + 1, burst_times[i], wait_times[i], turnaround_times[i]);
}
printf("Average Wait Time: %.2f\n", (float)total_wait / n);
printf("Average Turnaround Time: %.2f\n", (float)total_turnaround / n);
}
int main() {
int burst_times[] = {6, 8, 7, 3};
int n = sizeof(burst_times) / sizeof(burst_times[0]);
sjf_non_preemptive(n, burst_times);
return 0;
}
抢占式短作业优先(Preemptive SJF)
#include <stdio.h>
void sjf_preemptive(int n, int arrival_times[], int burst_times[]) {
int remaining_times[n], wait_times[n], turnaround_times[n];
int total_wait = 0, total_turnaround = 0;
int completed = 0, current_time = 0, min_index;
// 初始化
for (int i = 0; i < n; i++) {
remaining_times[i] = burst_times[i];
}
while (completed != n) {
min_index = -1;
for (int i = 0; i < n; i++) {
if (arrival_times[i] <= current_time && remaining_times[i] > 0) {
if (min_index == -1 || remaining_times[i] < remaining_times[min_index]) {
min_index = i;
}
}
}
if (min_index == -1) {
current_time++;
continue;
}
remaining_times[min_index]--;
current_time++;
if (remaining_times[min_index] == 0) {
completed++;
turnaround_times[min_index] = current_time - arrival_times[min_index];
wait_times[min_index] = turnaround_times[min_index] - burst_times[min_index];
total_wait += wait_times[min_index];
total_turnaround += turnaround_times[min_index];
}
}
printf("Process\tArrival Time\tBurst Time\tWait Time\tTurnaround Time\n");
for (int i = 0; i < n; i++) {
printf("P%d\t\t%d\t\t%d\t\t%d\t\t%d\n", i + 1, arrival_times[i], burst_times[i], wait_times[i], turnaround_times[i]);
}
printf("Average Wait Time: %.2f\n", (float)total_wait / n);
printf("Average Turnaround Time: %.2f\n", (float)total_turnaround / n);
}
int main() {
int arrival_times[] = {0, 1, 2, 3};
int burst_times[] = {6, 8, 7, 3};
int n = sizeof(burst_times) / sizeof(burst_times[0]);
sjf_preemptive(n, arrival_times, burst_times);
return 0;
}
这两个实现展示了如何通过C语言实现非抢占式和抢占式短作业优先调度算法,帮助理解这两种调度方式的工作原理及其在代码中的实现。