💡整理不易,先赞后看,养成习惯💡
6.1 I/O系统的功能、模型和接口
I/O系统的基本功能
隐藏物理设备的细节
操作系统无需关心输入输出设备的具体细节,只需要通过接口接入设备即可
与设备的无关性
输入输出设备需要做到即插即用,不影响操作系统的正常工作流程,做到CPU和设备无关
提高处理机和I/O的利用率
IO设备的速度相对CPU是很慢的,所以需要IO系统解决速度不一致的问题
对I/O设备进行控制
对于具体调用哪一台设备进行输入输出这个工作,主要由I/O系统控制而非CPU。这样可以让CPU更加专注于作业的处理
确保对设备的正确共享
一台设备可能被多个用户使用,I/O需要保证多个用户之间可以正确的共享使用该设备
错误处理
如果在使用输入输出设备的时候出现错误,I/O系统需要及时处理错误
上述六点中,其中:1、2
主要目的是方便用户,3、4
主要目的是提高CPU和I/O设备的利用率,5、6
主要目的是为用户共享设备提供方便。
I/O系统的层次结构和模型
目前I/O系统普遍采用层次结构,它将系统中的设备管理模块,分为若干个层次,每一层都是利用其下层提供的服务,完成输入输出功能中的某些子功能,并屏蔽这些功能实现的细节,向高层提供服务。
层次结构如下:
用户层软件
实现与用户交互的接口,用户可直接调用在用户层提供的、与I/O操作有关的库函数,对设备进行操作。
设备独立软件
用于实现用户程序与设备驱动器的统一接口、设备命名、设备的保护以及设备的分配与释放等,同时为设备管理和数据传送提供必要的存储空间。
设备驱动程序
用于具体实现系统对设备发出的操作指令,驱动I/O设备工作的驱动程序。
中断处理程序
用于保存被中断进程的CPU环境,转入相应的中断处理程序进行处理
I/O系统接口
不同类型的设备对应不同的接口,主要包括:
- 块设备接口
- 流设备接口
- 网络通信接口
6.2 I/O设备和设备控制器
I/O设备
设备类型
按使用特性分类:
- 存储设备:也称外存、辅存。
- I/O设备:分为输入设备、输出设备和交互式设备。
按传输速率分类:
- 低速设备:其传输速率仅为每秒钟几个字节至数百个字节的一类设备,如键盘、鼠标器。
- 中速设备:传输速率在每秒钟数千个字节至数十万个字节的一类设备,如行式打印机、激光打印机等。
- 高速设备:传输速率在数十万字节至千兆字节的一类设备,如磁带机、磁盘机、光盘机等
I/O设备的组成
通常,设备并不直接与CPU进行通信,而是与设备控制器进行通信,因此,在I/O设备中应还有与设备控制器之间的接口。主要通过三根线进行主要的通信:
- 状态信号线:—用于传送指示设备当前状态的信号。—设备当前状态有:正在读(或写),设备已读(或写)完成,并准备好新的数据传送。
- 控制信号线:—设备控制器向I/O设备发送控制信号时的通路。 —规定了设备将要执行的操作,比如:读、写、执行磁头移动等。
- 数据信号线:—用于在设备和设备控制器之间传送数据信号。
假设我现在需要在打印机上打印一条Hello world
语句,首先需要判断打印机是否繁忙(或者说是否在工作中),这时候就需要用到状态信息号线
,如果CPU想要传送相应的数据到打印机,就需要状态信号线
用于传送控制信息,以及数据信号线
用于传送数据。
设备控制器
主要功能
- 控制一个或多个I/O设备,以实现I/O设备和计算机之间的数据交换
- 它是CPU与I/O设备之间的接口,它接收从CPU发来的命令,并去控制I/O设备工作,以使处理机从繁忙的设备控制事务中解脱出来。
简单来说就是帮助CPU对设备进行管理的一个设备。
基本功能
接收和识别命令
设备控制器能接收并识别处理机发来的多种命令,并对所接收的命令进行译码。
数据交换
实现CPU与控制器之间、控制器与设备之间的数据交换 前者通过数据总线,由CPU并行地把数据写入控制器 后者则是设备将数据输入到控制器中,或从控制器传送给设备。为此,在控制器中必须设置数据寄存器
标识和报告设备状态
控制器应记下设备的状态供CPU了解
地址识别
能够识别其所控制的每个设备的地址
数据缓冲区
缓和CPU、内存与I/O设备的速度差异的矛盾故在控制器中必须设置一缓冲区。
差错控制
兼管对由I/O设备传送来的数据,进行差错检测,保证数据输入的正确性。
内存映像I/O
通常来说I/O命令都是抽象且复杂的,所以需要通过**驱动程序**
将I/O命令,转换出的一系列具体的命令、参数等数据,装入设备控制器的相应寄存器,由控制器来执行这些命令,具体实施对I/O设备的控制。
有两种方法:
- 利用特定的I/O指令:这种方式需要区分访问内存和访问设备,分别设置两种指令。
- 内存映像I/O:该方式统一了对内存和对控制器的访问的方法,简化了I/O的编程。也就是只有一种访问指令。在编址上采用k表示地址,当k值处于
0-n-1
范围时,被认为是内存地址,若k大于等于n时,被认为是某个控制器的寄存器地址。
利用特定的I/O指令
简单来说,设备的地址是独立于内存地址的,每一个设备的地址都相当于从0开始计算。
内存映像I/O
简单来说,设备的地址是依赖于内存地址的,每一个设备的地址都相当于从上一个设备地址或者内存地址开始计算,比如在
设备控制器0
的首地址,就是跟在内存地址的最后一位n-1
加上一位变成n
。
通道
I/O通道的引入
在前文提到的系统当中,CPU享有对于I/O设备
的控制权,I/O设备
输入输出都由CPU进行控制,相对来说我们更希望CPU更多的进行计算操作,所以可以释放CPU对I/O的组织、管理。
所以引入了通道,这是一种特殊的执行I/O指令的处理机,CPU只需要把相关的I/O指令发送给通道即可,无需参与对于I/O的实际管理,这个功能交给通道处理。
对于通道来说:指令类型单一,硬件较简单,与CPU共享内存,可以有自己的总线。
引入通道之后的I/O系统的结构如下:
主机——通道——设备控制器——I/O设备
也叫四级连接方式。这里后续会深入讲解
瓶颈问题
由于通道价格昂贵,致使系统中所设置的通道数量势必较少,这往往使它成了I/O的瓶颈,进而造成整个系统吞吐量下降。
解决方法为:增加设备到主机间的通路
6.3 中断机构和中断处理程序
中断简介
中断是指程序执行过程中,遇到急需处理的事件时,暂时中止CPU上现行程序的运行,转去执行相应的事件处理程序,待处理完成后再返回原程序被中断处或调度其他程序执行的过程。
中断和陷入
- 中断:CPU对I/O设备发来的中断信号的一种响应,中断是由外部设备引起的,又称外中断。
- 陷入:由CPU内部事件所引起的中断,通常把这类中断称为内中断或陷入(trap)。如地址越界、非法指令、电源故障等
中断和陷入的主要区别:
- 信号的来源不同
- CPU外部还是内部
中断向量表和中断优先级
(1) 中断向量表:为每种设备配以相应的中断处理程序,并把该程序的入口地址,放在中断向量表的一个表项中,并为每一个设备的中断请求,规定一个中断号,它直接对应于中断向量表的一个表项中。
(2) 中断优先级:系统根据不同中断信号源,对服务要求的紧急程度的不同,它们分别规定不同的优先级。
多中断源处理方式
屏蔽(禁止)中断:所有中断都将按顺序依次处理。
不能用于对实时性要求较高的中断请求
嵌套中断:在设置了中断优先级的系统中,通常按这 样的规则来进行优先级控制
当同时有多个不同优先级的中断请求时,CPU优先响应最高优先级的中断请求;
中断处理程序
- 测定是否有未响应的中断信号
- 保护被中断进程的CPU环境
- 转入相应的设备处理程序
- 中断处理
- 恢复CPU现场并退出中断
6.4 💡设备驱动程序
设备驱动程序概述
概念区分
首先清楚一个概念:
设备驱动程序是I/O系统的高层与设备控制器之间的通信程序。
设备控制器
和设备驱动程序
是两个概念:
- 设备控制器是在硬件层面控制I/O设备
- 设备驱动程序是在软件层面控制I/O设备
设立驱动程序的最主要的原因是避免用户直接使用硬件指令(机器语言等低级语言),而是通过更贴近自然语言的I/O指令的方式调用相关设备。
驱动程序的功能
最主要的功能有两点:
- 接受上层软件发来的抽象I/O要求,再把它转化为具体要求,发送给设备控制器,启动设备去执行。
- 反之,它也将由设备控制器发来的信号,传送给上层软件。
第一点很好理解,就是需要通过CPU控制I/O,所以需要对I/O指令
进行一个“翻译”的工作。
第二点是因为设备输入输出的过程中需要发送中断指令,此时需要向CPU进行报告,这个内容也是需要通过驱动程序来传达。
发送中断的原因无非三点:
- 人为控制
- 指令完成自然中断
- 故障中断
总功能:
- 检查用户I/O请求的合法性
- 接收由与设备无关的软件发来的命令和参数,并将命令中的抽象要求,转换为与设备相关的低层操作序列;
- 发出I/O命令,如果设备空闲,便立即启动I/O设备,完成指定的I/O操作;如果设备忙碌,则将请求者挂在设备队列上等待;
及时响应由设备控制器发来的中断请求,并根据其中断类型,调用相应的中断处理程序进行处理。
驱动程序的特点(不重要)
驱动程序是与设备无关的软件和设备控制器之间通信和转换的程序。
- 驱动程序,与设备控制器和I/O设备的硬件特性,紧密相关。
- 驱动程序与I/O设备所采用的I/O控制方式紧密相关。
- 由于驱动程序与硬件紧密相关,因而其中的一部分必须用汇编语言编写。
驱动程序应允许可重入,一个正在运行的驱动程序常会在一次调用完成前被再次调用。
设备驱动程序的处理过程
将抽象要求转换为具体要求
- 对服务请求进行校验:主要判断请求是否合法,比如用户视图从打印机中输入数据
- 检查设备的状态:如果设备忙就等待,否则就是做输入输出
- 传送必要的参数
- 启动I/O设备
对于I/O设备的控制遵循以下原则:
尽量减少主机(一般来说也可以认为是CPU)对I/O控制的干预,把主机从繁杂的I/O控制事务中解脱出来,以便更多地去完成数据处理任务。
I/O设备的控制方式
轮询的可编程I/O
从上面的概念中我们知道:设备驱动程序是I/O系统的高层与设备控制器之间的通信程序。所以最传统的方式就是需要CPU对于I/O设备进行管理和控制。
轮询的可编程I/O方式也叫程序直接控制方式,也就是说CPU的设备驱动程序通过设备控制器控制I/O设备,已输出一个字符为例,主要的步骤如下:
- 发出输出命令
- 检查相关设备是否处于空闲状态
- 如果在忙则等待
- 不忙进入下一步
- 控制输出字符
- 完成改命令就结束这个指令
对于上述步骤再深入了解一下,就需要先了解详细的设备控制器结构:
这里以读取输入设备中的数据为例:
- 数据寄存器:用于暂存需要读取的数据
- 控制寄存器:用于存放CPU需要执行的控制指令
- 状态寄存器:用于记录输入设备的状态(0表示空闲,1表示忙)
- I/O逻辑:简单来说就是用于“翻译”的部分
读操作的步骤如下:
- 首先发出读操作,设备此时启动,不断通过控制器把数据输入到数据寄存器中,状态寄存器置为
1
- CPU轮询检查状态寄存器:
- 如果状态寄存器一直为
1
,说明还在读取数据 - 如果状态寄存器为
0
,说明已经读取完毕
- 如果状态寄存器一直为
- 输入设备输入完成,通过控制器向CPU报告已完成读取
- I/O逻辑部分会“翻译”这个“报告”,把状态寄存器的
1
置为0
- CPU检测到状态寄存器变为
0
了,此时把数据寄存器的数据读入CPU的寄存器,再把CPU的寄存器中的数据读入内存中(所以数据的走向是:设备控制器的数据寄存器->CPU的数据寄存器->内存)。 - 如果还需要其他指令,则继续重复类似上述的步骤
完整流程图如下:
这种方式很容易发现一个缺点:CPU需要不断的读取状态,干预I/O操作,大部分时间由于读取的状态是忙,所以是都是无意义的操作,cpu利用率很低。为了避免CPU主动去读取状态,产生了中断处理的方式。
中断的可编程的I/O
这种方式引入了中断机制,简单来说,设备是否完成I/O操作不再需要CPU轮询检查了,而是CPU启动设备之后就去忙其他操作,知道设备完成操作之后发出中断指令,CPU再来继续处理。
什么时候发送中断指令? 一旦数据进入数据寄存器,控制器便通过控制线,向CPU发送一中断信号。
大致流程和上述差不多:
通过中断的方式能很好的解决轮询带来的无意义的性能开销问题,但是这里同样需要注意两点:
- CPU会在每个指令周期的末尾检查中断
- 中断处理过程需要保存和恢复运行环境,这个过程也有一定的时间开销
所以如果存在读取多个字的情况,CPU会大量的进行中断操作,这也会造成性能低下。
而且在这种方式下,读取数据还是需要通过CPU才能写入内存,也就是需要走三步:设备控制器的数据寄存器->CPU的数据寄存器->内存。如果可以直接从设备控制器的寄存器写入内存又可以节省一部分时间了,所以出现了直接存储器访问。
直接存储器访问
直接存储器访问方式也叫DMA方式,通过DMA控制器对设备进行控制:
DMA控制器和设备控制器最大的不同在于其寄存器的设计:
- 命令/状态寄存器CR:用于接收从CPU发来的I/O命令,或有关控制信息,或设备的状态。
- 内存地址寄存器MAR:在输入时,它存放把数据从设备传送到内存的起始目标地址,在输出时,它存放由内存到设备的内存源地址。
- 数据寄存器DR:用于存放从设备到内存或内存到设备的数据。
- 数据计数器DC:存放本次CPU要读写的字数。
区别上述两种的控制方式,DMA方式读写数据是按照字块读写,而前两种方式都是按照字读写的,按照字块读写就意味着CPU可以做更少的中断处理,这就解决了中断可编程I/O方式中CPU大量处理中断的问题。
于此同时,通过DMA控制器中的MAR和DC(也就是知道了存储的起始地址和数据的长度),我们就可以知道需要读写的数据应该直接存放在内存中的哪个位置,做到了不必过问CPU即可让设备读写的数据直接流入内存,又进一步的提升了CPU的效率。
DMA方式看着已经很好了,但是还有一个缺陷,由于是通过DMA控制器中的MAR和DC确定数据在内存中的位置,说明DMA实际只能读取连续的字块,而不能读取离散的字块。实际读写中也有很大的概率字块在内存中的位置并不连续,所以最后出现了I/O通道
的方式解决这一问题。
I/O通道
I/O通道在前面有简略介绍过,可以理解成一个简略的CPU,可以代替CPU完成简单的读写指令,从而进一步的释放CPU在I/O操作上的消耗。同时由于I/O通道的存在,其实可以一次读写一组字块,进一步提升读写效率。
大致可以理解为CPU是大哥,但是忙不过处理I/O操作,于是招募了一个小弟I/O通道,小弟不会自己去处理I/O操作,而是大哥会交代小弟需要处理I/O操作,对于数据读写的地址内容等,交代完之后小弟就去处理I/O操作,处理完之后大哥回来验收就行了。
I/O通道会设立一个通道程序,通过执行通道程序与设备控制器共同实现对I/O设备的控制的。通道程序的每条指令包含以下信息:
- 操作码:规定了指令所执行的操作
- 内存地址:表明字符送入内存或从内存取出时的首址
- 计数:表示本条指令所要读或写数据的字节数
- 通道程序结束位P:P=1表示本条指令是通道程序的最后一条指令
- 记录结束标志R:
- R=0表示本通道指令与下一条指令所处理的数据属于同一个记录;
- R=1表示这是处理某记录的最后一条指令
通道程序可以简单理解为这是大哥给小弟设定的任务清单。
对于P和R举个例子:
操作 | P | R | 计数 | 内存地址 |
---|---|---|---|---|
Write | 0 | 0 | 80 | 813 |
Write | 0 | 0 | 140 | 1034 |
Write | 0 | 1 | 60 | 5830 |
Write | 0 | 1 | 300 | 2000 |
Write | 0 | 0 | 250 | 1850 |
Write | 1 | 1 | 250 | 720 |
这里前三条个操作就属于同一个指令,第四条操作就是一个单独的指令,最后两个操作属于同一个指令
四种方式的对比
6.5 与设备无关的I/O软件
基本概念
什么是与设备无关
设备无关软件(Device-Independent Software)是一种独立于特定硬件设备的软件,也被称为通用软件或平台无关软件。设备无关软件可以在不同的硬件平台或操作系统上运行,而无需进行修改或适配。
与设备无关也可以叫做设备独立性,产生这个概念是由于不同的I/O设备
对应的驱动程序也是不同的,举个例子:
比如这里有两台打印机A和B,两个打印机对应控制器的状态寄存器如下:
打印机\标志位 | 1 | 0 |
---|---|---|
A | 表示忙 | 表示闲 |
B | 表示闲 | 表示忙 |
很明显对应标志位,两者的状态其实是完全相反的,所以需要两个不同的驱动程序来识别两个打印机的真实状态。
但是用户实际不需要关心驱动程序是怎么样的,对应不同的打印机应该需要一套的标准进行调用,所以需要设备无关软件,也就是说在这一软件层面上,用户无需关心不同I/O设备之间的差异,可以使用同一套标准调用不同的I/O设备。
设备名
用户在调用设备的时候,通常对于设备的标识,比如打印机就是打印机1
和打印机2
,这种设备名被叫做逻辑设备名,与之实际对应的就是物理设备名。
因系统只识别物理设备名,所以在实际执行中,需要实现逻辑设备名到物理设备名的转换。所以需要在系统中配置一张逻辑设备表用于转换。
这个逻辑设备表需要至少三项内容:
逻辑设备名 | 物理设备名 | 驱动程序入口 |
---|---|---|
xxx | xxx | xxx |
为什么需要驱动程序入口上一小结已经阐述了原因了。
设备无关软件需要实现的功能
- 设备驱动程序的统一接口
- 缓冲管理
- 差错控制
- 对独占设备的分配和回收
- 独立于设备的逻辑数据块
设备分配
设备分配(Allocation of Devices)是指对于多个进程或程序,操作系统需要合理地管理计算机硬件设备的占用和访问请求。 当计算机上的硬件设备被多个进程或程序同时申请访问时,操作系统必须通过一定的机制,来决定将该设备授予哪一个进程或程序进行使用。
需要设备分配的原因在于,许多硬件设备如打印机、磁盘驱动器等,都是共享资源,在同一时刻只能被一个进程或程序所访问。如果没有设备分配机制,就会出现多个程序同时访问同一个硬件设备的情况,这往往会导致数据混乱、死锁等问题,从而影响整个计算机系统的稳定性和运行效率。
一般而言,设备分配主要包括三个方面的内容:
- 设备状态管理
- 设备请求调度
- 设备释放
具体来说,操作系统需要维护设备的不同状态(如空闲、占用、故障等),并按照一定的规则对设备请求进行调度和分配;在设备占用完成之后,还需要及时释放相应的设备资源,以便后续的进程或程序能够正常申请访问。
静态分配和动态分配
静态分配:进程运行前直接为进程分配全部的所需资源,运行结束之后归还所有的资源
动态分配:进程运行过程中动态申请设备资源
设备分配中的数据结构
首先我们需要知道设备、控制器、通道之间的关系:
一个通道可以控制多个设备控制器,每个设备控制器可以控制多个设备。
设备控制表DCT:为每一个设备都配置了一张,记录设备的情况 | 属性 | 意义 | | —- | —- | | 设备类型 | 描述设备的类型,比如打印机/扫描仪等 | | 设备标识符 | 表示物理设备名 | | 设备状态 | 空闲/忙碌/故障/… | | 指向设备控制表的指针 | 指向控制该设备的设备控制器 | | 重复执行的次数或者时间 | 重复执行多次I/O操作后还不成功,认为此次I/O失败 | | 设备队列的队首指针 | 指向正在等待该设备的进程队列 |
控制器控制表COCT :为每一个控制器都设置一张,记录控制器情况。 | 属性 | 意义 | | —- | —- | | 控制器标识符 | 每个控制器的唯一ID | | 控制器状态 | 空闲/忙碌/故障/… | | 指向通道的指针 | 指向控制该设备控制器的通道 | | 控制器队列的队首指针 | 指向正在等待此控制器的进程队列 | | 控制器队列的队尾指针 | 指向正在等待此控制器的进程队列 |
通道控制表(CHCT):为每一个通道都设置一张,记录通道情况。 | 属性 | 意义 | | —- | —- | | 通道标识符 | 每个通道的唯一ID | | 通道状态 | 空闲/忙碌/故障/… | | 与通道连接的控制器表首地址 | 通过这个指针可以找到改通道管理的所有控制器的相关信息 | | 通道队列的队首指针 | 指向正在等待此通道的进程队列 | | 通道队列的队尾指针 | 指向正在等待此通道的进程队列 |
系统设备表SDT:这是系统范围的数据结构,记录系统中全部设备的情况,每个设备占一个表目。
表目的属性如下:
属性 | 意义 |
---|---|
设备类型 | 描述设备的类型,比如打印机/扫描仪等 |
设备标识符 | 物理设备名(唯一) |
DCT | |
程序驱动入口 |
设备分配的步骤
- 根据进程请求的物理设备名查找系统设备表(SDT)
- 根据SDT中唯一的设备标识符找到对应的设备控制表(DCT),设备不忙碌的情况下,把该设备分配给进程
- 根据DCT找到唯一的控制器控制表(COCT),控制器不忙碌的情况下,把该控制器分配给进程
- 根据COCT找到通道控制表(CHCT),通道不忙碌的情况下,把该通道分配给进程
只有在设备、控制器和通道三者都分配成功时,这次的I/O请求才算成功,便可启动I/O设备进行数据传输。
在这种分配步骤下有一个缺陷就是用户如果想要调用设备,就必须使用设备的物理设备名,一来这极度不方便,第二如果更换设备,比如打印机1
换成打印机2
,虽然可能是同一类型的打印机,但是物理设备名必然不同,导致无法正常调用打印机。
所以改进方法就是增加逻辑设备名,建立逻辑设备名和物理设备名之间的映射即可。
对应的映射表叫做(LTU),这在后续会讲解
设备分配时应考虑的因素
(1)设备的固有属性
- 独占设备的分配策略。将该设备分配给进程后,便由进程独占,直至该进程释放。
- 共享设备的分配策略。可同时分配给多个进程,注意进程访问次序的合理调度。
- 虚拟设备的分配策略。虚拟设备属于可共享的设备。
(2)设备分配算法
- FIFO
- 优先级高者优先
(3)设备分配中的安全性
安全分配(同步):每当进程发出一个I/O请求后,即阻塞,直到其I/O完成。打破了死锁条件。
缺点:CPU、I/O 是串行工作的,进程进展缓慢。不安全分配(异步):需进行安全性检查,进程执行效率相对较高。
逻辑设备名到物理设备名映射的实现
逻辑设备表(LUT)
前面说过,逻辑设备表至少包含三项:
逻辑设备名 | 物理设备名 | 驱动程序入口 |
---|---|---|
xxx | xxx | xxx |
逻辑设备表的设置问题
对于LTU的设置有两种方式:
- 整个系统只设置一个LTU
这表明在这个LTU中,每个设备的名字都不可重复。
- 对于系统中的每一个用户都设置一个LTU
这表明在这个LTU中,每个设备的名字都可重复。但同时需要增加对用户表的管理。
6.6 用户层的I/O软件
系统调用与库函数
系统调用
对于保护计算机设备的安全性,实际上用户不能直接设备相关的任何指令,操作系统规定了不允许运行在用户态的应用进程,去直接调用运行在核心态(系统态)的OS过程。所以如果应用程序想要调用OS中的I/O过程,对I/O设备进行操作,就需要通过系统调用。
也就是说让系统帮你调用相关设备指令。
库函数
与系统调用相对应的是库函数(Library Function),它是由编程语言或第三方软件开发者提供的一组函数库,用于简化用户在应用程序中使用系统调用时可能遇到的复杂性。库函数通常集成了许多常见的操作系统功能,如字符串处理、数学计算、图形界面等,通过调用这些函数,应用程序可以更加方便地完成相应的任务。
一般来说在用户层写程序我们调用的都是已经封装好的库函数。
假脱机系统
引入
为了缓和CPU的高速性与I/O设备低速性间的矛盾,而引入了脱机输入、脱机输出技术。该技术是利用专门的外围控制机,先将低速I/O设备上的数据,传送到高速磁盘上,或者相反。
用两道程序模拟外围控制机功能:
- 一道模拟脱机输入时的外围控制机功能,把低速I/O设备上的数据传送到高速磁盘
- 另一道模拟脱机输出时外围机的控制机功能,把数据从磁盘传送到低速输出设备上
我们把这种在联机情况下实现的同时外围操作的技术,称为SPOOLing(Simultaneaus Periphernal Operating OnLine)技术,或称为假脱机技术。
为什么叫脱机技术?脱机是因为实际输入输出并不是又主机完成的,而是由外围控制机完成。 那什么是假脱机技术?这是因为实际我们并不需要真实的外围控制机,而是通过软件模拟的方式实现脱机。
SPOOLing系统组成
- 输入井和输出井:这是磁盘上开辟出来的两个区域。输入井和输出井中的数据一般以文件形式组织,称为井文件。
- 输入缓冲区和输出缓冲区:这是内存中开辟出来的两个区域,用于缓和CPU和磁盘之间速度不匹配的矛盾。
- 输入进程和输出进程:输入进程用于模拟脱机输入时的外围机而输出进程用于模拟脱机输出时的外围机。
- 井管理程序:用于控制作业与井之间的信息交换。当作业执行过程中向某台设备发出I/O请求时,由OS调用井管理程序,由其控制从输入井读取信息或将信息输出至输出井。
假脱机打印机系统
一般来说打印机属于独占设备。也就说说同一时间只能有一个用户使用打印机。利用假脱机技术,可将它改造为一台可供多个用户共享的打印设备,从而提高设备的利用率。
思路如下:
当多个用户进程提出输出打印的请求时,系统会答应它们的请求,但是并不是真正把打印机分配给他们,而是由假脱机管理进程为每个进程做两件事:
- 在磁盘输出井中为进程申请一个空闲缓冲区(也就是说,这个缓冲区是在磁盘上的),并将要打印的数据送入其中;
- 为用户进程申请一张空白的打印请求表,并将用户的打印请求填入表中(其实就是用来说明用户的打印数据存放位置等信息的),再将该表挂到假脱机文件队列上。
当打印机空闲时,输出进程会从文件队列的队头取出一张打印请求表,并根据表中的要求将要打印的数据从输出井传送到输出缓冲区,再输出到打印机进行打印。用这种方式可依次处理完全部的打印任务
假脱机打印机系统的特点
和普通进程不同的地方在于守护进程是运行在后台,不与任何用户交互的服务型进程,通常负责系统的某些基本任务,如管理打印队列、监听网络等。而常规进程是由用户直接启动和操作的应用程序进程,需要在用户前台界面进行交互操作。
守护进程可以实现:
- 假脱机打印机
- 把一台独占设备改造为可为多个进程共享的设备
6.7 缓冲区管理
缓冲的引入
缓冲区是一个存储区域,可以由专门的硬件寄存器组成,也可利用内存作为缓冲区。
使用硬件作为缓冲区的成本较高,容量也较小,一般仅用在对速度要求非常高的场合(如存储器管理中所用的联想寄存器,由于对页表的访问频率极高,因此使用速度很快的联想寄存器来存放页表项的副本)
一般情况下,更多的是利用内存作为缓冲区,“设备独立性软件”的缓冲区管理就是要组织管理好这些缓冲区。
单缓冲区和双缓冲区
我们首先规定如下符号:
T
: 从磁盘把一块数据送至缓冲区的时间M
: OS将缓冲区中的数据传送到用户区的时间C
: CPU对这一块数据的处理时间
单缓冲区
单缓冲区如上图所示,操作系统在内存中会分配一个缓冲区,没有特别说明的情况下,缓冲区的大小为一个字块的大小。(所以说内存和设备交换是按照字块大小进行交换,同样CPU一次处理数据的大小也是按照字块处理)
缓冲机制有一个一定要遵守的原则:
💡当缓冲区数据非空时,不能往缓冲区冲入数据,只能从缓冲区把数据传出。当缓冲区为空时,可以往缓冲区冲入数据,但必须把缓冲区充满以后,才能从缓冲区把数据传出。
这一原则同样针对于工作区。
后面所有的缓冲区都需要遵守这个原则
接下来就需要计算每处理一个字块的数据,平均需要多长时间。
首先假定初始状态:工作区**满,缓冲区空。
这里有两种情况:
(1) T > C
此时由于缓冲区为空,所以设备可以向缓冲区传递数据。工作区满,所以CPU此时可以处理工作区数据。上述两部互不干扰可以同时进行。
但由于T > C,这意味着CPU处理数据块的时间更短,工作区会在缓冲区冲满之前先空闲。
同时缓冲区不满之前又无法传输数据,只能等待设备传输数据完毕之后,缓冲区才可以向工作区传输数据。
同样对于工作区,工作区不满,CPU也无法处理数据,只能等待缓冲区向工作区传输数据完毕之后才继续工作。
于是有了如下时间图:
从图中可以看出,每T + M
个时间,整个系统就恢复初始状态,即工作区满,缓冲区**空。所以系统对每一块数据的处理时间表示为T + M
(2)T < C
这种情况与上述情况很类似,直接看图:
从图中可以看出,每C + M
个时间,整个系统就恢复初始状态,即工作区**满,缓冲区空**。所以系统对每一块数据的处理时间表示为C + M
综合上述两种情况,我们可以知道,采用单缓冲的策略下,处理一块数据的平均耗时是:**Max(T,C) + M**
双缓冲区
双缓冲区顾名思义,就是在内存设立两个缓存区,一般来说哪个缓存区空,设备就向哪个缓存区传输数据,那个缓存区满,哪个缓存区就向工作区传输数据。
对于双缓冲区处理一块数据的平均耗时这里不予推导了,记结论:
双缓冲题目中,假设初始状态为:工作区空,其中一个缓冲区满,另一个缓冲区空结论 采用双缓冲策略,处理一个数据块的平均耗时为
**Max (T,C+M)**
两者的区别
单缓冲区有一个最致命的缺陷,即为单缓冲区只能单向传输,也就是同一时刻只能由一端传输到另一端:
双向缓冲区就可以做到双向传输,同一时刻两端可以互相传输:
环形缓冲区
环形缓冲区包含了多个缓冲区,通过两个指针指向下一个可以冲入数据的缓冲区以及下一个可以取出数据的缓冲区:
了解原理即可。
缓冲池
缓冲池结构如下:
这里设立了四个工作缓冲区,同时设立三个队列:
- 空缓冲队列emq
- 输入队列inq
- 输出队列outq
工作方式如下:
- 收容输入:输入进程调用Getbuf(emq),从空缓冲区队首摘下一个缓冲区,把它作为收容输入工作缓冲区hin,再调用PutBuf(inq,hin)过程,将它挂在输入队列inq队列上。
- 提取输入:计算进程调用Getbuf(inq),从输入缓冲区队首摘下一个缓冲区,把它作为提取输入工作缓冲区sin,输入提取完毕后,再调用PutBuf(emq,sin)过程,将sin挂在空队列emq队列上。
- 收容输出:计算进程调用Getbuf(emq),从空缓冲区队首摘下一个缓冲区,把它作为收容输出工作缓冲区hout,再调用PutBuf(outq,hout)过程,将它挂在输出队列hout队列上。
- 提取输出:输出进程调用Getbuf(outq),从输出缓冲区队首摘下一个缓冲区,把它作为提取输出工作缓冲区sout,输出提取完毕后,再调用PutBuf(emq,sout)过程,将sout挂在空队列emq队列上。
同样了解即可。
6.8 磁盘存储器的性能和调度
磁盘的基本概念
数据的组织和格式
磁盘是一种计算机存储设备,通常由一个或多个可旋转的**圆盘**
构成,这些圆盘上记录着数字信息,可以持久保存在磁盘中。每个磁盘通常都有一个或多个读/写头,可以对磁盘表面进行读写操作。
以下是与磁盘相关的一些基本概念:
- 扇区(Sector):磁盘被划分成许多相同大小的扇区,每个扇区存储一个固定长度的数据块。扇区大小通常为512字节或4K字节。
- 磁道(Track):磁盘表面上的圆形轨迹称为磁道,通常沿着同心圆排列。每个磁道包含若干个扇区,其数量取决于磁盘的容量和格式。
- 柱面(Cylinder):一个柱面指的是所有磁盘表面上相同半径的磁道组成的集合,柱面的数量等于所有盘片上磁道数目的最大公因数。
- 磁头(Head):磁盘驱动器使用读/写头从磁盘表面读取或写入数据。每个磁盘都有多个磁头,其数量等于每个盘面的数量,每个读/写头在磁盘表面上代表一个区域。
一般来说磁盘都是只有一面存放数据,但是也有两面都用于存放数据的。磁盘存放数据可以简单理解为把需要的数据“雕刻”在盘面上,一般来说都是按照磁道进行“雕刻”的,所以想要读取数据就只需要磁头读取磁道即可。
一个磁道上面也需要存放不同的数据块,所以才有了扇区的概念,一般来说一个数据块占据一个磁道上的一个扇区。
由于每一个磁道上存放的数据大小都是一致的,所以内圈的磁道往往数据密度大于外圈磁道。
如右图所示,一组磁盘会配备一组的磁头,这里的磁头都是共同进退的,一般来说每个磁盘的磁道都是统一大小的,所以整组磁盘读取数据的时候,读取的都是同一圈的磁道。
这一组磁盘就可以看做是一个存储器。
在一组磁盘中,定位到具体的数据的物理位置就需要三个属性:
- 柱面号
- 盘面号
- 扇区号
可根据该地址读取一个“块”
① 根据“柱面号”移动磁臂,让磁头指向指定柱面;
② 激活指定盘面对应的磁头;
③ 磁盘旋转的过程中,指定的扇区会从磁头下面划过,这样就完成了对指定扇区的读/写。
磁盘的类型
移动头磁盘
每一个盘面仅配有一个磁头,也被装入磁臂中。为能访问该盘面上的所有磁道,该磁头必须能移动以进行寻道。可见,移动磁头仅能以串行方式读/写,致使其I/O速度较慢。
固定头磁盘
这种磁盘在每条磁道上都有一读/写磁头,所有的磁头都被装在一刚性磁臂中。通过这些磁头可访问所有各磁道,并进行并行读/写,有效地提高了磁盘的I/O速度。这种结构的磁盘主要用于大容量磁盘上。
磁盘访问时间
从前文我们知道,想要磁头找到数据的物理地址需要经过以下步骤:
- 找到目标的磁盘
- 找到目标的磁道
- 找到目标的扇区
如果在单一的磁盘上,则只需要后面两步需要时间。
对于后面两步再详细展开需要花费的时间就是:
- 寻道时间Ts(寻找磁道的时间)
- 磁头启动时间:
s
- 移动磁头的时间:假设一个磁道跨越时间为
m
,总共需要跨域n
个磁道,则需要m * n
- 磁头启动时间:
- 延迟时间Tr(寻找扇区的时间):假设磁盘旋转的速度为
r
,那么平均延迟时间为1/2r
- 传输时间Tt(磁盘传输数据的时间):从磁盘读出或向磁盘写入数据所经历的时间,假设磁盘转速为
r
,此次读/写的字节数为b
,每个磁道上的字节数为N
。则Tt=(1/r)* (b/N)= b/(rN)
这里的推导过程省略,有兴趣的可以自己推导一下。
所以加总上述的时间,可以得出磁盘访问的时间之和为:
Ta = s + m n+1/2r+b/rN
为了提高性能,就需要缩短磁盘的整体访问时间,在这三段时间中,延迟时间和磁盘旋转速度挂钩,这属于硬件层面,操作系统无法干预;传输时间中,r也取决于硬件,b是需要读写的字节数改变了没有意义,N为磁道上的字节数同样也属于硬件决定,所以这两段时间操作系统都无法缩短。
剩下唯一能缩短的就是寻道时间,也就是说操作系统可以通过*决定磁头寻找磁道的顺序来缩短寻道时间,这就属于磁盘调度算法的内容。
磁盘调度算法
在后面的讲解中,我们设定一个实例的场景,假设需要读取以下编号的磁道上的数据:
55、58、39、18、90、160、150、38、184
读取磁道数的请求按照顺序依次到来,在读取前所有的请求都已经到达了。
初始情况下磁头在**100号磁道**
。
先来先服务(FCFS)
顾名思义,我们需要根据读取请求的顺序来访问磁道的数据:
(从100号磁道开始) | |
---|---|
被访问的下一个磁道号 | 移动距离(磁道数) |
55 | 45 |
58 | 3 |
39 | 19 |
18 | 21 |
90 | 72 |
160 | 70 |
150 | 10 |
38 | 112 |
184 | 146 |
平均寻道长度:55.3 |
优点:公平;如果请求访问的磁道比较集中的话,算法性能还算过的去
缺点:如果有大量进程竞争使用磁盘,请求访问的磁道很分散,则FCFS在性能上很差,寻道时间长。
最短寻道优先(SSTF)
首先选择要求访问的磁道与当前磁头所在的磁道,距离最近的进程,以使每次的寻道时间最短。但这种算法不能保证平均寻道时间最短。
(从100号磁道开始) | |
---|---|
被访问的下一个磁道号 | 移动距离(磁道数) |
90 | 10 |
58 | 32 |
55 | 3 |
39 | 16 |
38 | 1 |
18 | 20 |
150 | 132 |
160 | 10 |
184 | 124 |
平均寻道长度:27.5 |
从100
开始移动的话,在55、58、39、18、90、160、150、38、184
这个队列中,90
距离100
最近,所以优先访问90
,后续同理。
优点:性能较好,平均寻道时间短
缺点:可能产生“饥饿”现象
例如:在本例中,如果在处理18号磁道的访问请求时又来了一个38号磁道的访问请求,处理38号磁道的访问请求时又来了一个18号磁道的访问请求。如果有源源不断的18号、38号磁道的访问请求到来的话,150、160、184号磁道的访问请求就永远得不到满足,从而产生“饥饿”现象。
扫描算法(SCAN)
扫描算法是对短寻道优先算法的改进,防止老进程出现饥饿现象。
这种算法需要自里向外地访问,直至再无更外的磁道需要访问时,才将磁臂换向,自外向里移动。
(从100号磁道开始) | |
---|---|
被访问的下一个磁道号 | 移动距离(磁道数) |
150 | 50 |
160 | 10 |
184 | 24 |
90 | 94 |
58 | 32 |
55 | 3 |
39 | 16 |
38 | 1 |
18 | 20 |
平均寻道长度:27.8 |
由于需要自里向外访问,所以第一个找的是比100
大,且距离100
最近的磁道,也就是150
,接着依次向外扩展,直到184
,没有比184
更大的编号了之后,再不断向内部扩展。
优点:性能较好,平均寻道时间较短,不会产生饥饿现象
缺点:SCAN算法对于各个位置磁道的响应频率不平均(如:假设此时磁头正在往右移动,且刚处理过90号磁道,那么下次处理90号磁道的请求就需要等磁头移动很长一段距离;而响应了184号磁道的请求之后,很快又可以再次响应184号磁道的请求了)
循环扫描算法
算法规定磁头单向移动。例如,只自里向外移动,当磁头移到最外的磁道时,磁头立即返回到最里的欲访磁道,即将最小磁道号紧接着最大磁道号构成循环,进行扫描。
(从100号磁道开始) | |
---|---|
被访问的下一个磁道号 | 移动距离(磁道数) |
150 | 50 |
160 | 10 |
184 | 24 |
18 | 166 |
38 | 20 |
39 | 1 |
55 | 16 |
58 | 3 |
90 | 32 |
平均寻道长度:27.5 |
如图所示: