操作系统之课程设计常用磁盘调度算法的实现,附源码

计算机网络 专栏收录该内容
14 篇文章 3 订阅

《操作系统》课程设计任务书

题目: 常用磁盘调度算法的实现

学生姓名:
班 级:

学 号: 指导教师

一、设计目的

学生通过该题目的设计过程,掌握常用页面置换算法的原理、软件开发方法并提高解决实际问题的能力。

二、设计内容

1.了解UNIX的命令及使用格式,熟悉UNIX/LINUX的常用基本命令,练习并掌握UNIX提供的vi编辑器来编译C程序,学会利用gcc、gdb编译、调试C程序。

2.设计一个磁盘工作区,并使用先来先服务算法(FCFS)、最短寻道时间优先算法(SSTF)、扫描算法(SCAN)和循环扫描算法(C-SCAN)计算磁头移动的总磁道数。

三、 设计要求及工作量

  1. 分析设计要求,给出解决方案(要说明设计实现所用的原理、采用的数据结构)。

  2. 设计合适的测试用例,对得到的运行结果要有分析。

  3. 设计中遇到的问题,设计的心得体会。

4.文档:课程设计打印文档每个学生一份,并装在统一的资料袋中。

  1. 光盘:每个学生的文档和程序资料建在一个以自己学号和姓名命名的文件夹下,刻录一张光盘,装入资料袋中。

四、要提交的成果

  1. 设计说明书一份,内容包括:

(1)中文摘要100字;关键词3-5个;

(2)设计思想;

(3)各模块的伪码算法;

(4)函数的调用关系图;

(5)测试结果;

(6)源程序(带注释);

(7)设计总结;

(8)参考文献、致谢等。

2.刻制光盘一张。

五、设计进度计划及时间安排

周次

日期

内容

地点

第1周

星期一~二

教师讲解设计要求
查找参考资料

教室
图书馆

星期三~五

算法设计,编程实现

教室

第2周

星期一~三

调试测试,撰写文档

教室

星期四~五

检查程序,答辩

教室

六、主要参考文献

[1]汤子瀛 ,哲凤屏.《计算机操作系统》[M].西安电子科技大学出版社.

[2]王清,李光明.《计算机操作系统》[M].冶金工业出版社.

[3]孙钟秀等.《操作系统教程》[M].高等教育出版社.

[4]曾明.《Linux操作系统应用教程》[M].陕西科学技术出版社.

[5]张丽芬,刘利雄.《操作系统实验教程》[M].清华大学出版社.

[6]孟静.《操作系统教程——原理和实例分析》[M].高等教育出版社.

[7]周长林.《计算机操作系统教程》[M].高等教育出版社.

[8]张尧学.《计算机操作系统教程》[M].清华大学出版社.

[9]任满杰.《操作系统原理实用教程》[M].电子工业出版社.

[10]张坤.《操作系统实验教程》[M].清华大学出版社.

摘要

设备的动态分配算法与进程调度相似,也是基于一定的分配策略的。常用的分配策略有先请求先分配、优先级高者先分配等策略。在多道程序系统中,低效率通常是由于磁盘类旋转设备使用不当造成的。操作系统中,对磁盘的访问要求来自多方面,常常需要排队。这时,对众多的访问要求按一定的次序响应,会直接影响磁盘的工作效率,进而影响系统的性能。存取盘中的信息一般要用三部分时间:寻道时间、旋转延迟时间、传输时间。对于大多数磁盘来说,寻到时间远远大于后两者之和,其中查找时间是决定因素。因此,减少平均寻道时间就可以显著提高系统性能。

本设计的目的就是通过设计一个磁盘调度模拟系统,以加深对FCFS、最短寻道时间以及电梯等磁盘调度算法的理解。通过输入为一组作业的磁道分配请求,可以输出为按选择的算法(先来先服务FCFS、最短寻道时间SSTF、扫描算法SCAN、循环扫描算法C-SCAN)执行时的磁头移动轨迹。在Linux环境下实现。

关键词:操作系统;磁盘调度算法;先来先服务;最短寻道时间;Linux

目录
1 绪论… 1
1.1 设计任务… 1
1.2 设计思想… 2
1.3 基础知识… 3
1.3.1 四种基础磁盘调度算法… 3
1.3.2 操作系统的作用… 4
1.3.3 操作系统的定义及特征… 6
1.3.4 程序及进程基本概念… 7
1.3.5 进程状态及创建… 8
1.3.6 线程基本概念及实现方式… 9
1.3.7 处理机调度的层次… 9
1.3.8 典型的调度算法… 9
2 各模块伪码算法… 12
2.1 定义函数部分主要代码… 13
2.2 先来先服务(FCFS)算法… 13
2.3 最短寻道时间优先(SSTF)算法部分主要代码… 14
2.4 扫描调度(SCAN)算法… 16
2.5
循环扫描(C-SCAN)算法… 18
3 函数调用关系图… 20
4 调试及测试… 21
4.1 调试及测试所用软件介绍… 21
4.2 常用磁盘调度算法的测试界面… 21
4.2.1 测试界面图… 21
4.2.2 功能列表图… 22
4.3 测试结果… 22
4.3.1 先来先服务(FCFS)算法… 22
4.3.2 最短寻道时间(SSTF)算法… 23
4.3.3 扫描(SCAN)算法… 23
4.3.4 循环扫描(C-SCAN)算法… 24
5 源程序… 25
总结… 33
参考文献… 34
致谢… 35

1绪论

磁盘是外设中一个很常用的部分,所以,对磁盘数据的寻道时间的长短可以直接影响机器的整体运行速度的快慢。本设计为一个模拟磁盘调度算法的磁盘调度模拟系统,能够模拟先来先服务(FCFS)算法、最短寻道时间(SSTF)算法、扫描(SCAN)算法、循环扫描(C_SCAN)算法四个磁盘调度算法,输入为一组作业的磁道请求,输出为按选择的算法执行时的磁头移动轨迹。其中分为四个模块,先来先服务(FCFS)算法、最短寻道时间(SSTF)算法、扫描(SCAN)算法为基本算法,循环扫描(C_SCAN)算法。

1.1设计任务

调度磁盘I/O请求服务,采用好的方式能提高访问时间和带宽。本实验通过编程对磁盘调度算法的实现,加深对算法的理解,同时通过用C语言编写程序实现这些算法,并在Linux平台上实现,更好的掌握操作系统的原理以及实现方法,提高综合运用专业课知识的能力,熟悉并掌握磁盘调度算法管理系统的设计方法,加强对所学各种调度算法及相应算法的特点了解,掌握磁盘调度的基本概念,深刻体会各个算法的优缺点,以及算法间的相似点,就能使使用者加深对先来先服务(FCFS)算法、最短寻道时间(SSTF)算法、扫描(SCAN)算法、循环扫描(C_SCAN)算法等磁盘调度算法的理解。

选择一个自己熟悉的计算机系统和程序设计语言模拟操作系统基本功能的设计方法实现过程完成各分项功能。在算法的实现过程中,要求可决定变量应是动态可变的;同时模块应该有一个合理的输出结果。具体可参照实验的程序模拟各功能程序要求自行编写程序实现,不得调用现有操作系统提供的模块或功能函数。磁盘调度程序模拟。先来先服务调度算法.最短寻道时间优先调度,循环(SCAN)调度算法。程序设计语言自选,最终以软件(含源代码以及执行程序)和设计报告的形式提交课程设计结果.。磁盘调度让有限的资源发挥更大的作用。在多道程序设计的计算机系统中,各个进程可能会不断提出不同的对磁盘进行读/写操作的请求。由于有时候这些进程的发送请求的速度比磁盘响应的还要快,因此我们有必要为每个磁盘设备建立一个等待队列。

1.2设计思想

设备的动态分配算法与进程调度相似,也是基于一定的分配策略的。常用的分配策略有先请求先分配、优先级高者先分配等策略。在多道程序系统中,低效率通常是由于磁盘类旋转设备使用不当造成的。操作系统中,对磁盘的访问要求来自多方面,常常需要排队。这时,对众多的访问要求按一定的次序响应,会直接影响磁盘的工作效率,进而影响系统的性能。访问磁盘的时间因子由3部分构成,它们是查找(查找磁道)时间、等待(旋转等待扇区)时间和数据传输时间,其中查找时间是决定因素。因此,磁盘调度算法先考虑优化查找策略,需要时再优化旋转等待策略。

平均寻道长度(L)为所有磁道所需移动距离之和除以总的所需访问的磁道数(N),即:L=(M1+M2+….+Mi+….+MN)/N

其中Mi为所需访问的磁道号所需移动的磁道数。

启动磁盘执行输入输出操作时,要把移动臂移动到指定的柱面,再等待指定扇区的旋转到磁头位置下,然后让指定的磁头进行读写,完成信息传送。因此,执行一次输入输出所花的时间有:

寻找时间——磁头在移动臂带动下移动到指定柱面所花的时间。 延迟时间——指定扇区旋转到磁头下所需的时间。 传送时间——由磁头进程读写完成信息传送的时间。

其中传送信息所花的时间,是在硬件设计就固定的。而寻找时间和延迟时间是与信息在磁盘上的位置有关。为了减少移动臂进行移动花费的时间,每个文件

的信息不是按盘面上的磁道顺序存放满一个盘面后,再放到下一个盘面上。而是按柱面存放,同一柱面上的各磁道被放满信息后,再放到下一个柱面上。所以各磁盘的编号按柱面顺序,每个柱面按磁道顺序,每个磁道又按扇区顺序进行排序。

磁盘是可供多个进程共享的设备,当有多个进程都要求访问磁盘是,应采用一种最佳调度算法,以使各种进程对磁盘的平均访问时间最小。由于在访问磁盘的时间中,主要是寻道时间,因此,磁盘调度的目标,是使磁盘的平均寻道时间最少。目前常用的磁盘的调度算法有:先来先服务、最短寻道时间优先及扫描等算法。

存取盘中的信息一般要用三部分时间:寻道时间、旋转延迟时间、传输时间。对于大多数磁盘来说,寻到时间远远大于后两者之和,其中查找时间是决定因素。因此,减少平均寻道时间就可以显著提高系统性能。编译程序运用磁盘的四种调度算法实现对磁盘的调度,四种算法分别为先来先服务(FCFS)算法,最短寻道时间优先(SSTF)算法,扫描调度(SCAN)算法,循环扫描(C-SCAN)算法。

1.3 基础知识

1.3.1四种基础磁盘调度算法

1.先来先服务(FCFS)算法: 即先来的请求先被响应。FCFS策略看起来似乎是相当"公平"的,但是当请求的频率过高的时候FCFS策略的响应时间就会大大延长。FCFS策略为我们建立起一个随机访问机制的模型,但是假如用这个策略反复响应从里到外的请求,那么将会消耗大量的时间。为了尽量降低寻道时间,看来我们需要对等待着的请求进行适当的排序,而不是简单的使用FCFS策略。这个过程就叫做磁盘调度管理。有时候FCFS也被看作是最简单的磁盘调度算法。

2.最短寻道时间优先(SSTF)算法: 该算法选择这样的进程,其要求访问的磁道与当前磁头所在的磁道距离最近,以使每次的寻道时间最短,该算法可以得到比较好的吞吐量,但却不能保证平均寻道时间最短。其缺点是对用户的服务请求的响应机会不是均等的,因导致响应时间的变化幅度很大。在服务请求很多的情况下,对内外边缘磁道的请求将会无限期的被延迟,有些请求的响应时间将不可预期。

3.扫描调度(SCAN)算法: 该算法不仅考虑到欲访问的磁道与当前磁道间的距离,更优先考虑的是磁头当前的移动方向。例如,当磁头正在自里向外移动时,SCAN算法所考虑的下一个访问对象,应是其欲访问的磁道,既在当前磁道之外,又是距离最近的。这样自里向外的访问,直至再无更外的磁道需要访问时,才将磁道换向自外向里移动。这时,同样也是每次选择这样的进程来调度,也就是要访问的当前位置内距离最近者,这样,磁头又逐步地从外向里移动,直至再无更里面的磁道要访问,从而避免了出现“饥饿”现象。

4.循环扫描(C-SCAN)算法: 是对扫描算法的改进,当磁头刚从里向外移动而越过了某一磁道时,恰好又有一进程请求访问此磁道,这时,该里程就必须等待,为了减少这种延迟,CSCAN算法规定磁头单向移动,而本实验过程中我们所设计的是磁头从里向外移动,而从外向里移动时只须改方向而已,本实验未实现。但本实验已完全能演示循环扫描的全过程。

1.3.2操作系统的作用

1.存储管理

管理目标: 提高利用率,方便用户使用,提供足够的存储空间,方便进程并发运行。

(1)存储分配与回收

为进程分配存储空间。

(2)存储保护

防止进程间相互干扰、相互保密;如:访问合法性检查。

(3)地址映射(变换)

进程逻辑地址到内存物理地址的映射。

(4)存储扩充(覆盖、交换和虚拟存储)

在不增加物理内存的情况下向用户提供海量存储空间,提高内存利用率,扩大进程的内存空间。

2.处理机管理

在多道程序环境下,处理机的分配和运行都是以进程为基本单位,因而对处理机的管理归结为对进程的管理。

(1)进程调度

为进程分配处理机,以充分利用处理机资源和提高系统性能:核心为调度算法。

(2)进程控制

创建、撤消、挂起、封锁进程——主动改变进程的状态通过原语/系统调用实现。

(3)进程同步

协调进程间的关系,互斥/同步。

同步机制,如锁、信号量。

(4)进程通讯

进程间的信息交换。

3.设备管理

任务:分配与回收设备,驱动设备,响应I/O请求。

目的:提高I/O设备利用率,方便使用I/O设备。

(1)缓冲管理

解决CPU与I/O速度不匹配,提高两者的利用率,缓冲机制:单缓冲、双缓冲、缓冲池。

(2)设备分配与回收

根据用户的I/O请求和相应的分配策略,为该用户分配外设以及通道,控制器等,在多用户间共享I/O设备资源。

虚拟设备:将一个物理设备变换为多个与之对应的逻辑设备,供多个用户共享。

(3)设备处理

控制设备工作,处理中断请求,设备驱动程序,实现CPU与通道和外设之间的通信。

(4)设备独立性

设备独立性:指用户程序与物理设备无关,即提供统一的I/O设备接口,使应用程序独立于物理设备,提高可适应性。

4.文件管理

解决软件资源的存储、共享、保密和保护。

(1)文件存储空间管理

分配、回收文件空间,解决如何存放信息,以提高空间利用率和读写性能。

(2)目录管理

解决信息检索问题。实现文件按名存取,共享与保护。

(3)文件读写和存取控制

实现对文件的具体访问,防止文件被坏。

(4)文件操作的一般管理

实现对文件的创建、删除、打开、关闭等操作。

5.用户接口

目标:提供一个友好的用户访问操作系统的接口。操作系统向上提供三种接口。

(1)命令接口

键盘命令:供用户用于组织和控制自己的作业运行。

(2)程序接口(编程接口)

系统调用:供用户程序和系统程序调用操作系统功能。

(3)图形接口

窗口菜单:用户利用鼠标、图标等图形用户界面工具,可以直观,方便,有效地使用系统服务和各种应用程序和实用工具。

1.3.3操作系统的定义及特征

是管理和控制计算机系统中各种硬件和软件资源、合理地组织计算机工作流程的系统软件,是用户与计算机之间的接口。

1.多道批处理系统

(1)在内存中同时存放多道程序,在管理程序的控制下交替执行,这些作业共享

CPU和系统其他资源。

(2)多道程序运行的特点:

多道:内存中同时存放几道独立的程序。

宏观上并行:多道程序都处于运行状态,但都未运行完。

微观上串行:多道程序交替使用。

(3)用户提交的作业都先存放在外存上,并排成一个“作业队列”,等待调度作业调度程序按一定的调度原则从作业队列中选择若干个作业进入内存,使它们交替运行,并共享CPU和系统各种资源。

2.分时系统

把处理机运行时间分成时间片,按时间片轮转的方式,把处理机分配给各联机作业使用。允许多个用户与计算机直接交互。

3.实时系统

系统能及时(或即时)响应外部事件的请求,在规定的时间内完成对该事件的处理,并控制所有实时任务协调一致地运行。提供即时响应和高可靠性,响应时间快,可以在毫秒级甚至微秒级立即处理。

4.并发

并发是指两个或多个事件在同一时间间隔内发生。微观上还是程序在分时地交替执行。

5.共享

共享是指系统中的资源可供内存中多个并发执行的进程共同使用。

(1)互斥共享方式

如打印机、磁带机。在一段时间内只允许个进程访问该资源。

(2)同时访问方式

如磁盘设备。

6.虚拟

虚拟是指把一个物理上的实体变为若干个逻辑上的对应物。比如说虚拟处理器,虚拟内存,虚拟外部设备。

7.异步

在多道程序环境下,允许多个程序并发执行,但由于资源有限,进程的0行不是一贯到底,而是走走停停,以不可预知的速度向前推进,这就是进程的异步性,操作系统最基本的特征是并发和共享,两者互为存在条件。

1.3.4程序及进程基本概念

1.程序执行的两种方式

(1)顺序执行: 一个计算的若干操作必须按照严格的先后次序顺序地执行,这类计算过程就是程序的顺序执行过程。

(2)并发执行:指一组在逻辑上相互独立的程序或程序段在执行过程中,其执行时间在客观上相互重叠,即一个程序段的执行尚未结束,另一个程序段的执行已经开始的这种执行方式。

2.进程的定义

进程是指一个具有独立功能的程序对某个数据集在处理机上的执行过程和分配资源的基本单位。引入进程的概念,以便更好地描述和控制程序的并发执行。

程序封闭性是指进程执行的结果只取决于进程本身,不会受外界影响。

进程和程序的区另

(1)进程是动态的,程序是静态的

程序本身可以作为软件资源而长期存在;进程是程序的一次执行过程,有一定的生命期。

(2)进程具有并发特征,而程序没有

进程是一个能够独立运行的单位,是作为资源申请和调度单位存在的,能与其他进程井发执行;程序不能作为一个独立运行的单位而并发执行。

(3)程序和进程没有一一对应关系

通过多次执行,一个程序可对应多个进程:通过调用关系,一个进程可包括多个程序。

(4)各个进程在并发执行过程中会产生相互制约关系

因为进程是竞争资源的基本单位,从而其井发性受到系统自己的制约,而程序却没有。

4.进程的组成

进程通常由程序,数据集合和进程控制块PCB三部分组成。程序和它操作的数据是进程存在的静态实体,而专门的数据结构PCB用来描述进程当前的状态、本身的特性等。

当进程被中断时,操作系统会把程序计数器和处理器寄存器(上下文数据)保存在PCB中的对应位置,进程状态已被改变为其他的值,例如阻塞态或就绪态。PCB是进程存在的唯一标志。故操作系统是根据进程控制块来对并发执行的进程进行控制和管理。

PCB内含的数据结构主要有:进程标志信息、进程控制信息、进程资源信息、CPU现场信息。

每个进程包含独立的地址空间,进程各自的地址空间是私有的,只有执行自己地址空间中的程序,且只能访问自己地址空间中的数据,相互访问会导致指针的越界错误。

对进程的管理和控制功能是通过执行各种原语实现的,如创建原语。

1.3.5进程状态及创建

1.进程的三种基本状态:

(1)运行态

(2)就绪态

(3)阻塞态

2.创建进程:

(1)给新进程分配一个唯一的进程标识符

(2)给进程分配空间

(3)初始化进程控制块

(4)设置正确的连接

(5)创建或扩充其他数据结构

1.3.6线程基本概念及实现方式

1.线程基本概念

引入线程,是为了减小程序在并发执行时所付出的时空开销,提高操作系统的并发性能。

线程最直接的理解就是“轻量级进程”,它是一个基本的CPU执行单元,也是程序执行流的最小单元。

线程共享进程拥有的全部资源。

线程不拥有系统资源,但是它可以访问进程所拥有的系统资源。

线程没有自己独立的地址空间,他共享他所属的进程的空间。

线程的实现方式

(1)用户级线程

(2)内核级线程

1.3.7处理机调度的层次

  1. 作业调度,又称高级调度。就是内存与辅存之间的调度。

  2. 中级调度。又称内存调度。引入中级调度是为了提高内存利用率和系统吞吐量。使那些暂时不能运行的进程,调至外存等待,把此时的进程状态称为挂起状态。当他们具备运行条件且内存又稍有空闲时,由中级调度来决定,把外存上的那些已具备运行条件的就绪进程再重新调入内存。

  3. 进程调度。又称为低级调度。按照某种方法私策略从就绪队列中选取一个进程给CPU。

1.3.8典型的调度算法

  1. 先来先服务调度算法(FCFS)

  2. 短作业优先(SJF)调度算法

从后备队列中选择一个或若干个估计运行时间最短的作业,将他们调入内存运行。

  1. 短进程优先(SPF)调度算法

从就绪队列中选择一个估计运行时间最短的进程,将处理机分配给它,使之立即执行。

优先级调度算法

根据能否抢占进程,可将调度算法分为:

(1)非剥夺式优先级调度算法

(2)剥夺式优先级调度算法

根据进程创建后其优先级是否可以改变,分为:

(3)静态优先级。优先级在创建进程时确定且在进程的整个运行期间保持不变。

(4)动态优先级。可动态调整优先级。

  1. 高响应比优先调度算法

高响应比优先调度算法主要用于作业调度,该算法是对FCFS调度算法和SJF调度算法的种综合平衡,同时考虑每个作业的等待时间和估计的运行时间。在每次进行作业调度时,先计算后备作业队列中每个作业的响应比,从中选出响应比最高的作业投入运行。

(1)当作业的等待时间相同时,则要求服务时间越短,其响应比越高,有利于短作业。

(2)当要求服务时间相同时,作业的响应比由其等待时间决定,等待时间越长,其响应比越高,因而它实现的是先来先服务

(3)对于长作业,作业的响应比可以随等待时间的增加而提高,当其等待时间足够长时,其响应比便可升到很高,从而也可获得处理机。克服了饥饿状态,兼顾了长作业。

6.时间片轮转调度算法

7.多级反馈队列调度算法

多级反馈队列调度算法的实现思想如下:

(1)应设置多个就绪队列,并为各个队列赋予不同的优先级,第1级队列的优先级最高,第2级队列次之,其余队列的优先级逐次降低。

(2)赋予各个队列中进程执行时间片的大小也各不相同,在优先级越高的队列中,每个进程的运行时间片就越小。例如,第2级队列的时间片要比第1级队列的时间片长1倍…第计1级队列的时间片要比第i级队列的时间片长1倍。

(3)当一个新进程进入内存后,首先将它放入第1级队列的末尾,按FCFS原则排队等待调度。当轮到该进程执行时,如它能在该时间片内完成,便可准备撤离系统;如果它在一个时间片结束时尚未完成,调度程序便将该进程转入第2级队列的末尾,再同样地按FCFS原则等待调度执行;如果它在第2级队列中运行一个时间片后仍未完成,再以同样的方法放入第3级队列如此下去,当一个长进程从第1级队列依次降到第n级队列后,在第n级队列中便采用时间片轮转的方式运行。

(4)仅当第1级队列为空时,调度程序才调度第2级队列中的进程运行;仅当第1(i-1)级队列均为空时,才会调度第i级队列中的进程运行。如果处理机正在执行第i级队列中的某进程时又有新进程进入优先级较高的队列(第1(i-1)中的任何一个队列),则此时新进程将抢占正在运行进程的处理机,即由调度程序把正在运行的进程放回到第i级队列的末尾,把处理机分配给新到的更高优先级的进程。

2各模块伪码算法

磁盘调度算法共分四种:先来先服务(FCFS)算法、最短寻道时间优先(SSTF)算法、扫描调度(SCAN)算法、循环扫描(C-SCAN)算法。故本设计有一个主函数定义模块和四个调度算法模块,共计五个模块,如图2.1所示。

图2.1程序流程图

2.1定义函数部分主要代码

此模块为整个程序代码段的主函数。它主要定义了其它的四个程序子模块。分别是:先来先服务(FCFS)算法、最短寻道时间优先(SSTF)算法、扫描调度(SCAN)算法、循环扫描(C-SCAN)算法。

#include

#include

using
namespace std;

void FCFS(int
a[],int n);

void SSTF(int
a[],int n);

void SCAN(int
a[],int n);

void CSCAN(int
a[],int n);

2.2先来先服务(FCFS)算法

这是一种简单的磁盘调度算法。它根据进程请求访问磁盘的先后次序进行调度,磁头访问序列不变,依次读出。此算法的优点是公平、简单,且每个进程的请求都能依次得到处理,不会出现某一进程的请求长期得不到满足的情况。但此算法由于未对寻道进行优化,致使平均寻道时间可能较长。

优点:简单,公平;

缺点:效率不高,相邻两次请求可能会造成最内到最外的柱面寻道,使磁头反复移动,增加了服务时间,对机械也不利。

输入磁道号,按先来先服务的策略输出磁盘请求序列,求平均寻道长度,输出移动平均磁道数,如图2.2所示。

部分主要代码:

void FCFS(int
a[],int n)

{

int sum=0,j,i,first=0,now;

cout<<“请输入当前磁道号:”;

cin>>now;//确定当前磁头所在位置

cout<<“磁盘调度顺序为:”<<endl;

for(
i=0;i<n;i++){

cout<<a[i]<<"
";}

for(i=0,j=1;j<n;i++,j++){

first+=abs(a[j]-a[i]);}

sum+=first+abs(now-a[0]);

cout<<endl;

cout<<"移动的总磁道数为: "<<sum<<endl;}

图2.2 先来先服务算法流程图

2.3最短寻道时间优先(SSTF)算法部分主要代码

该算法选择这样的进程,其要求访问的磁道与当前磁头所在的磁道距离最近,以使每次的寻道时间最短,将磁道序号由小到大排列,将当前磁头位置虚拟插入,找出位置所在。判断左右两端哪个位置距离该位置较近,即为下一个访问号,并据此方向至最大值或最小值,接着磁头方向转变,跳过已访问过的磁道,顺势完成后半部分的调度。但这种调度算法却不能保证平均寻道时间最短,优先选择距当前磁头最近的访问请求进行服务,主要考虑寻道优先。

优点:改善了磁盘平均服务时间,相较于先来先服务算法(FCFS)有更好的寻道性能,使每次的寻道时间最短。

缺点:造成某些访问请求长期等待得不到服务,易造成某个进程发生“饥饿”现象。

将磁道号用冒泡法从小到大排序,输出排好序的磁道序列,输入当前磁道号,根据前磁道在已排的序列中的位置,选择扫描的顺序,求出平均寻道长度,输出移动的平均磁道数,如图2.3所示。

部分主要代码:

for(i=0;i<n;i++)

for(j=i+1;j<n;j++)

{

if(a[i]>a[j])

{

temp=a[i];

a[i]=a[j];

a[j]=temp;

}

}

if(a[n-1]<=now)//当前磁头位置大于最外围欲访问磁道

{

for(i=n-1;i>=0;i–)

cout<<a[i]<<" ";

sum=now-a[0];

}

if(l=-1)//磁头位置里侧的磁道已访问完

{

for(j=r;j<n;j++)//访问磁头位置外侧的磁道

{

cout<<a[j]<<" ";

}

sum+=a[n-1]-a[0];

}

if(r==n)//磁头位置外侧的磁道已访问完

{

for(j=k-1;j>-1;j–) //访问磁头位置里侧的磁道

       {

           cout<<a[j]<<"

";

       }

       sum+=a[n-1]-a[0];

}

}

cout<<endl;

cout<<“移动的总道数为:”<<sum<<endl;

}

图 2.3 最短寻道时间优先算法流程图

2.4扫描调度(SCAN)算法

此算法克服了最短寻道优先的缺点,既考虑了距离,同时又考虑了方向。当设备无访问请求时,磁头不动;当有访问请求时,磁头按一个方向移动,在移动过程中对遇到的访问请求进行服务,然后判断该方向上是否还有访问请求,如果有则继续扫描;否则改变移动方向,并为经过的访问请求服务,如此反复,SCAN算法又称电梯调度算法,SCAN算法是磁头前进方向上的最短查找时间优先算法,如图2.4所示

优点:排除了磁头在盘面局部位置上的往复移动,SCAN算法在很大程度上消除了SSTF算法的不公平性,但仍有利于对中间磁道的请求。

缺点:新进来的访问此磁道的进程的请求会被大大地推迟,增加延迟。SCAN算法又称电梯调度算法。SCAN算法是磁头前进方向上的最短查找时间优先算法。

部分主要代码:

void SCAN(int
a[],int n)

{

int temp;

int k=1;

int now,l,r;

int i,j,sum=0;

for(i=0;i<n;i++)

if(a[n-1]<=now) //磁头位置大于最外围欲访问磁道

{

for(i=n-1;i>=0;i–)

cout<<a[i]<<" ";

sum=now-a[0];

}

图2.4 扫描调度算法流程图

2.5循环扫描(C-SCAN)算法

循环扫描算法也称单向扫描算法,电梯算法杜绝了饥饿,但当请求对磁道的分布是均匀时,磁头回头,近磁头端的请求很少(因为磁头刚经过),而远端请求较多,这些请求等待时间要长一些。

总是从0号柱面开始向里扫描。移动臂到达最后个一个柱面后,立即带动读写磁头快速返回到0号柱面。返回时不为任何的等待访问者服务。返回后可再次进行扫描,如图2.5所示。

部分主要代码:

void CSCAN(int
a[],int n)

{

int temp;

int now,l,r;

int i,j,sum=0;

int k=1;

for(i=0;i<n;i++)

if(a[n-1]<=now)//磁头位置大于最外围欲访问磁道

{

for(i=0;i<n;i++)

cout<<a[i]<<" ";

sum=now-2*a[0]+a[n-1];

}

3函数调用关系图

磁盘调度模拟系统主要分为五个部分。分别是先来先服务算法模块、最短寻道时间优先模块、扫描算法模块、循环扫描算法模块和退出。

  1. 先来先服务(FCFS)算法。即先来的请求先被响应。FCFS策略看起来似乎是相当"公平"的,但是当请求的频率过高的时候FCFS策略的响应时间就会大大延长。FCFS策略为我们建立起一个随机访问机制的模型,但是假如用这个策略反复响应从里到外的请求,那么将会消耗大量的时间。为了尽量降低寻道时间,看来我们需要对等待着的请求进行适当的排序,而不是简单的使用FCFS策略。这个过程就叫做磁盘调度管理。有时候FCFS也被看作是最简单的磁盘调度算法。

  2. 最短寻道时间优先(SSTF)算法。要求访问的磁道,与当前磁头所在的磁道距离最近,以使每次的寻道时间最短。

  3. 扫描调度(SCAN)算法。该算法不仅考虑到欲访问的磁道与当前磁道间的距离,更优先考虑的是磁头当前的移动方向。SCAN算法所考虑的下一个访问对象,应是其欲访问的磁道,既在当前磁道之外,又是距离最近的。这样自里向外的访问,直至再无更外的磁道需要访问时,才将磁道换向自外向里移动。这时,同样也是每次选择这样的进程来调度,也就是要访问的当前位置内距离最近者,这样,磁头又逐步地从外向里移动,直至再无更里面的磁道要访问,从而避免了出现“饥饿”现像。

  4. 循环扫描(C-SCAN)算法。当磁头刚从里向外移动而越过了某一磁道时,恰好又有一进程请求访问此磁道,这时,该里程就必须等待,为了减少这种延迟,CSCAN算法规定磁头单向移动,而本实验过程中我们所设计的是磁头从里向外移动,而从外向里移动时只须改方向而已,本实验未实现。但本实验已完全能演示循环扫描的全过程,如图3.1所示。

4调试及测试

4.1调试及测试所用软件介绍

vim简介及其优点:Vim是从vi发展出来的一个文本编辑器。代码补全、编译及错误跳转等方便编程的功能特别丰富,在程序员中被广泛使用,和Emacs并列成为类Unix系统用户最喜欢的文本编辑器。具有三种模式选择。

(1)命令模式(默认):刚进入vim的时候,默认就是命令模式,可以复制行,删除行等。

(2)输入模式:可以输入内容。

(3)末行模式:在最下边,除编辑模式,可以输入诸多管理员命令

g++是GNU组织开发出的编译器软件集合(GCC)下的一个C++编译器。它是Unix 和 Linux系统下标配的 基于命令行的 C++编译器。

ubuntu是一个以桌面应用为主的Linux操作系统。

4.2常用磁盘调度算法的测试界面

4.2.1测试界面图

在我们对程序进行编写和调试之后。我们对程序进行下一步的测试以判断四大磁盘算法(先来先服务算法、最短寻道时间算法、扫描算法、循环扫描算法)的功能。

在程序测试界面,输入当前磁道的个数按Enter键生成随机磁道号。如图4.1所示。

图4.1测试界面图

4.2.2功能列表图

生成随机磁道号后,输入所需选择功能的前置编号,并输入当前磁道号 ,如图4.2所示

图4.2功能列表图

4.3测试结果

4.3.1先来先服务(FCFS)算法

输入磁道号,按先来先服务的策略输出磁盘请求序列,求平均寻道长度,输出移动平均磁道数。这是一种简单的磁盘调度算法。它根据进程请求访问磁盘的先后次序进行调度。此算法的优点是公平、简单,且每个进程的请求都能依次得到处理,不会出现某一进程的请求长期得不到满足的情况。但此算法由于未对寻道进行优化,致使平均寻道时间可能较长。

在程序测试界面,我们按下Enter键随机产生磁道号(磁道号的个数可以由测试者随意决定)。在磁盘调度算法功能列表选择先来先服务算法,再确定当前磁道号,随后的测试结果有磁盘调度的顺序以及移动的总磁道数,如图4.3所示。

图4.3先来先服务算法测试图

4.3.2最短寻道时间(SSTF)算法

将磁道号用冒泡法从小到大排序,输出排好序的磁道序列,输入当前磁道号,根据前磁道在已排的序列中的位置,选择扫描的顺序,求出移动的总磁道数。该算法选择这样的进程,其要求访问的磁道与当前磁头所在的磁道距离最近,以使每次的寻道时间最短,但这种调度算法却不能保证平均寻道时间最短。

在选择界面选择最短寻道时间算法,确定当前磁道号。然后由程序出结果(磁盘调度顺序以及移动的总磁道数),如图4.4所示。

图4.4最短寻––道时间算法测试图

4.3.3扫描(SCAN)算法

将磁道号用冒泡法从小到大排序,输出排好序的序列,输入当前磁道号,选择移动臂的移动方向,根据当前磁道在已排的序列中的位置,选择扫描的顺序,求出平均寻道长度,输出移动的平均磁道数。SCAN算法不仅考虑到欲访问的磁道与当前磁道的距离,更优先考虑的是磁头的当前移动方向。例如,当磁头正在自里向外移动时,SCAN算法所选择的下一个访问对象应是其欲访问的磁道既在当前磁道之外,又是距离最近的。这样自里向外地访问,直到再无更外的磁道需要访问才将磁臂换向,自外向里移动。这时,同样也是每次选择这样的进程来调度,即其要访问的磁道,在当前磁道之内,从而避免了饥饿现象的出现。由于这种算法中磁头移动的规律颇似电梯的运行,故又称为电梯调度算法。

扫描算法又称电梯算法,因此这种算法有两种结果一是向小的方向二是向大的方向。在确定当前磁道号之后,进入程序测试得到的结果一是递增二是递减,如图4.5所示。

图4.5扫描算法测试图

4.3.4循环扫描(C-SCAN)算法

循环扫描算法和扫描算法一样,同样有两个方向、两种结果,这里只显示磁头向外的方向,如图4.6所示。

图4.6循环扫描算法测试图

5源程序

#include

#include

using
namespace std;

void FCFS(int
a[],int n);

void SSTF(int
a[],int n);

void SCAN(int
a[],int n);

void CSCAN(int
a[],int n);

int main()

{

int n; //磁道的个数

int s; //功能号

cout<<"----------------------------------------------------------------------------"<<endl;

cout<<" 2019年秋季学期《操作系统课程设计》 "<<endl;

cout<<"
"<<endl;

cout<<" 课程名称:常用磁盘调度算法的实现 "<<endl;

cout<<"
"<<endl;

cout<<" 班级:2017级软件工程1班 "<<endl;

cout<<" "<<endl;

cout<<" 设计人:徐建国 武鹏国 杨广玺 "<<endl;

cout<<"----------------------------------------------------------------------------"<<endl;

cout<<" 欢迎使用!!! "<<endl;

cout<<"
"<<endl;

cout<<"
"<<endl;

cout<<"
"<<endl;

cout<<“请输入当前磁道的个数,按Enter键显示生成的随机磁道号:”<<endl;

cin>>n;

int *a=new int[n];

cout<<“生成的随机磁道号为:”;

srand((unsigned)time(NULL)); //srand()函数获取随机数

for(int i=0;i<n;i++)

{

  a[i]=(rand()%100)+1;   //rand按顺序获取srand()里面的数字

  cout<<a[i]<<" ";

}

cout<<endl;

while(1)

{ cout<<endl;

cout<<"
┌────────────────────────┐"<<endl;

cout<<" │磁盘调度算法功能列 │"<<endl;

cout<<" ├────────────────────────┤"<<endl;

cout<<" │1、先来先服务算法 │"<<endl;

cout<<" ├────────────────────────┤"<<endl;

cout<<" │2、最短寻道时间算法 │"<<endl;

cout<<" ├────────────────────────┤"<<endl;

cout<<" │3、扫描算法 │"<<endl;

cout<<" ├────────────────────────┤"<<endl;

cout<<" │4、循环扫描算法 │"<<endl;

cout<<" ├────────────────────────┤"<<endl;

cout<<" │0、退出 │"<<endl;

cout<<" └────────────────────────┘"<<endl;

cout<<endl;

cout<<“请选择所需功能的前置编号:”;

cin>>s;

if(s>4)

{

   cout<<"数据输入有误!请重新输入:"<<endl;

}

else

{

 switch(s)

{ case 0: exit(0);break ;

         case 1:FCFS(a,n); break;

         case 2:SSTF(a, n);break;

         case 3:SCAN(a, n);break;

         case 4:CSCAN(a,n);break;

    }

}

}

    return 0; 

}

//先来先服务调度算法(FCFS)

void FCFS(int
a[],int n)

{

int sum=0,j,i,first=0,now;

cout<<“请输入当前磁道号:”;

cin>>now;//确定当前磁头所在位置

cout<<“磁盘调度顺序为:”<<endl;

for(
i=0;i<n;i++)//按访问顺序输出磁道号

{

cout<<a[i]<<"
";

}

//计算sum

for(i=0,j=1;j<n;i++,j++)

{

first+=abs(a[j]-a[i]);//外围磁道与最里面磁道的距离

}

sum+=first+abs(now-a[0]);

cout<<endl;

cout<<"移动的总磁道数为:
"<<sum<<endl;

cout<<“平均磁道数为:”<<sum/n<<endl;

}

//最短寻道时间算法(SSTF)

void SSTF(int
a[],int n)

{

int temp;

int k=1;

int now,l,r;

int i,j,sum=0;

//将磁道号按递增排序

for(i=0;i<n;i++)

for(j=i+1;j<n;j++)

{

if(a[i]>a[j])

{

temp=a[i];

a[i]=a[j];

a[j]=temp;

}

}

cout<<“按递增顺序排好的磁道显示为:”<<endl;

for(
i=0;i<n;i++)

{

cout<<a[i]<<"
";//输出排好的磁道顺序

}

cout<<endl;

cout<<“请输入当前的磁道号:”;

cin>>now;//确定当前磁头所在位置

cout<<“磁盘调度顺序为:”<<endl;

if(a[n-1]<=now)//当前磁头位置大于最外围欲访问磁道

{

for(i=n-1;i>=0;i–)

cout<<a[i]<<" ";

sum=now-a[0];

}

else

if(a[0]>=now)//当前磁头位置小于最里欲访问磁道

{

for(i=0;i<n;i++)

cout<<a[i]<<" ";

sum=a[n-1]-now;

}

else

{

while(a[k]<now)//确定当前磁道在已排的序列中的位置

   {

 k++;

   }

 l=k-1;//在磁头位置的前一个欲访问磁道

 r=k;//磁头欲访问磁道



while((l>=0)&&(r<n))

   {

 if((now-a[l])<=(a[r]-now))//选择离磁头近的磁道

    {

  cout<<a[l]<<" ";

  sum+=now-a[l];

  now=a[l];

  l=l-1;

    }

else

{

cout<<a[r]<<" ";

sum+=a[r]-now;

now=a[r];

r=r+1;

}

}

if(l=-1)//磁头位置里侧的磁道已访问完

{

for(j=r;j<n;j++)//访问磁头位置外侧的磁道

{

cout<<a[j]<<" ";

}

sum+=a[n-1]-a[0];

}

if(r==n)//磁头位置外侧的磁道已访问完

{

for(j=k-1;j>-1;j–)
//访问磁头位置里侧的磁道

       {

           cout<<a[j]<<"

";

       }

       sum+=a[n-1]-a[0];

}

}

cout<<endl;

cout<<“移动的总道数为:”<<sum<<endl;

cout<<“平均磁道数为:”<<sum/n<<endl;

}

//扫描算法(SCAN)

void SCAN(int
a[],int n)

{

int temp;

int k=1;

int now,l,r;

int i,j,sum=0;

for(i=0;i<n;i++)//对访问磁道按由小到大顺序排列输出

for(j=i+1;j<n;j++)

{

if(a[i]>a[j])

{

temp=a[i];

a[i]=a[j];

a[j]=temp;

}

}

cout<<“按递增顺序排好的磁道为:”<<endl;

for(
i=0;i<n;i++)

{

cout<<a[i]<<"
";

}

cout<<endl;

cout<<“请输入当前的磁道号:”;

cin>>now;

//以下算法确定磁道访问顺序

if(a[n-1]<=now) //磁头位置大于最外围欲访问磁道

{

   for(i=n-1;i>=0;i--)

   cout<<a[i]<<" ";

   sum=now-a[0];

}

else

   if(a[0]>=now) //磁头位置小于最里欲访问磁道

   {

      for(i=0;i<n;i++)

      cout<<a[i]<<"

";

      sum=a[n-1]-now;

   }

   else                   //磁头位置在最里侧磁道与最外侧磁道之间

   {  

int d;

       while(a[k]<now)

       {             //确定当前磁道在已排的序列中的位置

           k++;

       }

       l=k-1;//在磁头位置的前一个欲访问磁道

       r=k;   //磁头欲访问磁道

       cout<<"请输入当前磁头移动的方向 (0 表示向内 ,1表示向外) : ";

       cin>>d;    //确定磁头访问的方向

       cout<<"磁盘调度顺序为:";

       if(d==0||d==1)

{

              if(d==0)    //磁头向内

                          {

                for(j=l;j>=0;j--)

                               {

cout<<a[j]<<" ";

                               }

                for(j=r;j<n;j++)

                               {

                 cout<<a[j]<<"

";

                               }

                 sum=now-2*a[0]+a[n-1];

                          }

                if(d==1)               //磁头向外

                               { 

                                      for(j=r;j<n;j++)

                                      {

cout<<a[j]<<" ";

                                      }

                    for(j=l;j>=0;j--)

                                      {

cout<<a[j]<<" ";

                                      }

                     sum=2*a[n-1]-now-a[0];

                               }

}

    else

                 cout<<"请输入0或1!"<<endl;

          }

cout<<endl;

cout<<"移动的总道数为:
"<<sum<<endl;

cout<<“平均磁道数为:”<<sum/n<<endl;

}

//循环扫描算法(CSCAN)

void CSCAN(int
a[],int n)

{

int temp;

int now,l,r;

int i,j,sum=0;

int k=1;

for(i=0;i<n;i++)//对访问磁道按由小到大顺序排列输出

for(j=i+1;j<n;j++)

{

if(a[i]>a[j])

   {

 temp=a[i];

a[i]=a[j];

a[j]=temp;

   }

}

cout<<“按递增顺序排好的磁道为:”<<endl;

for( i=0;i<n;i++)

{

cout<<a[i]<<" ";

}

cout<<endl;

cout<<“请输入当前的磁道号:”;

cin>>now;//确定当前磁道号

if(a[n-1]<=now)//磁头位置大于最外围欲访问磁道

{

for(i=0;i<n;i++)

    cout<<a[i]<<" ";

    sum=now-2*a[0]+a[n-1];

}

else

    if(a[0]>=now)//磁头位置小于最里欲访问磁道

    {

        for(i=0;i<n;i++)

        cout<<a[i]<<"

";

        sum=a[n-1]-now;

    }

    else //磁头位置在最里侧磁道与最外侧磁道之间

    {   

int d;

        while(a[k]<now)

        {

            k++;

        }

       l=k-1;//在磁头位置的前一个欲访问磁道

       r=k;   //磁头欲访问磁道

cout<<"请输入当前磁头移动的方向 (0 表示向内 ,1表示向外) : ";

       cin>>d;    //确定磁头访问的方向

cout<<“磁盘调度顺序为:”;

       if(d==0||d==1)

{

           if(d==1)    //磁头向外侧访问

                    {

            for(j=r;j<n;j++)//先访问外侧磁道再转向最里欲访问磁道

                        {

             cout<<a[j]<<"

";

                        }

            for(j=0;j<r;j++)

                        {

             cout<<a[j]<<"

";

                        }

             sum=2*a[n-1]-now-2*a[0]+a[l];

                    }

          if(d==0)                //磁头向内侧访问

                   { 

                         for(j=r-1;j>=0;j--)

                         {

             cout<<a[j]<<"

";

                         }

                      for(j=n-1;j>=r;j--)

                         {

             cout<<a[j]<<"

";

                         }

                         sum=2*a[n-1]-2*a[0]+now-a[r];

                   }

}

else

cout<<“请输入0或1!”;

   }

cout<<endl;

cout<<"移动的总道数为:
"<<sum<<endl;

cout<<“平均磁道数为:”<<sum/n<<endl;

}

总结

从算法本身来看,先来先服务算法应该是最公平、简单的一种了,来一个处理一个,每个请求都会得到处理,但是缺点也明显:大大增加了磁头移动距离;最短寻道时间算法要求访问的磁道与当前磁头所在的磁道距离最近,以使每次的寻道时间最短,看起来每次的磁头移动距离是最短的,但是这种调度算法却不能保证平均寻道时间最短;电梯算法中磁头移动的规律颇似电梯的运行,其不仅考虑到欲访问的磁道与当前磁道的距离,更优先考虑的是磁头的当前移动方向。从大局观上是比较省事的。

但是从结果上来看。却有两种不同结局,第二组数据结果是最短寻道时间算法比较好;第一组数据结果却是电梯算法比较省力。经过分析导致这种不同的原因是第一组数据其中几个数字比较接近、集中,电梯算法刚好发挥优势,省去不必要的寻道路径,可以一次性经过处理。如果用最短寻道时间算法,这种有时会在“选择一个方向继续下去”形势下来回奔波失去这种优势。而看第二组数据,数据分散而且有几个特别大的数据,电梯算法有一个“回返”过程,并且这个回返过程是不处理数据的,这个距离足可以抵消最短寻道的来回奔波时间。

参考文献

[1]汤子瀛 ,哲凤屏.《计算机操作系统》[M].西安电子科技大学出版社.

[2]王清,李光明.《计算机操作系统》[M].冶金工业出版社.

[3]孙钟秀等.《操作系统教程》[M].高等教育出版社.

[4]曾明.《Linux操作系统应用教程》[M].陕西科学技术出版社.

[5]张丽芬,刘利雄.《操作系统实验教程》[M].清华大学出版社.

[6]孟静.《操作系统教程——原理和实例分析》[M].高等教育出版社.

[7]周长林.《计算机操作系统教程》[M].高等教育出版社.

[8]张尧学.《计算机操作系统教程》[M].清华大学出版社.

[9]任满杰.《操作系统原理实用教程》[M].电子工业出版社.

[10]张坤.《操作系统实验教程》[M].清华大学出版社.

致谢

在这几天的课程设计中,由于之前做过相似的实验,流程图框架刚开始就确立了,不过在接下来的编写代码的阶段里,出现很大的问题,花费了很多的时间。好在有老师的耐心细心的指导,一步一步的验证,一点一点的改正。每一次的运行看到错误都在慢慢的减少,正确的设计结果也在不断的靠近,最终取得了成功。由于自己的知识和能力还不到位,在课程设计时间里经历了很多困难和挑战,但我认为,在这过程中的每一次的错误和故障,都使我收获颇丰,使我成长了很多。

当然,这个磁盘调度系统的设计远非完美,还有很多地方可以改进,例如界面可以更加友好,资源可以更加节约,算法也还有优化的余地,但是时间有限,经历也有限,在课程设计时间允许的范围内只能做到这样,我会在课余时间自行完善该磁盘调度算法程序。

每一次的课程设计都是自己对所学知识的强化,是一次难得的动手机会。在课程设计的每一个步骤的执行中,都要认真的反复的去做,因为一个小小的错误都会导致课程设计结果发生巨大的偏差。完成一个成功的设计,会让自己学会很多很多的东西,并且能够很清楚的看到自己的不足,查补缺漏,继续学习。通过自己的动手动脑,既增加了知识,又给了我专业知识以及专业技能上的提升,对提高自己的思维能力和操作能力有很大的帮助。同时我也会更加努力,认真学习,争取在以后的课程中做得更好!

  • 8
    点赞
  • 0
    评论
  • 45
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 黑客帝国 设计师:白松林 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值