磁盘调度算法模拟)
一、实验名称
实验(四)磁盘调度算法模拟
二、实验目的
掌握磁盘调度的策略及原理,理解和掌握磁盘调度算法——先来先服务算法(FCFS)、最短寻道时间优先算法(SSTF)、电梯扫描算法(SCAN),比较各算法相关特性。
三、实验内容和要求
(1)模拟先来先服务法(FCFS),最短寻道时间优先法(SSTF),电梯扫描算法(SCAN)三种磁盘调度算法;
(2)输入两组请求访问磁道序列,分别输出每种调度算法的磁头移动轨迹和移动的总磁道数。
四、实验设计
1、程序流程图:
2、实验环境:jdk1.8
3、代码与注释
package com.symc.dsaa.os.test4;
import java.util.Scanner;
/**
* @Author: 凤文 沈阳医学院2019级医学信息工程 0213
* @CreateTime: 2021/12/05 17:28
* @Description: 磁盘调度算法实验
*/
public class Test {
public static void main(String[] args) {
int[] tracks = getRandomArr();
System.out.println("随机生成了10个磁道号(0-200)");
for (int track : tracks) {
System.out.print(track + "\t");
}
System.out.println();
Scanner sc = new Scanner(System.in);
System.out.println("请设置一个起始的磁道号(0-200):");
int startTrack;
while (true) {
startTrack = sc.nextInt();
if (startTrack < 0 || startTrack > 200) {
System.out.println("您输入的磁道号越界,请重新设置(0-200):");
continue;
}
break;
}
do {
printMenu();
} while (choose(sc, tracks, startTrack));
}
public static void printMenu() {
System.out.println("##########################");
System.out.println("请选择您想用的算法:");
System.out.println("1.先来先服务");
System.out.println("2.最短寻道时间优先");
System.out.println("3.扫描算法");
System.out.println("0.退出本次实验");
System.out.println("##########################");
}
/**
* 随机生成一个0-200的序列来代替磁道号
* 用随机的目的是方便快速测试
* @return
*/
public static int[] getRandomArr() {
int[] arr = new int[10];
for (int i = 0; i < arr.length; i++) {
arr[i] = (int) (Math.random() * 200);
}
return arr;
}
public static boolean choose(Scanner sc, int[] tracks, int startTrack) {
int option = sc.nextInt();
switch (option) {
case 0:
return false;
case 1:
printFCFS(tracks, startTrack);
break;
case 2:
printSSTF(tracks, startTrack);
break;
case 3:
printSCAN(tracks, startTrack, 1);
break;
default:
System.out.println("输入错误!请重新输入!");
choose(sc, tracks, startTrack);
break;
}
return true;
}
public static void printFCFS(int[] tracks, int startTrack) {
int[] sequence = DiskScheduling.FCFS(tracks);
int[] move = DiskScheduling.move(sequence, startTrack);
print(sequence, move, startTrack);
}
public static void printSSTF(int[] tracks, int startTrack) {
int[] sequence = DiskScheduling.SSTF(tracks, startTrack);
int[] move = DiskScheduling.move(sequence, startTrack);
print(sequence, move, startTrack);
}
public static void printSCAN(int[] tracks, int startTrack, int direction) {
int[] sequence = DiskScheduling.SSTF(tracks, startTrack);
int[] move = DiskScheduling.move(sequence, startTrack);
if (direction > 0) {
System.out.println("向磁道号增加方向访问");
print(sequence, move, direction);
} else {
System.out.println("向磁道号减少方向访问");
}
}
public static void print(int[] sequence, int[] move, int startTrack) {
System.out.println("从" + startTrack + "号磁道开始");
System.out.println("被访问的下一个磁道号\t移动距离(磁道数)");
int averMove = 0;
for (int i = 0; i < sequence.length; i++) {
System.out.println(sequence[i] + "\t\t\t\t\t" + move[i]);
averMove += move[i];
}
System.out.println("平均寻道长度:" + (double) averMove / sequence.length);
}
}
package com.symc.dsaa.os.test4;
import java.util.Arrays;
import java.util.Stack;
/**
* @Author: 凤文 沈阳医学院2019级医学信息工程 0213
* @CreateTime: 2021/12/05 17:28
* @Description:
*/
public class DiskScheduling {
/**
* 先来先服务
* @param tracks 传入一组乱序的磁盘轨道序列
* @return 返回一组FCFS的磁头移动轨迹的序列
*/
public static int[] FCFS(int[] tracks) {
int len = tracks.length;
int[] sequence = new int[len];
for (int i = 0; i < len; i++) {
sequence[i] = tracks[i];
}
return sequence;
}
/**
* 最短寻道时间优先
* @param tracks 传入一组乱序的磁盘轨道序列
* @param startTrack 需要知道磁头的起始位置
* @return 返回一组SSTF的磁头移动轨迹的序列
*/
public static int[] SSTF(int[] tracks, int startTrack) {
Stack<Integer> stack1 = new Stack<Integer>();
Stack<Integer> stack2 = new Stack<Integer>();
Stack<Integer> stack2Temp = new Stack<Integer>();
Arrays.stream(tracks).sorted().forEach((track) -> {
if (track <= startTrack) {
stack1.push(track);
} else if (track > startTrack) {
stack2Temp.push(track);
}
});
while (!stack2Temp.isEmpty()) {
stack2.push(stack2Temp.pop());
}
int head = startTrack;
int[] sequence = new int[tracks.length];
for (int i = 0; i < sequence.length; i++) {
if (stack1.isEmpty()) {
head = stack2.pop();
sequence[i] = head;
continue;
}
if (stack2.isEmpty()) {
head = stack1.pop();
sequence[i] = head;
continue;
}
if (stack2.peek() - head < head - stack1.peek()) {
head = stack2.pop();
sequence[i] = head;
} else {
head = stack1.pop();
sequence[i] = head;
}
}
return sequence;
}
/**
* 电梯扫描
* @param tracks 传入一组乱序的磁盘轨道序列
* @param startTrack 需要知道磁头的起始位置
* @param direction 扫描算法还需要知道磁头起始的移动方向
* @return 返回一组SCAN的磁头移动轨迹的序列
*/
public static int[] SCAN(int[] tracks, int startTrack, int direction) {
int len = tracks.length;
int[] sequence = new int[len];
for (int i = 0; i < len; i++) {
sequence[i] = tracks[i];
}
Arrays.sort(sequence);
int num =
(int) Arrays.stream(sequence).filter(track -> track < startTrack).count();
int[] reSequence = new int[num];
for (int i = 0; i < sequence.length; i++) {
if (sequence[i]<startTrack){
reSequence[i]=sequence[i];
}else {
sequence[i-num]=sequence[i];
}
}
for (int i = num-1; i >= 0; i--) {
sequence[len-i-1] = reSequence[i];
}
return sequence;
}
/**
* 移动序列确定好之后,移动距离的算法都一致,都使用move即可
* @param sequence
* @param startTrack
* @return 返回一个存放移动距离的数组
*/
public static int[] move(int[] sequence, int startTrack) {
int len = sequence.length;
int[] trackValues = new int[len];
int trackNum;
for (int i = 0; i < len; i++) {
if (i == 0) {
trackNum = startTrack - sequence[i];
} else {
trackNum = sequence[i] - sequence[i - 1];
}
trackValues[i] = trackNum > 0 ? trackNum : -trackNum;
}
return trackValues;
}
}
五、实验步骤及实验结果
1、实验内容
2、实验结果
选择先来先服务结果如下:
最短寻道时间优先:
扫描算法:
结束本次实验:
为了避免偶然性,我们可以进行多次实验,每次运行后都将这个序列按照这三种算法测试。
六、实验中出现的问题及解决方法
问题1:SSTF算法的实现
这个算法需要我们比较当前磁头的位置与哪个序列最近,在实现的时候,无论是使用数组还是ArrayList,发现它实现思路特别麻烦。于是,我想了新的思路,使用双栈。首先,肯定要把磁道序列先排序,就按从小到大升序,然后把比磁头位置大的磁道号全放到一个栈,栈内顺序就是大的放最下面,比磁头位置小的磁道号全放到另一个栈,栈内顺序就是小的放最下面,假如有一个排序好的序列:5,10,12,17,20,40,60,磁头位置在15,结构如图:
我们只需要比较磁头与两个栈顶的磁道哪个差值最小,那这个栈顶就是下一个访问的磁道,比如这里,12跟15相差3,17跟15相差2,那下一个序列就是17。同时要注意这个栈顶17要取出,然后磁头位置由15变成了17,下一轮继续这么比较。代码实现如下:
public static int[] SSTF(int[] tracks, int startTrack) {
Stack<Integer> stack1 = new Stack<Integer>();
Stack<Integer> stack2 = new Stack<Integer>();
Stack<Integer> stack2Temp = new Stack<Integer>();
Arrays.stream(tracks).sorted().forEach((track) -> { //我这里使用了stream去排序
if (track <= startTrack) { //排好序后放到对应的栈内
stack1.push(track);
} else if (track > startTrack) {
stack2Temp.push(track);
}
});
while (!stack2Temp.isEmpty()) {
stack2.push(stack2Temp.pop());
}
int head = startTrack;
int[] sequence = new int[tracks.length];
for (int i = 0; i < sequence.length; i++) {
if (stack1.isEmpty()) {//当比较前,如果发现有一个栈是空的,那就不用比较了
head = stack2.pop();//说明只能访问另一个栈,磁头也要指向对应的位置
sequence[i] = head;
continue;
}
if (stack2.isEmpty()) {
head = stack1.pop();
sequence[i] = head;
continue;
}
if (stack2.peek() - head < head - stack1.peek()) { //比较出下一个访问的磁道
head = stack2.pop();
sequence[i] = head;
} else {
head = stack1.pop();
sequence[i] = head;
}
}
return sequence;
}
七、结论
FIFS:其平均寻道时间较大,故只适合请求磁盘I/O进程数目较少的场合
SSTF:保证每次的寻道时间最短,但不能保证平均寻道时间最短
可防止低优先级进程出现饥饿现象