操作系统:磁盘调度

操作系统:磁盘调度的分析和实现


一、目的

磁盘是高速、大容量、旋转型、可直接存取的存储设备。它作为计算机系统的辅助存储器,担负着繁重的输入输出工作,在现代计算机系统中往往同时会有若干个要求访问磁盘的输入输出要求。系统可采用一种策略,尽可能按最佳次序执行访问磁盘的请求。由于磁盘访问时间主要受寻道时间T的影响,为此需要采用合适的寻道算法,以降低寻道时间。本实验要求学生模拟设计一个磁盘调度程序,观察调度程序的动态运行过程。通过实验让学生理解和掌握磁盘调度的职能。

二、内容

模拟扫描调度算法,对磁盘进行移臂操作。

三、要求

实现SCAN算法;实现CSCAN算法;实现SCAN算法的改进,解决磁臂粘着问题;实现图形化演示。

四、要求

1、假设磁盘只有一个盘面,并且磁盘是可移动头磁盘。

2、访问磁道随机生成,适当控制范围。


五、实现过程

1、算法实现思路与步骤

1. SCAN算法(电梯算法)

实现思路:

  • 磁头沿一个方向移动,依次处理该方向上所有请求

  • 到达磁盘一端后,掉头反向移动处理剩余请求

实现步骤:

  1. 对请求序列进行排序

  2. 根据当前磁头位置和方向确定第一个要处理的请求

  3. 沿当前方向处理所有请求,记录移动距离

  4. 到达边界后掉头,处理另一方向的请求

  5. 计算总寻道长度和平均寻道长度


2. CSCAN算法(循环扫描算法)

实现思路:

  • 磁头沿一个方向移动处理请求

  • 到达磁盘一端后,立即返回到另一端继续处理(形成循环)

实现步骤:

  1. 对请求序列进行排序

  2. 根据当前磁头位置和方向确定第一个要处理的请求

  3. 沿当前方向处理所有请求

  4. 到达边界后直接跳到另一端继续处理

  5. 计算总寻道长度和平均寻道长度

3. FSCAN算法(改进的SCAN算法)

实现思路:

  • 将请求分为两组:初始请求和新到达请求

  • 先处理初始请求组,再处理新到达请求组

  • 每组内部使用SCAN算法处理

实现步骤:

  1. 将请求分为初始请求和新到达请求两组

  2. 对每组请求分别排序

  3. 先对初始请求组执行SCAN算法

  4. 再对新到达请求组执行SCAN算法

  5. 计算总寻道长度和平均寻道长度


2、三种算法的流程图:

流程图


六、实现过程中的问题及对应思考、解决

1、问题与解决

总体分为以下几个方面的问题:

  1. 函数间的调用问题

我们都知道,在FSCAN中需要每个请求组都调用SCAN,所以在写SCAN的时候就要考虑是一个从范围a到b调用的函数,这样我们就可以将FSCAN中新到达的请求放于队尾,进而在整体的处理,是可以在一个容器中分段的执行SCAN来磁盘调度。

所以写SCAN的时候就不能对一个整体容器的考虑,而是对一个容器中范围的考虑,这样在SFCAN中就可以比较好的调用,代码的重用性就高。

  1. 磁头初始位置的生成问题

在开始的SCAN算法中,因为默认的头指针是不在第一个访问之前或者最后一个访问位置之后的,就是不存在单向访问。

但是到了FSCAN算法的时候,因为要调用SCAN算法,所以可能出现访问了第一个片段之后,头指针大于第二个片段的最大值或小于其最小值,所以需要对头指针的范围进行考虑。

也就是说:当处理完第一个分组后比下一个分组请求中最小的小且向左怎么办?比最大的大且向右怎么办?

所以我们需要单独判断这种情况,这样其实就变成了一种单向的传递不用反向处理


2、相关思考和说明

为什么CSCAN算法出现空跑的现象但是还是一种改进?

因为SCAN算法在磁头调头时,靠近当前移动方向的请求响应快,反方向的请求需等待磁头往返。CSCAN的循环特性确保所有请求的最大等待时间有界(不超过2倍磁道范围),避免极端延迟。CSCAN强制磁头完成全盘循环,公平服务所有区域的请求。

场景SCAN算法CSCAN算法
平均寻道时间较短(无空跑)略长(有空跑)
响应时间分布不均匀(远端请求延迟高)均匀
高负载适应性可能磁臂粘着更公平
实现复杂度简单(需处理调头)简单(无需调头逻辑)

但是也有不足的地方

比如如果开始的磁头位置是50,而第一个到达请求是49,磁头又向内,往大的方向去访问请求,并且访问完后回到0再依次访问,那么49会出现长期的饥饿现象,第一个到但是最后一个才得到访问,这也是一个需要改进的地方。


对于FSCAN算法的思考:

如果我们不将新到达的程序分组而是直接插入访问,其实在最后的平均寻道时间要远小于FSCAN算法,但是这样就会出现磁臂粘着的问题,所以我们不能只看最后的平均寻道时间来判定一个算法的优劣。

其次,我们可以像进程调度中用权重的方法来解决磁臂粘着的问题,避免有请求长期饥饿,但是如果请求的访问十分不均匀,那么寻道时间一定会更长,也就不满足电梯算法的本质了,这也是一个需要考虑的事情。


3、三种算法的比较:

算法优点缺点
SCAN公平性较好,避免了饥饿现象;寻道时间较短响应时间不均匀,可能出现新到的请求磁臂粘着的情况
CSCAN响应时间更均匀;空跑情况其实提高了效率处于磁头初始位置的背后请求可能长时间才会被访问
FSCAN有效解决磁臂粘着问题;新请求不会立即影响当前扫描实现较复杂;需要维护两个请求队列

七、附录

核心代码

SCAN 算法

// SCAN 算法
int SCAN(vector<int>& requests, int frist, int last, // 磁盘的起始和结束位置,方便FSCAN算法的调用,其中last可以理解为容器的长度大小
	string** SACN_Array, int C, int& Head, int& direction) {
	for (int i = frist;i < last;i++) {
		SACN_Array[0][i] = to_string(requests[i]);
	}
	SACN_Array[0][C - 1] = "移动距离"; // 最后一列解释说明
	int next = Head;
	// 找到当前磁头所在位置,并处理好第一个需要访问的请求可能是边界的情况
	int index = frist;
	while (index < last && requests[index] < Head) { ++index; }
	if (index == last) { // 代表此时的头磁盘这个容器的所以访问请求都要大
		index = last - 1;
		direction = 0; // 此时磁头只能向左移动
		next = index;
	}
	else if (index == frist) {
		direction == 1; // 此时磁头只能向右移动
		next = index;
	}
	else { // 在中间的情况
		next = (direction == 0) ? index - 1 : index; // 找到磁头第一个要访问的位置
	}
	int original = next; // 记录原始位置,方便访问到头结束回来

	int ret = frist; // 记录已经访问的请求数量,方便用于多次调用SACN的情况
	int sum = 0; // 总寻道长度
	int distance = 0; // 当前磁头到下一个请求的距离

	if (direction == 1) { // 初始向左的情况
		while (ret < last) {
			distance = abs(Head - requests[next]); // 计算当前磁头到下一个请求的距离
			sum += distance; // 累加寻道长度

			SACN_Array[ret + 1][C - 1] = to_string(distance); // 最后一列记录当前寻道长度
			ret++; // 访问请求数量加一

			if (ret == last) {
				SACN_Array[ret][next] = "--";
				Head = requests[next]; // 更新最终磁头位置
				break;// 如果已经访问完了,直接退出
			}
			else if (direction == 1) { // 还有请求要访问
				Head = requests[next]; // 更新当前磁头位置
				if (next == last - 1) { // 代表这个方向已经访问完了
					direction = 0; // 磁头调转
					SACN_Array[ret][next] = "<-";
					next = original - 1;
				}
				else { // 继续访问该方向的请求
					SACN_Array[ret][next] = "->";
					next++;
				}
			}
			else { // 磁头调转并且还有需要访问的请求
				Head = requests[next]; // 更新当前磁头位置
				SACN_Array[ret][next] = "<-";
				next--;
			}
		}
	}
	else { // 初始向左的情况
		while (ret < last) {
			distance = abs(Head - requests[next]); // 计算当前磁头到下一个请求的距离
			sum += distance; // 累加寻道长度

			SACN_Array[ret + 1][C - 1] = to_string(distance); // 二维数组中记录当前寻道长度
			ret++; // 访问请求数量加一

			if (ret == last) {
				SACN_Array[ret][next] = "--";
				Head = requests[next]; // 更新最终磁头位置
				break;// 如果已经访问完了,直接退出
			}
			else if (direction == 0) { // 还有请求要访问
				Head = requests[next]; // 更新当前磁头位置
				if (next == frist) { // 代表这个方向已经访问完了
					direction = 1; // 磁头调转
					SACN_Array[ret][next] = "->";
					next = original + 1;
				}
				else {
					SACN_Array[ret][next] = "<-";
					next--;
				}
			}
			else { // 磁头调转并且还有需要访问的请求
				Head = requests[next]; // 更新当前磁头位置
				SACN_Array[ret][next] = "->";
				next++;
			}
		}

	}
	return sum;
}

CSCAN 算法

// CSCAN 算法
int CSCAN(vector<int>& requests, int Head, int direction, string** CSACN_Array,
	int frist, int last, int C) {
	for (int i = 0;i < requests.size();i++) {
		CSACN_Array[0][i] = to_string(requests[i]);
	}
	CSACN_Array[0][C - 1] = "移动距离";

	int next = Head;
	// 找到当前磁头所在位置,并处理好第一个需要访问的请求可能是边界的情况
	int index = frist;
	while (index < last && requests[index] < Head) { ++index; }
	if (index == last) { // 代表此时的头磁盘这个容器的所以访问请求都要大
		index = last - 1;
		direction = 0; // 此时磁头只能向左移动
		next = index;
	}
	else if (index == frist) {
		direction == 1; // 此时磁头只能向右移动
		next = index;
	}
	else { // 在中间的情况
		next = (direction == 0) ? index - 1 : index; // 找到磁头第一个要访问的位置
	}
	int original = next; // 记录原始位置,方便访问到头结束回来

	int ret = 0; // 记录已经访问的请求数量
	int sum = 0; // 总寻道长度
	int distance = 0; // 当前磁头到下一个请求的距离

	if (direction == 1) {
		while (ret < requests.size()) {
			distance = abs(Head - requests[next]); // 计算当前磁头到下一个请求的距离
			sum += distance; // 累加寻道长度
			CSACN_Array[ret + 1][C - 1] = to_string(distance); // 记录当前寻道长度
			ret++; // 访问请求数量加一

			if (ret == requests.size()) {
				CSACN_Array[ret][next] = "--";
				Head = requests[next]; // 更新最终磁头位置
				break;// 如果已经访问完了,直接退出
			}
			else if (next == requests.size() - 1) { // 代表这个方向已经访问完了
				CSACN_Array[ret][next] = "<-";
				Head = requests[next];
				next = 0; // 回到第一个请求
			}
			else {
				CSACN_Array[ret][next] = "->";
				Head = requests[next]; // 更新当前磁头位置
				next++;
			}
		}
	}
	else { // 初始向左的情况
		while (ret < requests.size()) {
			distance = abs(Head - requests[next]); // 计算当前磁头到下一个请求的距离
			sum += distance; // 累加寻道长度
			CSACN_Array[ret + 1][C - 1] = to_string(distance); // 记录当前寻道长度
			ret++; // 访问请求数量加一

			if (ret == requests.size()) {
				CSACN_Array[ret][next] = "--";
				Head = requests[next]; // 更新最终磁头位置
				break;// 如果已经访问完了,直接退出
			}
			else if (next == 0) { // 代表这个方向已经访问完了
				CSACN_Array[ret][next] = "->";
				Head = requests[next];
				next = requests.size() - 1; // 回到最后一个请求
			}
			else {
				CSACN_Array[ret][next] = "<-";
				Head = requests[next]; // 更新当前磁头位置
				next--;
			}
		}
	}
	return sum;
}


FSCAN算法

int FSCAN(vector<int>& initial_Requests, int Head, int direction, string** FSCAN_Array, int C, vector<int>& newRequests) {
	// 将两个容器放在一起,并且进行分组调用SCAN算法
	// 这里的newRequests是新到的请求,initial_Requests是原始请求
	sort(newRequests.begin(), newRequests.end()); // 对新到的请求进行排序

	// 将两个容器合并
	vector<int> allRequests;
	allRequests.insert(allRequests.end(), initial_Requests.begin(), initial_Requests.end());
	allRequests.insert(allRequests.end(), newRequests.begin(), newRequests.end());

	FSCAN_Array[0][C - 1] = "移动距离";

	// 对于原始的请求,我们直接进行正常的 SCAN 处理,而不用管新到的请求,从而解决磁臂粘着

	// 先对第一个请求容器进行处理
	int sum = SCAN(allRequests, 0, initial_Requests.size(), FSCAN_Array, C, Head, direction);

	// 处理新到的请求
	sum += SCAN(allRequests, initial_Requests.size(), allRequests.size(), FSCAN_Array, C, Head, direction);

	return sum;
}

程序结果

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


八、温馨说明

学力有限,拙文恐有未周,敬祈读者批评指正,由甚感激!若有讹误或排版不当,尚祈见谅!
☺️☺️☺️☺️☺️

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值