一、前言 – 关于磁盘磁盘(硬盘,HDD)是一种机械式存储设备,其 结构图如下:
image-202506111433218801. 磁盘的基本组成① 盘片(Platter)
功能:盘片的表面涂有磁性物质,这些磁性物质用来记录二进制数据。因为正反两面都可涂上磁性物质,故一个盘片可能会有两个盘面特点: 一个磁盘(如一个 1T 的机械硬盘)由多个盘片(如上图中的 0 号盘片)叠加而成(多盘片设计可增加容量)数据以同心圆(磁道)形式存储在盘片表面② 磁头(Read/Write Head)
功能 :读取或写入数据的机械臂末端装置,悬浮在盘片表面(通过空气动力学原理,与盘片保持极小间隙)。特点 : 每个盘片表面有一个磁头,多个磁头固定在同一磁头臂(Actuator Arm)上,同步移动。磁头通过改变磁性材料的极性(写入)或检测极性(读取)来操作数据。③ 主轴(Spindle)
功能 :驱动盘片高速旋转的电机轴,决定盘片的转速(如 5400 RPM、7200 RPM、10000 RPM)。转速影响 :转速越高,**旋转延迟(Rotational Latency)**越低。④ 磁道(Track)
定义 :盘片上的一圈同心圆,每个磁道存储一定量的数据。特点 :磁道编号从外向内递增(最外层为 0 号磁道),靠近主轴的同心园用于停靠磁头,不存储数据⑤ 扇区(Sector)
定义 :磁道被划分为多个扇区,每个扇区存储固定大小的数据(通常为 512 字节或 4KB)。特点 : 扇区是磁盘数据读写的最小单位。每个扇区包含数据、地址信息(如磁道号、扇区号)和校验码(CRC)如下图:
image-20250611144235201⑥ 柱面(Cylinder)
定义 :所有盘片上相同半径的磁道组成一个柱面(Cylinder)。例如,如果硬盘有 4 个盘片,则每个柱面包含 8 个磁道(每个盘片的上下表面各一个)。作用 :柱面是磁盘寻道操作的基本单位,减少磁头臂移动次数磁盘容量 = 磁头数 x 磁道(柱面)数 x 毎道扇区数 x 毎扇区字节数
2. 磁盘的工作原理磁盘通过机械运动和磁性技术实现数据的读写
(1)数据存储方式
磁性记录 :数据以二进制形式存储为磁性变化。磁头通过改变磁性材料的极性(写入)或检测极性(读取)来操作数据。编码方式 :现代硬盘使用PRML(部分响应最大似然)*或* EPRML 技术提高存储密度。(2)数据访问过程
由上,可用(柱面号,盘面号,扇区号)来定位任意一个“磁盘块”。
在“文件的物理结构”小节中,我们经常提到文件数据存放在外存中的几号块(逻辑地址),这个块号就可以转换成(柱面号,盘面号,扇区号)的地址形式。
可根据该地址读取一个“块”,操作如下:
寻道(Seek):根据“柱面号”移动磁臂,让磁头指向目标磁道所在柱面旋转延迟(Rotational Latency):等待目标扇区旋转到磁头下方数据传输(Transfer):磁盘旋转的过程中,指定的扇区会从磁头下面划过,这样就完成了对指定扇区的读/写磁盘访问时间由以下三部分组成:
寻道时间(Seek Time) :磁头移动到目标磁道所需时间,约 3-15 毫秒(取决于硬盘型号和磁头移动距离)旋转延迟(Rotational Latency) :磁盘旋转到目标扇区所需时间(取决转速,如7200 RPM硬盘平均延迟约 \frac{60}{7200*2}=4.17ms )
传输时间(Transfer Time) :读取或写入数据的时间(取决于数据块大小和磁盘带宽),如,传输 1MB 数据(带宽 100MB/s)需要 10 毫秒优化重点 :寻道时间通常占主导,因此磁盘调度主要针对减少磁头移动距离。
3. 磁盘的性能指标(1) 容量(Capacity)
定义 :磁盘可存储的总数据量,单位为 GB 或 TB。影响因素 :盘片数量、单盘片存储密度、扇区大小。(2) 转速(RPM, Revolutions Per Minute)
定义 :盘片每分钟旋转的次数,如 5400 RPM、7200 RPM、10000 RPM。影响 :转速越高,旋转延迟越低,但噪音和功耗增加。(3) 缓存(Cache)
定义 :硬盘内置的高速内存(通常为几 MB 到几百 MB),用于临时存储频繁访问的数据。作用 :减少磁头频繁移动,提升读写效率。(4) 平均寻道时间(Average Seek Time)
定义 :磁头从一个磁道移动到另一个随机磁道所需的平均时间。典型值 :3-15 毫秒。(5) 数据传输率(Data Transfer Rate)
定义 :单位时间内传输的数据量,分为内部传输率 (磁盘内部读写)和外部传输率 (接口到主机)。影响因素 :磁盘接口(如 SATA、SCSI)、盘片转速、存储密度。4. HDD vs SSD特性
HDD(机械硬盘)
SSD(固态硬盘)
存储原理
磁性存储,机械读写
电子存储(NAND 闪存),无机械部件
访问速度
慢(寻道时间和旋转延迟)
快(直接电子访问,无延迟)
耐用性
易磨损(机械部件)
寿命有限(写入次数限制)
功耗
高(电机和磁头运动)
低
价格
低(按容量计算)
高
适用场景
大容量存储(如备份、冷数据)
高速读写(如系统盘、数据库)
二、磁盘调度算法简介(1) 先来先服务(FCFS, First-Come-First-Served)原理 :FIFO 算法按照请求到达的顺序进行服务。无论磁头当前位置和请求磁道位置如何,总是先处理最早到达的请求。
优点: 实现逻辑简单,易于理解和实现。缺点: 不考虑磁盘物理特性(磁头位置),可能导致磁头频繁来回移动,寻道效率低下,平均寻道时间较长。适用场景 :请求较少或无法预测请求模式时。示例 :
磁头初始位置:50请求队列:[95, 180, 34, 119, 11, 123, 62, 64]处理顺序:50 → 95 → 180 → 34 → 119 → 11 → 123 → 62 → 64总寻道距离: (45)+(85)+(146)+(85)+(108)+(112)+(61)+(2) = 644(2) 最短寻道时间优先(SSTF, Shortest Seek Time First)原理 :SSTF 算法的核心思想是 “就近服务”。它总是选择离当前磁头位置最近的那个磁道请求进行服务。这样做可以最大程度地减少单次寻道时间,从而通常能获得较短的平均寻道时间
优点 :寻道时间短,吞吐量高。缺点 : 可能导致“饥饿”现象(starvation)。如果磁头附近不断有新的请求到来,远离磁头的请求可能长时间得不到服务。此外,每次都需要遍历所有未处理请求以找到最近的,开销相对较大。适用场景 :追求平均响应时间最短。示例 :
磁头初始位置:50
请求队列:[95, 180, 34, 119, 11, 123, 62, 64]
处理顺序:
50 → 62(+12)62 → 64(+2)64 → 34(-30)34 → 11(-23)11 → 95(+84)95 → 119(+24)119 → 123(+4)123 → 180(+57)总寻道距离:12+2+30+23+84+24+4+57 = 236
(3) 扫描算法(SCAN, 电梯算法)原理 :SCAN 算法(也称电梯算法)模拟电梯的运行方式。磁头从当前位置开始,沿一个方向(例如,磁道号增大的方向)移动并服务该方向上的所有请求,直到到达磁盘的最远端(或该方向上最后一个请求)。然后,它会反向移动,并服务另一个方向上的所有请求,直到到达另一端(或该方向上最后一个请求)
优点 :解决了 SSTF 的饥饿问题,因为磁头会扫描整个磁盘范围,所有请求最终都会被服务。相对 SSTF 提供了更公平的等待时间缺点 :磁头会强制移动到磁盘的边缘,即使在边缘方向没有请求,也需要额外的寻道时间适用场景 :平衡公平性和效率。示例 :
磁头初始位置:50,方向:向右
请求队列:[95, 180, 34, 119, 11, 123, 62, 64]
处理顺序:
50 → 62 → 64 → 95 → 119 → 123 → 180(到右端)反向:180 → 34(经过中间未处理的请求)34->11总寻道距离:(12)+(2)+(31)+(24)+(4)+(57)+(180-34=146) + (34-11=23) = 299
(4) 循环扫描(C-SCAN, Circular SCAN)原理 : C-SCAN 算法是 SCAN 算法的改进版,旨在提供更均匀的等待时间。磁头从当前位置开始向一个方向(例如,磁道号增大的方向)扫描并服务请求,直到到达磁盘的一段(如: 最大磁道),或者说这个方法上已经没有其他请求为止。然后,它会 立即跳回 到磁盘的另一端 (如:最小磁道),并从那里重新开始向同一方向(磁道号增大的方向)扫描和服务的循环。这个从最大磁道跳回最小磁道的寻道距离会计入总寻道长度,但回程路上不处理任何请求
优点 :提供了比 SCAN 更均匀的等待时间,因为它总是向一个方向服务,避免了磁头在回程途中服务请求导致某些请求等待时间过长缺点 :磁头在回程时不服务请求,这部分寻道开销是“浪费”的,但在某些场景下(如实时系统)均匀性可能比总寻道时间更重要适用场景 :适合均匀分布的请求示例 :
磁头初始位置:50,方向:向右
请求队列:[95, 180, 34, 119, 11, 123, 62, 64]
处理顺序:
50 → 62 → 64 → 95 → 119 → 123 → 180(到右端)快速返回:180 → 11(忽略中间请求)11 → 34
总寻道距离:(12)+(2)+(31)+(24)+(4)+(57) + (169) + (23) = 322SCAN 和 CSCAN区别:
SCAN 更注重减少磁头移动距离,但可能导致边缘请求等待时间较长。CSCAN 通过循环移动保证所有请求的响应时间更均匀,但回程的空转会增加总寻道距离。选择依据 :根据请求分布模式选择算法——SCAN 适合局部密集型请求,CSCAN 适合全局均匀型请求 。3. 算法对比算法
平均寻道时间
公平性
饥饿问题
适用场景
FCFS
高
高
无
简单环境
SSTF
最低
低
有
高吞吐量
SCAN
中等
中等
无
普遍适用
C-SCAN
中等
高
无
均匀请求
三、磁盘调度算法的模拟实现1. 类基本框架和辅助函数实现代码语言:javascript代码运行次数:0运行复制// 磁道范围 -- 用于SCAN/C-SCAN的边界处理
const int MIN_TRACK = 0;
const int MAX_TRACK = 999;
class DiskScheduler {
private:
std::vector
int _currentHeadPos; // 当前磁头存储位置
// 统一输出调度结果
void PrintSchedulingResults(const std::string& algorithmName,
const std::vector
double totalSeekLength,
int numRequests)
{
std::cout << "\n--- " << algorithmName << " 调度结果 ---" << std::endl;
std::cout << "寻道路径: ";
for (int i = 0; i < path.size(); ++i) {
std::cout << path[i];
if (i < path.size() - 1) std::cout << "-> ";
}
std::cout << std::endl;
std::cout << "总寻道长度: " << std::fixed << std::setprecision(2) << totalSeekLength << std::endl;
if (numRequests > 0) {
// 打印两位小数
std::cout << "平均寻道长度: " << std::fixed << std::setprecision(2)
<< totalSeekLength / numRequests << std::endl;
}
else {
std::cout << "没有磁道请求,无法计算平均寻道长度。" << std::endl;
}
std::cout << "--------------------------" << std::endl;
}
bool Empty() {
if (_requests.empty()) {
std::cout << "没有磁道请求需要处理!" << std::endl;
return true;
}
return false;
}
public:
DiskScheduler(const std::vector
:_requests(requests), _currentHeadPos(0) {}
~DiskScheduler() {
_requests.clear();
_currentHeadPos = 0;
}
// 获取用户输入磁道号, 并进行简单校验 prompt 是输入提醒字符串
static int getValidatedTrackInput(const std::string& prompt) {
int value;
while (true) {
if(!prompt.empty())std::cout << prompt;
std::cin >> value;
if (std::cin.fail() || value < MIN_TRACK || value > MAX_TRACK) {
std::cout << "输入无效!磁道号应在 " << MIN_TRACK << " 到 " << MAX_TRACK
<< " 之间。请重新输入。" << std::endl;
std::cin.clear(); // 清除错误标志
std::cin.ignore(std::numeric_limits
}
else {
return value;
}
}
}
// 设置当前磁头位置
void SetCurrentHead() {
_currentHeadPos = getValidatedTrackInput("请输入当前所在的磁道号: ");
}
// 显示当前初识磁道请求序列
void DisplayInitialRequests() const {
int max_label_length = 0;
for (size_t i = 1; i <= _requests.size(); ++i) {
int len = std::to_string(i).length() + 1; // "R" + 数字的长度
max_label_length = std::max(max_label_length, len);
}
// 设置固定字段宽度(标签+数值)
int field_width = max_label_length + 3; // 标签+数值总宽度(如 "R1" + 3位数字)
// 打印标签行
for (size_t i = 1; i <= _requests.size(); ++i) {
std::cout << std::setw(field_width) << "R" + std::to_string(i);
}
std::cout << std::endl;
// 打印数值行
for (int request : _requests) {
std::cout << std::setw(field_width) << request;
}
std::cout << "\n----------------------" << std::endl;
}
// 顺序处理
void RunFCFS(bool true){
if (Empty()) return;
// ...
PrintSchedulingResults("FIFO", path, totalSeekLength, _requests.size());
}
void RunSSTF(bool flag = true){
if (Empty()) return;
if (flag == true) SetCurrentHead();
// ...
PrintSchedulingResults("SSTF", path, totalSeekLength, _requests.size());
}
void RunSCAN(bool flag = true) {
if (Empty()) return;
if (flag == true) SetCurrentHead();
// ...
PrintSchedulingResults("SCAN", path, totalSeekLength, _requests.size());
}
void RunCSCAN(bool flag = true) {
if (Empty()) return;
if (flag == true) SetCurrentHead();
// ...
PrintSchedulingResults("C-SCAN", path, totalSeekLength, _requests.size());
}
void RunAll() {
SetCurrentHead();
RunFCFS(false); RunSSTF(false);
RunSCAN(false); RunCSCAN(false);
}
// 显示主菜单
void displayMenu() const {
std::cout << "\n============== 磁盘调度算法模拟 ==============" << std::endl;
std::cout << "1. 先进先出算法(FIFO)" << std::endl;
std::cout << "2. 最短服务时间优先算法(SSTF)" << std::endl;
std::cout << "3. 扫描算法(SCAN)" << std::endl;
std::cout << "4. 循环扫描算法(C-SCAN)" << std::endl;
std::cout << "5. 退出" << std::endl;
std::cout << "==============================================" << std::endl;
std::cout << "请选择: ";
}
};2. 算法函数模拟实现2.1 FCFS代码语言:javascript代码运行次数:0运行复制void RunFCFS(bool flag = true){
if (Empty()) return;
if(flag == true) SetCurrentHead();
std::vector
std::vector
path.push_back(_currentHeadPos);
double totalSeekLength = 0; // 总长
int currentTrack = _currentHeadPos; // 当前轨道位置
for(int request: _requests) {
path.emplace_back(request);
totalSeekLength += std::abs(request - currentTrack);
currentTrack = request;
}
PrintSchedulingResults("FIFO", path, totalSeekLength, _requests.size());
}代码实现分析:
if (Empty()) return;: 首先检查请求序列是否为空,如果为空则直接返回。
if(flag == true) SetCurrentHead();: 如果 flag 为真(默认情况),则提示用户输入当前磁头位置。这使得在单独运行 FIFO 时可以设置磁头位置,而在 RunAll 中统一设置一次即可。
std::vector
double totalSeekLength = 0; int currentTrack = _currentHeadPos;: 初始化总寻道长度和当前磁头位置。
for(int request: _requests) { ... }: 遍历原始的 _requests
向量,因为 FIFO 不改变请求顺序。
path.emplace_back(request);: 将当前服务的请求磁道添加到寻道路径中。totalSeekLength += std::abs(request - currentTrack);: 计算从 currentTrack 到 request 的绝对距离,并累加到 totalSeekLength。currentTrack = request;: 更新 currentTrack 为刚刚服务的请求磁道,作为下一次寻道计算的起点。 PrintSchedulingResults("FIFO", path, totalSeekLength, _requests.size());: 调用辅助函数打印 FIFO 调度结果。
2.2 SSTF代码语言:javascript代码运行次数:0运行复制void RunSSTF(bool flag = true){
if (Empty()) return;
if (flag == true) SetCurrentHead();
std::vector
std::vector
path.push_back(_currentHeadPos);
double totalSeekLength = 0; // 总长
int currentTrack = _currentHeadPos; // 当前轨道位置
while (!remainingRequests.empty()) {
// 最小距离 下个轨道位置
int shortestDistance = -1, nextTrackIndex = -1;
// 找每次剩余磁头中距离当前最近的轨道(绝对距离)
for (size_t i = 0; i < remainingRequests.size(); ++i) {
int dis = std::abs(remainingRequests[i] - currentTrack);
if (nextTrackIndex == -1 || dis < shortestDistance) {
shortestDistance = dis;
nextTrackIndex = i;
}
}
// 此时就是最近, 然后处理走到下一个轨道
totalSeekLength += shortestDistance;
currentTrack = remainingRequests[nextTrackIndex];
path.emplace_back(currentTrack);
remainingRequests.erase(remainingRequests.begin() + nextTrackIndex);
}
PrintSchedulingResults("SSTF", path, totalSeekLength, _requests.size());
}代码实现分析:
if (Empty()) return; / SetCurrentHead();: 与 FIFO 类似,进行空检查并设置磁头位置。std::vector
if (Empty()) return;
if (flag == true) SetCurrentHead();
std::vector
std::sort(sortedRequests.begin(), sortedRequests.end());
std::vector
path.push_back(_currentHeadPos);
double totalSeekLength = 0; // 总数
int currentTrack = _currentHeadPos; // 当前轨道
// 使用二分查找当前轨道的大于等于轨道
// 方式一:适用于随机访问容器 (vector)
// int startIndex = std::lower_bound(sortedRequests.begin(), sortedRequests.end(), _currentHeadPos) - sortedRequests.begin();
// 方式二:适用于所有容器
auto it = std::lower_bound(sortedRequests.begin(), sortedRequests.end(), _currentHeadPos);
int startIndex = std::distance(sortedRequests.begin(), it);
// 升序往右走
for (int i = startIndex; i < sortedRequests.size(); ++i) {
// 当前轨道和下一个轨道距离
totalSeekLength += std::abs(sortedRequests[i] - currentTrack);
currentTrack = sortedRequests[i];
path.emplace_back(currentTrack);
}
for (int i = startIndex - 1; i >= 0; --i) {
totalSeekLength += std::abs(sortedRequests[i] - currentTrack);
currentTrack = sortedRequests[i];
path.push_back(currentTrack);
}
PrintSchedulingResults("SCAN", path, totalSeekLength, _requests.size());
}代码实现分析:
if (Empty()) return; / SetCurrentHead();: 检查和设置磁头位置。std::vector
if (Empty()) return;
if (flag == true) SetCurrentHead();
std::vector
std::sort(sortedRequests.begin(), sortedRequests.end());
std::vector
path.push_back(_currentHeadPos);
double totalSeekLength = 0; // 总数
int currentTrack = _currentHeadPos; // 当前轨道
// 二分
auto it = std::lower_bound(sortedRequests.begin(), sortedRequests.end(), _currentHeadPos);
int startIndex = std::distance(sortedRequests.begin(), it);
// 升序往右走
for (int i = startIndex; i < sortedRequests.size(); ++i) {
// 当前轨道和下一个轨道距离
totalSeekLength += std::abs(sortedRequests[i] - currentTrack);
currentTrack = sortedRequests[i];
path.emplace_back(currentTrack);
}
// 从最左端开始
for (int i = 0; i < startIndex; ++i) {
totalSeekLength += std::abs(sortedRequests[i] - currentTrack);
currentTrack = sortedRequests[i];
path.push_back(currentTrack);
}
PrintSchedulingResults("C-SCAN", path, totalSeekLength, _requests.size());
}代码实现分析:
if (Empty()) return; / SetCurrentHead();: 检查和设置磁头位置。std::vector
std::cout << "欢迎来到磁盘调度算法模拟器!\n";
int numRequests;
while (true) {
std::cout << "请先输入磁道请求数量 (1-" << MAX_TRACK << "): ";
std::cin >> numRequests;
if (std::cin.fail() || numRequests <= 0 || numRequests > MAX_TRACK) {
std::cout << "输入无效!请求数量应为正整数且不超过 " << MAX_TRACK << "。请重新输入。\n";
std::cin.clear();
// 忽略缓冲区中当前行的所有字符(直到遇到换行符),防止无效输入残留在缓冲区中影响后续输入
std::cin.ignore(std::numeric_limits
}
else break;
}
std::vector
std::cout << "请输入 " << numRequests << " 个磁道序列 (范围 " << MIN_TRACK << "-" << MAX_TRACK << "):\n";
for (int i = 0; i < numRequests; ++i) {
initialRequests[i] = DiskScheduler::getValidatedTrackInput("请输入第 " + std::to_string(i + 1) + " 个磁道号: ");
// initialRequests[i] = DiskScheduler::getValidatedTrackInput("");
}
DiskScheduler scheduler(initialRequests); // 创建调度器对象
scheduler.DisplayInitialRequests(); // 显示初识请求
while (true) {
scheduler.displayMenu();
int choice;
std::cin >> choice;
if (std::cin.fail()) {
std::cout << "输入无效!请选择一个数字选项。" << std::endl;
std::cin.clear();
std::cin.ignore(std::numeric_limits
continue;
}
switch (choice) {
case 1:
scheduler.RunFCFS();
break;
case 2:
scheduler.RunSSTF();
break;
case 3:
scheduler.RunSCAN();
break;
case 4:
scheduler.RunCSCAN();
break;
case 5:
scheduler.RunAll();
break;
case 6:
std::cout << "\n感谢使用,再见!" << std::endl;
return 0;
default:
std::cout << "选择错误,请重新选择 (1-5)!" << std::endl;
break;
}
std::cout << std::endl; // 每次操作后换行,美观
}
return 0;
}结果如下:
代码语言:javascript代码运行次数:0运行复制欢迎来到磁盘调度算法模拟器!
请先输入磁道请求数量 (1-999): 8
请输入 8 个磁道序列 (范围 0-999):
请输入第 1 个磁道号: 95
请输入第 2 个磁道号: 180
请输入第 3 个磁道号: 34
请输入第 4 个磁道号: 119
请输入第 5 个磁道号: 11
请输入第 6 个磁道号: 123
请输入第 7 个磁道号: 62
请输入第 8 个磁道号: 64
R1 R2 R3 R4 R5 R6 R7 R8
95 180 34 119 11 123 62 64
----------------------
============== 磁盘调度算法模拟 ==============
1. 先进先出算法(FCFS)
2. 最短服务时间优先算法(SSTF)
3. 扫描算法(SCAN)
4. 循环扫描算法(C-SCAN)
5. 对比所有算法(all)
6. 退出
==============================================
请选择: 5
请输入当前所在的磁道号: 50
--- FIFO 调度结果 ---
寻道路径: 50-> 95-> 180-> 34-> 119-> 11-> 123-> 62-> 64
总寻道长度: 644.00
平均寻道长度: 80.50
--------------------------
--- SSTF 调度结果 ---
寻道路径: 50-> 62-> 64-> 34-> 11-> 95-> 119-> 123-> 180
总寻道长度: 236.00
平均寻道长度: 29.50
--------------------------
--- SCAN 调度结果 ---
寻道路径: 50-> 62-> 64-> 95-> 119-> 123-> 180-> 34-> 11
总寻道长度: 299.00
平均寻道长度: 37.38
--------------------------
--- C-SCAN 调度结果 ---
寻道路径: 50-> 62-> 64-> 95-> 119-> 123-> 180-> 11-> 34
总寻道长度: 322.00
平均寻道长度: 40.25
--------------------------四、完整代码及小结代码语言:javascript代码运行次数:0运行复制#include
#include
#include
#include
#include
#include
#include
// 数据
// 磁道请求队列:[95, 180, 34, 119, 11, 123, 62, 64]
// 当前磁道: 50
// 磁道范围 -- 用于SCAN/C-SCAN的边界处理
const int MIN_TRACK = 0;
const int MAX_TRACK = 999;
class DiskScheduler {
private:
std::vector
int _currentHeadPos; // 当前磁头存储位置
// 统一输出调度结果
void PrintSchedulingResults(const std::string& algorithmName,
const std::vector
double totalSeekLength,
int numRequests)
{
std::cout << "\n--- " << algorithmName << " 调度结果 ---" << std::endl;
std::cout << "寻道路径: ";
for (int i = 0; i < path.size(); ++i) {
std::cout << path[i];
if (i < path.size() - 1) std::cout << "-> ";
}
std::cout << std::endl;
std::cout << "总寻道长度: " << std::fixed << std::setprecision(2) << totalSeekLength << std::endl;
if (numRequests > 0) {
// 打印两位小数
std::cout << "平均寻道长度: " << std::fixed << std::setprecision(2)
<< totalSeekLength / numRequests << std::endl;
}
else {
std::cout << "没有磁道请求,无法计算平均寻道长度。" << std::endl;
}
std::cout << "--------------------------" << std::endl;
}
bool Empty() {
if (_requests.empty()) {
std::cout << "没有磁道请求需要处理!" << std::endl;
return true;
}
return false;
}
public:
DiskScheduler(const std::vector
:_requests(requests), _currentHeadPos(0) {}
~DiskScheduler() {
_requests.clear();
_currentHeadPos = 0;
}
// 获取用户输入磁道号, 并进行简单校验 prompt 是输入提醒字符串
static int getValidatedTrackInput(const std::string& prompt) {
int value;
while (true) {
if(!prompt.empty())std::cout << prompt;
std::cin >> value;
if (std::cin.fail() || value < MIN_TRACK || value > MAX_TRACK) {
std::cout << "输入无效!磁道号应在 " << MIN_TRACK << " 到 " << MAX_TRACK
<< " 之间。请重新输入。" << std::endl;
std::cin.clear(); // 清除错误标志
std::cin.ignore(std::numeric_limits
}
else {
return value;
}
}
}
// 设置当前磁头位置
void SetCurrentHead() {
_currentHeadPos = getValidatedTrackInput("请输入当前所在的磁道号: ");
}
// 显示当前初识磁道请求序列
void DisplayInitialRequests() const {
int max_label_length = 0;
for (size_t i = 1; i <= _requests.size(); ++i) {
int len = std::to_string(i).length() + 1; // "R" + 数字的长度
max_label_length = std::max(max_label_length, len);
}
// 设置固定字段宽度(标签+数值)
int field_width = max_label_length + 3; // 标签+数值总宽度(如 "R1" + 3位数字)
// 打印标签行
for (size_t i = 1; i <= _requests.size(); ++i) {
std::cout << std::setw(field_width) << "R" + std::to_string(i);
}
std::cout << std::endl;
// 打印数值行
for (int request : _requests) {
std::cout << std::setw(field_width) << request;
}
std::cout << "\n----------------------" << std::endl;
}
// 顺序处理原则
void RunFCFS(bool flag = true){
if (Empty()) return;
if(flag == true) SetCurrentHead();
std::vector
std::vector
path.push_back(_currentHeadPos);
double totalSeekLength = 0; // 总长
int currentTrack = _currentHeadPos; // 当前轨道位置
for(int request: _requests) {
path.emplace_back(request);
totalSeekLength += std::abs(request - currentTrack);
currentTrack = request;
}
PrintSchedulingResults("FIFO", path, totalSeekLength, _requests.size());
}
// 最近处理原则
void RunSSTF(bool flag = true){
if (Empty()) return;
if (flag == true) SetCurrentHead();
std::vector
std::vector
path.push_back(_currentHeadPos);
double totalSeekLength = 0; // 总长
int currentTrack = _currentHeadPos; // 当前轨道位置
while (!remainingRequests.empty()) {
// 最小距离 下个轨道位置
int shortestDistance = -1, nextTrackIndex = -1;
// 找每次剩余磁头中距离当前最近的轨道(绝对距离)
for (size_t i = 0; i < remainingRequests.size(); ++i) {
int dis = std::abs(remainingRequests[i] - currentTrack);
if (nextTrackIndex == -1 || dis < shortestDistance) {
shortestDistance = dis;
nextTrackIndex = i;
}
}
// 此时就是最近, 然后处理走到下一个轨道
totalSeekLength += shortestDistance;
currentTrack = remainingRequests[nextTrackIndex];
path.emplace_back(currentTrack);
remainingRequests.erase(remainingRequests.begin() + nextTrackIndex);
}
PrintSchedulingResults("SSTF", path, totalSeekLength, _requests.size());
}
// 基于当前轨道升序走, 走到尽头再回到起始轨道位置降序走
void RunSCAN(bool flag = true) {
if (Empty()) return;
if (flag == true) SetCurrentHead();
std::vector
std::sort(sortedRequests.begin(), sortedRequests.end());
std::vector
path.push_back(_currentHeadPos);
double totalSeekLength = 0; // 总数
int currentTrack = _currentHeadPos; // 当前轨道
// 使用二分查找当前轨道的大于等于轨道
// 方式一:适用于随机访问容器 (vector)
// int startIndex = std::lower_bound(sortedRequests.begin(), sortedRequests.end(), _currentHeadPos) - sortedRequests.begin();
// 方式二:适用于所有容器
auto it = std::lower_bound(sortedRequests.begin(), sortedRequests.end(), _currentHeadPos);
int startIndex = std::distance(sortedRequests.begin(), it);
// 升序往右走
for (int i = startIndex; i < sortedRequests.size(); ++i) {
// 当前轨道和下一个轨道距离
totalSeekLength += std::abs(sortedRequests[i] - currentTrack);
currentTrack = sortedRequests[i];
path.emplace_back(currentTrack);
}
for (int i = startIndex - 1; i >= 0; --i) {
totalSeekLength += std::abs(sortedRequests[i] - currentTrack);
currentTrack = sortedRequests[i];
path.push_back(currentTrack);
}
PrintSchedulingResults("SCAN", path, totalSeekLength, _requests.size());
}
// 和 SCAN 类似, 但是其回头的时候直接从最左端开始
void RunCSCAN(bool flag = true) {
if (Empty()) return;
if (flag == true) SetCurrentHead();
std::vector
std::sort(sortedRequests.begin(), sortedRequests.end());
std::vector
path.push_back(_currentHeadPos);
double totalSeekLength = 0; // 总数
int currentTrack = _currentHeadPos; // 当前轨道
// 二分
auto it = std::lower_bound(sortedRequests.begin(), sortedRequests.end(), _currentHeadPos);
int startIndex = std::distance(sortedRequests.begin(), it);
// 升序往右走
for (int i = startIndex; i < sortedRequests.size(); ++i) {
// 当前轨道和下一个轨道距离
totalSeekLength += std::abs(sortedRequests[i] - currentTrack);
currentTrack = sortedRequests[i];
path.emplace_back(currentTrack);
}
// 从最左端开始
for (int i = 0; i < startIndex; ++i) {
totalSeekLength += std::abs(sortedRequests[i] - currentTrack);
currentTrack = sortedRequests[i];
path.push_back(currentTrack);
}
PrintSchedulingResults("C-SCAN", path, totalSeekLength, _requests.size());
}
void RunAll() {
SetCurrentHead();
RunFCFS(false); RunSSTF(false);
RunSCAN(false); RunCSCAN(false);
}
// 显示主菜单
void displayMenu() const {
std::cout << "\n============== 磁盘调度算法模拟 ==============" << std::endl;
std::cout << "1. 先进先出算法(FCFS)" << std::endl;
std::cout << "2. 最短服务时间优先算法(SSTF)" << std::endl;
std::cout << "3. 扫描算法(SCAN)" << std::endl;
std::cout << "4. 循环扫描算法(C-SCAN)" << std::endl;
std::cout << "5. 对比所有算法(all)" << std::endl;
std::cout << "6. 退出" << std::endl;
std::cout << "==============================================" << std::endl;
std::cout << "请选择: ";
}
};
int main() {
std::cout << "欢迎来到磁盘调度算法模拟器!\n";
int numRequests;
while (true) {
std::cout << "请先输入磁道请求数量 (1-" << MAX_TRACK << "): ";
std::cin >> numRequests;
if (std::cin.fail() || numRequests <= 0 || numRequests > MAX_TRACK) {
std::cout << "输入无效!请求数量应为正整数且不超过 " << MAX_TRACK << "。请重新输入。\n";
std::cin.clear();
std::cin.ignore(std::numeric_limits
}
else break;
}
std::vector
std::cout << "请输入 " << numRequests << " 个磁道序列 (范围 " << MIN_TRACK << "-" << MAX_TRACK << "):\n";
for (int i = 0; i < numRequests; ++i) {
initialRequests[i] = DiskScheduler::getValidatedTrackInput("请输入第 " + std::to_string(i + 1) + " 个磁道号: ");
// initialRequests[i] = DiskScheduler::getValidatedTrackInput("");
}
DiskScheduler scheduler(initialRequests); // 创建调度器对象
scheduler.DisplayInitialRequests(); // 显示初识请求
while (true) {
scheduler.displayMenu();
int choice;
std::cin >> choice;
if (std::cin.fail()) {
std::cout << "输入无效!请选择一个数字选项。" << std::endl;
std::cin.clear();
std::cin.ignore(std::numeric_limits
continue;
}
switch (choice) {
case 1:
scheduler.RunFCFS();
break;
case 2:
scheduler.RunSSTF();
break;
case 3:
scheduler.RunSCAN();
break;
case 4:
scheduler.RunCSCAN();
break;
case 5:
scheduler.RunAll();
break;
case 6:
std::cout << "\n感谢使用,再见!" << std::endl;
return 0;
default:
std::cout << "选择错误,请重新选择 (1-5)!" << std::endl;
break;
}
std::cout << std::endl; // 每次操作后换行,美观
}
return 0;
}