SPOOLING技术模拟实现.docx
- 文档编号:18540414
- 上传时间:2023-08-19
- 格式:DOCX
- 页数:32
- 大小:205.53KB
SPOOLING技术模拟实现.docx
《SPOOLING技术模拟实现.docx》由会员分享,可在线阅读,更多相关《SPOOLING技术模拟实现.docx(32页珍藏版)》请在冰点文库上搜索。
SPOOLING技术模拟实现
摘要
SPOOLing技术实际上是一种外围设备同时联机操作技术。
它在输入和输出之间增加了“输入井”和“输出井”的排队转储环节,以消除用户的“联机”等待时间。
在系统输入模块收到作业输入请求信号后,输入管理模块中的读过程负责将信息从输入装置中读入输入井缓冲区。
当缓冲区满时,由写过程将信息从缓冲区写到外存的输入井中,读过程和写过程反复循环,直到一个作业输入完毕。
当读过程读到一个硬件结束标志之后,系统再次驱动写过程把最后一批信息写入外存输入井并调用中断处理程序结束该次输入。
然后,系统为该作业建立作业控制块,从而使输入井中的作业进入作业等待队列,等待作业调度程序选中后进入内存运行。
系统在管理输入井过程中可以“不断”读入输入的作业,直到输入结束或输入井满而暂停。
在SPOOLing系统中,实际上并没有为任何进程分配,而只是在输入井和输出井中,为进程分配一存储区并建立一张I/O请求表。
这样,便把独占设备改造为共享设备。
宏观上,虽然是多个进程在同时使用一台独立设备,而对每一个进程而言,它们都认为自己是独占了一个设备。
当然,该设备只是逻辑上的设备。
SPOOLing系统实现了将独占设备变换为若干台对应的逻辑设备的功能。
关键字:
输入井;输出井;存输出进程
1概述
Spooling,即外围设备联机并行操作,它除了是一种速度匹配技术外、也是一种虚拟设备技术。
用一种物理设备模拟另一类物理设备,使各作业在执行期间只使用虚拟的设备,而不直接使用物理的独占设备。
这种技术可使独占的设备变成可共享的设备,使得设备的利用率和系统效率都能得到提高。
为了缓和CPU的高速性与I/O设备低速性之间的矛盾而引入了脱机输入、脱机输出技术,该技术是利用专门的外围控制机,将低速I/O设备上的数据传送到高速磁盘上;或者相反。
事实上,当系统中引入了多道程序技术后,完全可以利用其中的一道程序,来模拟脱机输入时的外围控制机功能,把低速I/O设备上的数据传送到高速磁盘上,再用另一道程序来模拟脱机输出时外围控制机的功能,把数据从磁盘传送到低速输出设备上。
这样,便可在主机的直接控制下,实现脱机输入、功能。
此时的外围操作与CPU对数据的处理同时进行,我们把这种在联机情况下实现的同时外围操作称为Spooling,或称为假脱机操作。
由上述得知,Spooling技术是对脱机输入、输出系统的模拟,相应地,Spooling系统必须建立在具有多道程序功能的操作系统上,而且还应有高速随机外存的支持。
Spooling系统主要有以下三部分:
⑴输入井和输出井。
这是在磁盘上开辟的两个大存储空间。
输入井时模拟脱机输入时的磁盘设备,用于残存I/O设备输入的数据;输出井时模拟脱机输出时的磁盘,用于暂存用户程序的输出数据。
⑵输入缓冲区和输出缓冲区。
为了缓和CPU和磁盘之间速度不匹配的矛盾,在内存中要开辟两个缓冲区。
输入缓冲区用于暂存有输入设备送来的数据,以后再传送到输出井。
输入出缓冲区用于暂存从输出井送来的数据,以后再传送给输出设备。
⑶输入进程和输出进程。
利用者两个进程模拟脱机I/O时的外围控制机。
输入进程将用户要输入的数据从输入机通过输入缓冲区再送到输入井,当CPU需要输入数据时,直接从输入井读入内存;输出进程把用户要求输出的数据先从内存送到输出井,待输出设备空闲时,再将输出井中的数据经过输出缓冲区送到输出设备上。
如图1.1所示,为Spooling系统的组成结构图。
SPOOLing系统具有如下主要特点:
⑴提高了I/O的速度。
这里,对数据所进行的I/O操作,已从对低速I/O设备进行的I/O操作,演变为对输入井或输出井中数据的存取,如同脱机输入输出一样,提高了I/O速度,缓和了CPU与低速I/O设备之间速度不匹配的矛盾。
⑵将独占设备改造为共享设备。
因为在SPOOLing系统中,实际上并没有为任何进程分配设备,而只是在输入井或输出井中位进程分配一个存储区和建立一张I/O请求表,这样,便把独占设备改造为共享设备。
⑶实现了虚拟设备功能。
宏观上,虽然是多个进程在同时使用一台独占设备,而对于每一个进程而言,他们都会认为自己是独占了一个设备。
当然,该设备只是逻辑上的设备。
SPOOLing系统实现了将独占设备变换为若干台对应的逻辑设备的功能。
2需求分析
2.1系统基本需求
将Spooling输入/输出处理程序编成一个独立的进程模块并且与其它请求输入或请求输出的进程并发运行。
Spooling进程负责把从输入设备读入的信息送到外存输入井中,或把外存输出井中的信息送到打印机等输出设备上输出。
将整个系统分为三个模块:
输入模块、处理模块以及输出模块。
(1)输入模块(负责作业的输入):
首先查看是否有待输入的作业,若无则结束。
查看输入井是否满,若满,则保留待输入作业现场,结束。
将作业读入输入井,直到输入井满。
设有10道作业待输入,每道作业是一个字符串。
长度不超过20,并以“#”作为结束符号。
(2)处理模块(负责加工处理输入井中的作业):
首先查看是否还有已加工但未送到输出井的信息(若有,转向3)。
从输入井中读出一道作业,在作业中的每个字符间插入“.”。
查看输出井是否满,若满,则保留现场,结束;否则将处理过的作业送到输出井中。
若作业全部送入,则结束;否则说明作业还未送完而输出井满了,则保留现场,结束。
(3)输出模块(负责将输出井中处理后的作业输出):
查看输出井是否空,若空,则结束;否则从输出井中依次读出字符送到计算机屏幕显示,遇到“#”需换行。
根据设计要求,我们按照自己的理解,对Spooling系统的设计做出了如下改动:
(1)Spooling输入进程就是将作业送至输入井。
在设计中,为了方便,我们称之为存输入进程。
(2)Spooling输出进程就是将输出井中的作业送至输出设备打印出来。
在设计中,我们称之为存输出进程。
(3)按照设计要求,处理模块涉及到从输入井中取作业进程处理,处理完毕后将作业送至输出井中。
我们认为可以将处理模块分为两部分,一是负责从输入井中取作业进行处理,一是负责将处理的作业送至输出井,这样划分更加清晰。
我们将前者仍然称为处理进程,将后者称为存输出进程,根据设计要求以及Spooling系统的原理,可以分析出,处理进程处理完一道作业后,调用存输出进程,将处理后的作业送至输出井。
(4)为了模拟输入设备以及输入缓冲区,在设计中,当系统运行时,用户可以根据自己的需要将作业通过键盘输入至一二维数组中,然后存输入进程将二维数组中的数据送至输入井。
这样,键盘作为输入设备,用二维数组模拟输入缓冲区。
2.2系统中的制约关系
(1)根据Spooling系统的原理,分析得出系统中存在的制约关系如下:
当存输入进程将输入缓冲区送至输入井时,输入井满,则存输入进程阻塞,进入阻塞队列;当处理进程从输入井中取数据时,发现输入井空,则处理进程阻塞,进入阻塞队列;当存输出进程将处理完的作业送至输出井时,发现输出井满,存输出进程阻塞,同时处理进程阻塞(处理进程调用存输出进程),进入阻塞队列;当取输出进程从输出井取数据时,发现输出井空,则取输出进程阻塞,入阻塞队列;当处理进程从输入井中取得数据后,如果存输入进程处于阻塞状态,则处理进程唤醒存输入进程,存输入进程从阻塞队列入就绪队列;当存输入进程将数据送至输入井后,如果处理进程处于阻塞状态,则存输入进程唤醒处理进程,处理进程从阻塞队列入就绪队列;当存输出进程将数据送至输出井后,如果取输出进程处于阻塞状态,则存输出进程唤醒取输出进程,取输出进程从阻塞队列入就绪队列;当取输出进程从输出井取得数据打印后,如果存输出进程处于阻塞状态,则取输出进程唤醒存输出进程,存输出进程从阻塞队列入就绪队列。
(2)进程消亡的条件:
当所有的作业全部输入至输入井时,存输入进程消亡;当所有的作业全部送至输出井时,存输出进程和处理进程消亡;当所有的作业全部输出至屏幕时,取输出进程消亡;
如图2.1所示,为Spooling系统中的制约关系图。
3总体设计
3.1系统整体设计思路
进程有三个基本状态:
就绪状态、执行状态、阻塞状态。
就绪状态是指当进程已分配到出CPU一位的所有必要资源后,只要获得CPU,便可立即执行,在一个系统中处于就绪队列的进程可能有多个,通常将它们排成一个队列,称为就绪队列;执行状态是指进程已获得CPU,其程序正在执行,在程序中,当调用代表某个进程的函数时,则表示该该进程处于执行状态;阻塞状态是指正在执行的进程由于发生某事件而暂时无法继续执行时,便放弃处理机而处于暂停状态,亦即进程的执行受到阻塞。
根据Spooling系统原理以及我们自己的理解,应该设计四个进程,分别命名为:
存输入进程,处理进程,存输出进程,取输出进程。
其中,存输入进程是将输入缓冲区中的作业送至输入井中;处理进程是先从输入井中取出作业的数据,然后进行相应的处理;存输出进程是将处理过的作业送至输出井中;取输出进程是将输出井中的数据送至输出设备(显示器)上打印出来。
输入设备为键盘,当系统开始运行后,给出用户提示,用户通过键盘将自己的作业输入至输入缓冲区(二维数组)中,然后开始进程调度。
为了方便简单,采用非抢占式调度(直至进程结束或者阻塞时,才把处理机分配给其他进程)。
为了实现四个进程的调度,设计一个就绪队列和一个阻塞队列,用来存放被阻塞或者就绪的进程的编号;利用两个循环队列来模拟输入井和输出井。
首先创建四个进程的PCB,在进程控制块中主要有进程的编号以及进程的状态标识。
在开始时,先将四个进程的状态设为就绪状态,并且将存输入进程、处理进程以及取输出进程送至就绪队列,因为处理进程调用存输出进程,不用单独将存输出进程送入就绪队列,当存输出进程被阻塞时,同样会阻塞处理进程。
整个运行过程是:
从就绪队列中取出一个进程,先进入就绪队列中的进程先被分配CPU。
若进程遇到被阻塞的事件后,则进入主动阻塞状态,进入阻塞队列。
如果该进程在运行的过程中,满足了某个进程所需的数据,则设置某个进程的状态,并且送入就绪队列中,从阻塞队列中删除。
直至所有的作业输出完毕,所有的进程进入结束状态,释放进程PCB。
刚开始时是运行存输入进程,在运行过程中如果发现输入井已满时,则设置自己的状态为阻塞状态,若所有的作业已经全部输入至输入井中,则进程结束;当存输入进程阻塞时,则从就绪队列中取出下一个进程,将处理机分配给该进程,运行。
如此循环,当取输出进程将所有的作业全部送至输出井时,则所有的结束,释放所有的进程PCB。
在设计中,当输入井满而作业还没有完全输入至输入井中时,要保留现场。
同样,当输出井满,为一道作业还没有完整输入至输出井时,要保留现场,当下次执行该进程时,从断点处继续执行存输入。
3.2用到的主要函数的定义
(1)全局变量的定义
各个进程间的数据交换是以变量为介质的,变量的定义非常重要,分析在C语言中全局变量时静态存储方式,也就是在编译时开辟内存空间的,全局变量的作用域是整个文件,根据设计原理,一些变量必须要定义为全局变量。
SPOOLing系统要模拟输入井、输出井,用循环队列来模拟输入、输出井;就绪队列和阻塞队列都用循环队列来表示;进程控制块用结构体来表示,同时要设置一些整型变量和数组,例如记录带输出作业的数目、记录待输入作业的信息等等。
如下所示,为整个系统用到的全局变量。
#include"stdio.h"
#defineJOBNO3//宏定义作业的个数
#defineLENsizeof(structpcb)
#defineLEN1sizeof(structReady)
structpcb//进程控制块
{
intno;//进程编号
intstate;//进程状态,0:
可执行状态
}*p0,*p1,*p2,*p3;//由于输出井满,存输出进程阻塞,
(2)主函数
主函数中主要实现的是控制作业的输入、进程的创建、就绪队列和阻塞队列的初始化以及进程之间的状态转换和调度。
控制作业的输入主要是利用双重循环来控制的,实现进程的调度主要是利用while循环和switch语句配合实现的。
当所有的进程都结束后,整个运行过程结束,释放所有的进程。
voidmain()
{
intm,n;
printf("********欢迎进入SPOOLING模拟系统***********\n");
printf("请输入所有作业内容,字符串,每个字符串以#结束,字符串长度不大于19(包括#)\n");
//由于字符串最后以‘\0’结束,需要占用一个字符空间
printf("请输入完一道作业后,按回车键\n");
}
(3)存输入进程
存输入进程主要完成的是将作业输入至输入井,用到了全局变量js[2]来记录待输入作业的信息,js[0]记录待输入作业编号,js[1]记录待输入作业中要输入的字符位置。
voidstore_in()//存输入
{
return;
}
(4)处理进程
处理进程主要完成的功能是从输入井中取出一道作业进程处理,然后将处理的作业通过调用存输出进程送至输出井中。
首先要判断当前是否有已经处理完毕,但是还没有完全送至输出井的作业,如果有,要先将该作业送至输出井。
然后再进行下一道作业的处理。
主要用到了两个全局变量:
记录待输出至输出井中的作业数的整型变量,记录由于输入井空而没有取到完整作业时下次取的数据放的位置。
voidchuli()
{
return;
}
(5)存输出进程
存输出进程主要完成的是把处理完的作业送至输出井中。
voidstore_out(int*i)//存输出,由处理进程调用
{
return;
}
(6)取输出进程
取输出进程主要完成的是把输出井中的数据输出打印出来,主要用到了记录待输出作业数的全局变量,当待输出作业数为0时,说明已经把所有的作业输出打印完毕,进程消亡。
在打印输出数据时,遇到‘#’执行换行操作。
voidload_out()//取输出,打印
{
return;
}
(7)进程状态转换函数
进程状态转换函数主要完成的是唤醒进程,首先根据得到的参数值到就绪队列里查看有没有该进程编号,若有,则不进行操作,若没有,则该进程入就绪队列,出阻塞队列。
该函数是一个带整型参数的函数。
voidwait_to_ready(intz)//入就绪队列,从阻塞队列中删除
{
return;
}
4详细设计
4.1存输出进程算法
存输出进程主要完成的是将处理进程处理的作业送入输出井,当输出井满时,该进程阻塞;当取输出进程从输出井中取得数据打印输出时,如果该进程之前由于输出井满,则取输出进程唤醒该进程。
定义两个循环队列指针:
q_out.front(循环队列头指针)q_out.reat(循环队列尾指针),循环队列输出井的长度为20。
首先通过语句(q_out.rear+1)%20!
=q_out.frontshi;检测输出井是否满。
若满,则结束。
若输出井未满,将处理模块已加工后的作业通过语句q_out.date[q_out.rear]=buf[*i];存人到输出井中。
在处理后的作业存人到输出井的过程中遇到符号“#”时,表示一道作业已经完全输入至输出井,为作业创建请求输出表。
一道作业处理完毕并且已经全部输至输出井,可以开始下一道作业的处理。
在处理后的作业存入到输出井的过程中,若输出井满,存输出的进程阻塞,保留待输入作业现场,结束。
该进程由阻塞状态到就绪状态时,作业从保留现场的待输入作业开始执行。
4.2整体控制算法
整体控制实现的功能是:
提示用户输入作业,创建进程,并将创建的进程全部入就绪队列,通过循环语句来实现所有的进程控制。
首先从就绪队列中取出队列的首个数据,利用switch语句来进程匹配,当取出的数据为0时,则转入执行存输入进程;若取出的数据为2时,则转入执行处理进程(在处理进程中调用存输出进程);若取出的数据为3时,则转入执行取输出进程。
当进程由于某个原因而进入阻塞状态,返回至主函数(整体控制函数),接下来应该判断该进程的状态,若为阻塞状态则将该进程的编号送入阻塞队列,从就绪队列中删除,实现该进程的状态转换。
若判断当前进程为结束状态,则将直接从就绪队列中删除。
只有当取输出进程结束时,整个流程才能结束,所以while循环的结束条件就为取输出进程的状态为结束。
当循环结束后,要释放进程。
为了体现出输入设备以及输入缓冲区,在整体控制中,可以让用户根据自己的需要来输入作业,在输入作业时要根据作业的要求进行输入,输入一个数组,该数组用来模拟输入缓冲区。
当输入的作业不符合要求时,要给出提示,让用户重新输入。
为了实现程序的扩充,可以利用宏定义来记录整个流程中的作业总数(而不是设计要求中的仅仅限于10道作业的处理)。
如图4.1所示,为整体控制算法流程图。
4.3存输入进程算法
存输入进程主要是体现将输入设备中的数据读到输入井中的过程。
如果输入设备中有数据需要被读入至输入井,则首先要判断输入井中是否已经满,如果输入井已经满,那么就无法向输入井中写数据,也就是说不能将作业输入至输入井中,存输入进程就会阻塞,这时需要保留待输入作业现场,程序结束。
当输入井没有满的时候,表示待输入作业可以进入输入井。
程序中的变量in是记录待输入作业的个数的,每讲一个作业输入至注入井,待输入作业的个数就会减一,以此来判断是否还有作业在等待输入。
若输入设备中的所有待输入作业已经全部输入至输入井,则该进程结束。
如果待输入作业只输入了一部分输入井便已满,那么就不能再向输入井中输入作业,此时应该记录待输入作业的状态,即保留待输入作业的现场。
程序中用数值数组JS[2]保留输入作业的信息,其中JS[0]记录待输入作业的编号,JS[1]记录待输入作业中要输入的字符位置。
当输入井中的数据被取出后,那么输入井中就又有了空间,则待输入作业可以继续存入输入井,因为已经保留了待输入作业现场,那么未输完的作业就可以继续被输入至输入井,如此反复,直到所有作业都被输入至输入井,进程结束。
如图4.2所示,为存输入进程算法流程图。
4.4处理进程算法
处理进程完成的功能是从输入井中取得数据,进行处理后,将作业送至输出井等待输出。
如果输入井空,则该进程进入阻塞状态,如果输出井满,同样该进程因为存输出进程被阻塞而进入阻塞状态。
根据设计要求得出,要处理完一道作业后再进行输出,用一个buf[]数组来记录处理后的作业,用一个全局整型变量chuli_address来记录当前由于输入井空而阻塞时当前作业待处理的字符位置,也就是保留现场。
当从输入井中取得的数据为‘#’,则表示一道作业已经处理完毕(在处理过程中,把从输入井取得的数据送至buf[]数组,然后在数组中当前的下个位置中输入‘.’,这样完成处理过程的),应该进行存输出,即将处理完的作业送至输出井,也就是说接下来要调用存输出进程。
如果调用存输出进程返回后,判断存输出进程阻塞了,则将处理进程设为阻塞状态(因为处理的作业不能输出,也就是说I/O请求没有满足,阻塞)。
如果发现存输出进程仍然处于可执行状态,说明存输出进程已经将一道完整的作业送至输出井中,接下来应该判断是否所有的作业已经全部处理完毕并且送至输出井中,如果是,则处理进程和存输出进程结束;如果不是,则继续进行下一道作业的处理,此时应该设置chuli_address为0,要将下一道作业的数据依次从buf[]数组的开始位置存储。
如果因为存输出进程阻塞而致使处理进程阻塞,当下一次执行处理进程时,首先要判断之前作业有没有完全送入输出井,这就要利用处理进程中的chuli_address变量和存输出进程中记录当前作业待送至输出井字符位置的全局整型变量i进行比较。
如果两个整型变量相等,则说明之前的作业已经完全输入至输出井,可以开始下一道作业的处理;如果不等,则说明之前作业虽然处理完毕,但是还没有完全送至输出井中,要先将该作业完全送至输出井才可以开始下一道作业的处理。
如果输入井非空,即可以进行作业的处理,当从输入井中取得数据时,如果存输入进程之前由于输入井满而进入阻塞状态,则唤醒存输入进程。
即处理进程从输入井中取得数据后,输入井有空闲空间了,存输入进程所期待的事件发生了,进入就绪状态。
输入井和输出井都是利用循环队列模拟的,当输入井为空时,要阻塞该进程,在算法中只需利用语句q_in.front!
=q_in.rear来作为循环的条件即可。
4.5取输出进程算法
取输出进程主要完成的是将输出井中已经处理完毕的作业输出至屏幕打印出来,输出设备即为显示器。
首先判断是否所有的作业已经全部输出打印,设一个整型变量来记录待输出打印的作业数,如果该变量值为0,则说明所有的作业输出打印完毕,进程结束,整个流程也就结束了。
如果该变量值大于0,则说明还有作业没有输出打印,继续从输出井中取得数据。
如果当前数据为‘#’,则说明一道完整的作业已经输出打印完毕,根据设计要求,遇到‘#’换行,继续打印输出下一道作业。
取数据时,如果发现输出井空了,则取输出进程进入阻塞状态。
只有当存输出进程将数据送至输出井时,才唤醒该进程,进入就绪队列。
当从输出井中取得数据后,应该唤醒存输出进程,因为取出数据后,输出井中有空闲了,可以继续向输出井中送数据,也就是说存输出进程请求的事件已满足,进入就绪队列。
每当遇到一个‘#’时,记录待输出打印的整型变量值减1。
为了实现每次运行时都能够保持最新的数据,应该将该整型变量设为全局变量。
通过循环队列首指针的移动来取出数据,同时当首指针和尾指针相等时,则表示输出井空,取输出进程的状态要设为阻塞状态,返回至主函数,入阻塞队列。
4.6进程的状态转换算法
当被阻塞进程所期待的时间发生时,如I/O完成或其所期待的数据已经到达,则由有关进场能够调用唤醒原语,将等待该时间的进程唤醒。
唤醒原语执行的过程是:
首先把被阻塞的进程从等待该时间的阻塞队列中移出,将其PCB中的现行状态由阻塞改为就绪,然后再将该PCB插入到就绪队列中。
如果在某进程中调用了阻塞原语,则必须在与之相合作的另一进程中或其他相关的进程中安排唤醒原语,以能唤醒阻塞进程。
进程的阻塞是一种自身的主动行为,四个进程会由于某个原因而进入阻塞状态,实现该进程的唤醒操作就是利用进程的状态转换算法来代替唤醒原语。
进程的状态转换算法主要实现的是,当一个进程释放另一个进程时,设置就绪队列和阻塞队列应有的变化。
该算法被4个进程调用,被其他进程唤醒的进程的编号都是不同的,所以当某个进程要唤醒某个进程时,除了设置被唤醒进程的状态,还要将被唤醒进程的编号作为实参,调用该状态转换算法。
在该算法中,首先要判断传送的进程编号在就绪队列中是否存在,若存在则说明该进程之前没有被阻塞,注意,在此时如果还将该进程编号送至就绪队列,则进程的调度可能会出现问题。
如果该进程的编号在就绪队列中不存在,则将该进程入就绪队列,同时要修改阻塞队列,即到阻塞队列中找到该进程的编号,利用循环队列的特点,将该进程编号从阻塞队列中删除。
在就绪列中寻找进程编号时,通过移动队列指针来实现的,注意,如果没有找到进程编号时,应该恢复队列之前的指针状态(事先利用一个变量来记录就绪队列的指针状态)。
5测试结果
5.1初始界面
运行程序后,就会进入SPOOLing模拟系统。
首先看见的是初始界面。
初始界面是提示用户已经进入了SPOOLing模拟系统,提示用户要输入所有的作业,每个作业都为字符串形式并以“#”号结束,包括“#”号在内的长度不能大于19,如图5.1所示。
当用户输入的作业不符合要求时,将给出错误提示,提示用户重新输入作业,如图5.2所示。
图5.1初始界面
图5.2错误提示信息
5.2输入作业
在用户进入初始
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- SPOOLING 技术 模拟 实现