5.1 IO管理概述
IO设备的基本概念和分类
- 什么是IO设备?
I/O就是输入/输出
IO设备就是可以将数据输入到计算机,或者可以接收计算机输出数据的外部设备,属于计算机中的硬件部件。
UNIX系统将外部设备抽象成一种特殊的文件,用户可以使用与文件操作相同的方式对外部设备进行操作。 - IO设备分类:
- 按使用特性分类:
- 人机交互类外设:鼠标、键盘、打印机
数据传送速度慢 - 存储设备:U盘、光盘
数据传输速度块 - 网络通信设备:猫(调制解调器)
数据传输速度介于二者之间
- 人机交互类外设:鼠标、键盘、打印机
- 按传输速率分类:
- 低速设备:鼠标、键盘
每秒几个到几百字节 - 中速设备:激光打印机
每秒数千至上万个字节 - 高速设备:如磁盘
每秒数千至千兆字节
- 低速设备:鼠标、键盘
- 按信息交换的单位分类:
- 块设备:磁盘
传输基本单位是块,传输速率较高,可寻址,对它可随机读写任一块 - 字符设备:鼠标、键盘
数据传输基本单位是字符,数据传输速率较慢,不可寻址,在IO时常采用中断驱动方式。
- 块设备:磁盘
IO控制器
IO设备由机械部件和电子部件(IO控制器、设备控制器)组成。
CPU无法直接控制IO设备的机械部件,因此IO设备还要有一个电子部件作为CPU和IO设备机械部件之间的“中介”,用于实现CPU对设备的控制。
IO控制器的功能:
- 接受和识别CPU发出的命令。
若CPU发来的read/write命令,IO控制器中会有相应的控制寄存器来存放命令和参数。 - 向CPU报告设备的状态。
IO控制器中会有相应的状态寄存器,用于记录IO设备的当前状态。如1表示空闲,0表示忙碌。 - 数据交换。
IO控制器中会设置相应的数据寄存器。输出时,数据寄存器用于暂存CPU发来的数据,之后再由控制器传送设备。输入时,数据寄存器用于暂存设备发来的数据,之后CPU从寄存器中取走数据。 - 地址识别。
类似于内存的地址,为了区分设备控制器中的各个寄存器,也需要给各个寄存器设置一个特定的“地址”。IO控制器通过CPU提供的“地址”来判断CPU要读/写的是哪个寄存器。
IO控制器的组成:
- CPU与控制器的接口
用于实现CPU与控制器之间的通信。CPU通过控制线发出命令;通过地址线指明要操作的设备;通过数据线来取出输入数据或放入输出数据。 - IO逻辑
负责接收和识别CPU的各种命令。 - 控制器与设备的接口
用于实现控制器与设备之间的通信。
注:一个IO控制器可能会对应多个设备。
数据寄存器、控制寄存器、状态寄存器可能有多个(如:每个控制/状态寄存器对应一个具体的设备),且这些寄存器都要有相应的地址,才能方便CPU操作。有的计算机会让这些寄存器占用内存地址的一部分,称为内存映像IO;另一些计算机则采用IO专用地址,即寄存器独立编址。
内存映像IO:内存和IO寄存器的地址连续。优点:简化了指令,可以采用对内存进行操作的指令来对控制器进行操作。
寄存器独立编址:内存和IO寄存器的地址不连续。
缺点:需要设置专门的指令来实现对控制器的存在,不仅要指明寄存器的地址,还要指明控制器的编号。
IO控制方式
IO控制方式,即用什么样的方式来控制IO设备的数据读写。
需要注意:
- 完成一次读写操作的流程
- CPU干预的频率
- 数据传送的单位
- 数据的流向
- 主要缺点和主要优点
程序直接控制方式
- 完成一次读写操作的流程(轮询):
- CPU向控制器发出读指令。于是设备启动,并且状态寄存器设为1(未就绪)
- CPU轮询检查控制器的状态(不断地执行程序的循环,若状态位一直是1,说明设备还没准备好要输入的数据,于是CU会不断地轮询)
- 输入设备准备好数据后将数据传给控制器,并报告自身状态。
- 控制器将输入的数据放到数据寄存器中,并将状态改为0(已就绪)
- CPU发现设备已就绪,即可将数据寄存器中的内容读入CPU的寄存器中,再把CPU寄存器中的内容存入内存。
- 若还要继续读入数据,CPU发送下一条读命令。
- CPU干预的频率:
很频繁,IO开始之前、完成之后需要CPU介入,并且在等待IO完成的过程中CPU需要不断地轮询检查。 - 数据传送的单位:
每次读写一个字 - 数据的流向:
读操作(数据输入):IO设备 -> CPU -> 内存
写操作(数据输出):内存 -> CPU -> IO设备
每个字的读写都需要CPU帮助。 - 主要缺点和主要优点:
优点:实现简单。在读写指令之后,加上实现循环检查的一系列指令即可。
缺点:CPU和IO设备只能穿行工作,CPU需要一直轮询检查,长期处于“忙等”状态,CPU利用率低。
中断驱动方式
引入中断机制。由于IO设备速度很慢,因此在CPU发出读写命令后,可等待IO的进程阻塞,先切换到别的进程执行。当IO完成后,控制器会向CPU发出一个中断信号,CPU检测到中断信号后,会保存当前进程的运行环境信息,转去执行中断处理程序处理该中断。处理中断的过程中,CPU从IO控制器读一个字的数据传送到CPU寄存器,再写入主存。接着,CPU回复等待IO的进程(或其他进程)的运行环境,然后继续执行。
- 完成一次读写操作的流程(中断):
注:CPU会在每个指令周期的末位检查中断。
中断处理过程需要保存、恢复进程的运行环境,这个过程是需要一定时间开销的。可见如果中断发生的频率太高,也会降低系统性能。 - CPU干预的频率:
每次IO开始之前、完成之后需要CPU介入。
等待IO完成的过程中CPU可以切换到别的进程执行。 - 数据传送的单位:
每次读写一个字 - 数据的流向:
读操作(数据输入):IO设备 -> CPU -> 内存
写操作(数据输出):内存 -> CPU -> IO设备 - 主要缺点和主要优点:
优点:与“程序直接控制方式”相比,在“中断驱动方式”中,IO控制器会通过中断信号主动报告IO已完成,CPU不再需要不停地轮询。CPU和IO设备可并行工作,CPU利用率得到明显提升。
缺点:每个字在IO设备与内存之间的传输,都需要经过CPU。而频繁的中断处理会消耗较多的CPU时间。
DMA方式
与“中断驱动方式”相比,DMA方式(直接存储器存取)主要用于块设备的IO控制,有这样几个改造:
a. 数据的传送单位是“块”。不再是一个字一个字的传送。
b. 数据的流向是从设备直接放入内存,或者从内存直接到设备,不再需要CPU中间传递。
c. 仅在传送一个或多个数据块的开始和结束时,才需要CPU干预。
DMA控制器:
-
完成一次读写操作的流程:
-
CPU干预的频率:
仅在传送一个或多个数据块的开始和结束时,才需要CPU干预。 -
数据传送的单位:
每次读写一个或多个块(注:每次读写的只能是连续的多个块,且这些块读入内存后在内存中也必须是连续的) -
数据的流向:
读操作(数据输入):IO设备 -> 内存
写操作(数据输出):内存 -> IO设备 -
主要缺点和主要优点:
优点:数据传输率以“块”为单位,CPU介入频率进一步降低。数据的传输率不再需要先经过CPU再写入内存,数据传输效率进一步增加。CPU和IO设备的并行性得到提升。
缺点:CPU每发出一条IO指令,只能读写一个或多个连续的数据块。如果要读写多个离散存储的数据块,或者要将数据分别写到不同的内存区域中,CPU要分别发出多条IO指令,进行多次中断处理才能完成。
通道控制方式
通道:一种硬件,可以理解为简化版CPU,通道可以识别并执行一系列通道指令。
- 完成一次读写操作的流程:
CPU向通道发出IO指令,指明通道程序在内存中的位置,并指明要操作的是哪个IO设备,之后CPU就切换到其他进程执行了。
通道执行内存中的通道程序(其中指明了要读入/写出多少数据,读写的数据应防在内存的什么位置等信息)
通道执行完规定的任务后,向CPU发出中断信号,之后CPU对中断进行处理。 - CPU干预的频率:
极低,通过会根据CPU的指示执行相应的通道程序,只有完成一组数据块的读写之后才需要发出中断信号,请求CPU干预。 - 数据传送的单位:
每次读写一组数据块。 - 数据的流向:
读操作:IO设备 -> 内存
写操作:内存 -> IO设备 - 主要缺点和主要优点:
优点:CPU、通道、IO设备可并行工作,资源利用率很高。
缺点:实现复杂,需要专门的通道硬件支持。
IO软件层次结构
其中,设备独立性软件、设备驱动软件、中断处理程序属于OS内核部分,即IO系统,或称“IO核心子系统”。这些层次中越靠上越接近用户层。
用户层软件
用户层软件实现了与用户交互的接口,用户可直接使用用户层提供的、与IO操作相关的库函数对设备进行操作。
例:printf("hello world!");
用户层软件将用户请求翻译成格式化的IO请求,并通过“系统调用”请求OS内核的服务。
例:printf("hello world!");会被翻译成等价的write系统调用,用户层软件也会在系统调用时填入相应参数。
Windows操作系统向外提供的一系列系统调用,但是由于系统调用的格式严格,使用麻烦,因此在用户层上封装了一系列更方便的库函数接口供用户使用(Windows API)。
设备独立性软件
设备独立性软件,又称设备无关性软件。与设备的硬件特性无关的功能几乎都在这一层实现。
主要功能:
- 向上层提供统一的调用接口(如read/write系统调用)
- 设备的保护。
原理类似文件保护。设备被看作是一种特殊的文件,不同用户对各个文件的访问权限是不一样的,同理,对设备的访问权限也不一样。 - 差错处理。对一些设备的错误进行处理。
- 设备的分配与回收。
- 数据缓冲区管理。
可以通过缓冲技术屏蔽设备之间数据交换单位大小和传输速率的差异。 - 建立逻辑设备名到物理设备名的映射关系;根据设备类型选择调用相应的驱动程序。
用户层软件发出IO操作相关系统调用时,需要指明此次操作的IO设备的逻辑设备名(用户可见的设备名)。
设备独立性软件需要通过“逻辑设备表(LUT)”来确定逻辑设备对应的物理设备,并找到该设备对应的设备驱动程序。
OS可采用两种方式管理逻辑设备表(LUT):
第一种方式:整个系统只设置一张LUT,这就意味着所有用户不能使用相同的逻辑设备名,因此这种方式只适用于单用户OS。
第二种方式:为每个用户设置一张LUT,各个用户使用的逻辑设备名可以重复,适用于多用户OS。系统会在用户登录时为其建立一个用户管理进程,而LUT就存放在用户管理进程的PCB中。
设备驱动程序
主要负责对硬件设备的具体控制,将上层发出的一系列命令(如read/write)转化为特定设备“能听懂”的一系列操作。包括设置设备寄存器;检查设备状态等。
不同的IO设备有不同的硬件特性,具体细节只有设备的厂家才知道。因此厂家需要根据设备的硬件特性设计并提供相应的驱动程序。
注:驱动程序一般会以一个独立进程的方式存在。
中断处理程序
当IO任务完成时,IO控制器会发送一个中断信号,系统会根据中断信号类型找到相应 的中断处理程序并执行。中断处理程序的处理流程如下:
输入。输出应用程序接口
用户层的应用程序无法通过一个统一的系统调用来完成所有类型设备的IO。
字符设备接口
用户层软件如果要使用一个字符型设备,需要调用字符设备接口。
字符设备接口采用get/put系统调用:向设备读/写一个字符。
数据的读写是以字符为单位的。
字符型设备没有地址概念,所有读写用get/put。
块设备接口
块设备调用接口是read/write系统调用:向块设备的读写指针位置读/写多个字符;
seek系统调用:修改读写指针的位置。
块设备有地址概念,所有系统调用时需要提供地址参数。
网络设备接口
网络设备接口,又称“网络套接字(socket)接口”
socket系统调用:创建一个网络套接字,需指明网络协议(TCP? UDP?)
bind:将套接字绑定到某个本地端口。
connect:将套接字连接到远程地址。
read/write:从套接字读/写数据。
阻塞/非阻塞IO
- 阻塞IO:应用程序发出IO系统调用,进程需转为阻塞态等待。
e.g. 字符设备接口——从键盘都一个字符get。 - 非阻塞IO:应用程序发出IO系统调用,系统调用可迅速返回,进程无需阻塞等待。
e.g.块设备接口——往磁盘写数据write。
设备驱动程序接口
设备独立软件需要根据设备的不同,调用不同的设备驱动程序接口。
不同OS对设备驱动程序接口的标准各不相同,设备厂商必须根据OS的接口需求,开发相应的设备驱动程序,设备才能被使用。
5.2 设备独立性软件
IO核心子系统
- IO调度:
在设备独立性软件中实现,用某种算法确定一个好的顺序来处理各个IO请求。
如:磁盘调度(先来先服务算法、最短寻到优先算法、SCAN算法、C-SCAN算法、LOOK算法、C-LOOK算法)。当多个磁盘IO请求到来时,用某种调度确定满足IO请求的顺序。
同理,打印机等设备也可以用先来先服务、优先级算法、短作业优先算法来确定IO调度顺序。 - 设备保护:
设备保护,在设备独立性软件中实现。
在UNIX胸痛中,设备被看作是一种特殊的文件,每个设备也会有对应的FCB。当用户请求访问某个设备时,系统根据FCB中记录的信息来判断该用户是否会有相应的访问权限,以此实现“设备保护”的功能。
假脱机技术(SPOOLing技术)
-
什么是脱机技术,脱机技术可以解决什么问题?
批处理阶段引入了脱机技术/输出技术(用磁带完成):
在外围控制机的控制下,慢速输入设备的数据先被输入到更快速的磁带上。之后主机可以从快速的磁带上读入数据,从而缓解速度矛盾。
由于输入输出过程脱离主机的控制,所以被称为脱机操作。 -
假脱机技术的实现原理:
假脱机技术,又称“SPOOLing技术”,是用软件的方式模拟脱机技术。SPOOLing系统的组成如下:
- 输入井和输出井:
输入井模拟脱机技术输入时的磁带,用于收容IO设备输入的数据。
输出井模拟脱机技术输出时的磁带,用于收容IO设备输出的数据。 - 输入进程和输出进程:
输入进程模拟脱机输入时的外围控制机。
输出进程模拟脱机输出时的外围控制机。
要实现SPOOLing技术,必须要有多道程序技术的支持。系统会建立“输入进程”和“输出进程”。 - 输入缓冲区和输出缓冲区:
输入缓冲区和输出缓冲区在内存中,输入缓冲区在输入进程控制下暂存输入设备输入的数据,之后转存到输入井中。在输出进程控制下,输出缓冲区用户暂存从输出井送来的数据,之后再传送到输出设备上。
- 输入井和输出井:
-
共享打印机的原理分析:
独占式设备:只允许各个进程串行使用的设备。一段时间内只能满足一个进程的请求。
共享设备:允许多个进程“同时”使用的设备(宏观上同时使用,微观上可能是交替使用)。可以同时满足多个进程的使用请求。
打印机属于“独占式设备”,但是可以用SPOOLing技术改造成“共享设备”(独占式设备例子: 若进程1正在使用打印机,则进程2请求使用打印机时必然阻塞等待)
当多个用户提出输出打印的请求时,系统会答应它们的请求,但并不是真的吧打印机分配给它们,而是由假脱机技术管理进程,为每个进程做两件事:
- 在磁盘输出井中为进程申请一个空闲缓冲区(也就是说,这个缓冲区在磁盘上),并将要打印的数据送入其中。
- 为用户申请一张空白的打印请求表,并将用户的打印请求填入表中(其实就是用户来说明用户的打印数据存放位置等信息的),再将该表挂到假脱机文件队列上。
当打印机空闲时,输出进程会从文件队列的队头取出一张打印请求表,并根据表中的要求将要打印的数据从输出井传送到输出缓冲区,在输出到打印机进行打印。用这种方式可依此处理完出全部的打印任务。
采用SPOOLing技术后,虽然系统中只有一台打印机,但每个进程提出打印请求时,系统都会在输出井中为其分配一个存储区(相当于分配了一个逻辑设备),使每个用户进程都觉得自己在独占一台打印机,从而实现对打印机的共享。
设备的分配与回收
设备分配时应考虑的因素
- 设备的固有属性:
可分为三种:独占设备、共享设备、虚拟设备。
独占设备——一个时段内只能分配给一个进程(如打印机)。
共享设备——可同时分配给多个进程使用(如磁盘),各进程往往是宏观上同时共享设备,而微观上交替使用。
虚拟设备——采用SPOOLing技术将独占设备改造成共享设备,可同时分配给多个进程使用。 - 设备分配算法:
先来先服务
优先级高者优先
短任务优先
... - 设备分配中的安全性
从进程运行的安全性上考虑,设备分为两种方式:- 安全分配方式:为进程分配一个设备后就将进程阻塞,本次IO完成后才将进程唤醒。
一个时段内每个几次呢只能使用一个设备。
优点:破坏了“请求和保持”条件,不会死锁。
缺点:对于一个进程来说,CPU和IO设备只能串行工作。 - 不安全分配方式:进程发出IO请求后,系统为其分配IO设备,进程可继续执行,之后还可以发出新的IO请求。只有某个请求得不到满足时才能将进程阻塞。
一个进程可以同时使用多个设备。
优点:进程的计算任务和IO任务可以并行处理,使进程迅速推进。
缺点:有可能发生死锁(死锁避免、死锁检测和解除)
- 安全分配方式:为进程分配一个设备后就将进程阻塞,本次IO完成后才将进程唤醒。
静态分配与动态分配
- 静态分配:进程在运行前为其分配全部所需资源,运行后归还资源。
破坏了“请求和保持条件”,不会发生死锁。 - 动态分配:进程运行过程中申请设备资源。
设备分配管理中的数据结构
“设备、控制器、通道”之间的关系:
一个通道可以控制多个控制器,每个设备控制器可以控制多个设备。
一个系统中可以由多个通道。
系统为每个设备配置一张设备控制表(DCT),用于记录设备情况。
设备控制表的各个字段及其含义如下表:
字段 | 含义 |
---|---|
设备类型 | 如打印机、扫描仪、键盘 |
设备标识符 | 物理设备名,系统中每个设备的物理设备名唯一 |
设备状态 | 忙碌、空闲、故障 |
指向控制器表的指针 | 每个设备由一个控制器控制,该指针可找到相应控制器的信息 |
重估执行次数或时间 | 当重复执行多次IO存在后仍不成功,才认为此次IO失败 |
设备队列的队首指针 | 指向正在等待该设备的进程队列(由进程PCB组成队列) |
控制器控制表(COCT):每个设备控制器都会对应一张COCT。OS会根据COCT的信息对控制器进行操作和管理。
控制器控制表的各个字段及其含义如下表:
字段 | 含义 |
---|---|
控制器标识符 | 每个控制器的唯一ID |
控制器状态 | 忙碌、空闲、故障 |
指向通道表的指针 | 每个控制器由一个通道控制,该指针可找到相应通道的信息 |
控制器队列的队首指针 | 指向正在等待该控制器的进程队列(由进程PCB组成队列) |
控制器队列的队尾指针 | 指向正在等待该控制器的进程队列(由进程PCB组成队列) |
通道控制表(CHCT):每个通道会对应一张CHCT。OS根据CHCT的信息对通道进程操作和管理。
通道控制表的各个字段及其含义如下表:
字段 | 含义 |
---|---|
通道标识符 | 每个通道的唯一ID |
通道状态 | 忙碌、空闲、故障 |
与通道连接的控制器表首地址 | 可通过该指针找到该通道管理的所有控制器的相关信息 |
通道队列的队首指针 | 指向正在等待该通道的进程队列(由进程PCB组成队列) |
通道队列的队尾指针 | 指向正在等待该通道的进程队列(由进程PCB组成队列) |
系统设备表(SDT):记录了系统中全部设备的情况,每个设备对应一个表目。
设备分配的步骤
- 根据进程请求的物理设备名查找SDT(注:物理设备名是进程请求分配设备时提供的参数)
- 根据SDT找到DCT,若设备忙碌则将进程PCB挂到设备等待队列中,不忙碌则将设备分配给进程。
- 根据DCT找到COCT,若控制器忙碌则将进程PCB挂到控制器等待队列中,不忙碌则将控制器分配给进程。
- 根据COCT找到CHCT,若通道忙碌则将进程PCB挂到通道等待队列中,不忙碌则将通道分配给进程。
缺点:
- 用户编程时必须使用“物理设备名”,底层细节对用户不透明,不方便编程。
- 若换了一个物理设备,则程序无法运行。
- 若进程请求的物理设备正在忙碌,则即使系统中还有同类型的设备,进程也必须阻塞等待。
改进方法:建立逻辑设备名与物理设备名的映射机制,用户编程时只需提供逻辑设备名。
设备分配步骤的改进方法
- 根据进程请求的逻辑设备名查找SDT(注:用户编程时提供的逻辑设备名其实就是“设备类型”)
- 查找SDT,找到用户进程指定类型的、并且空闲的设备,将其分配给该进程。OS在逻辑设备表(LUT)中新增一个表项。
- 根据DCT找到COCT,若控制器忙碌则将进程PCB挂到控制器等待队列中,不忙碌则将控制器分配给进程。
- 根据COCT找到CHCT,若通道忙碌则将进程PCB挂到通道等待队列中,不忙碌则将通道分配给进程。
用户进程第一次使用设备时逻辑设备名向OS发出请求,OS根据用户几进程指定的设备类型(逻辑设备名)查找系统设备表,找到一个空闲设备分配给进程,并在LUT中增加相应表项。
只有第一次请求设备的时候会查找逻辑设备表(LUT),第二次请求相同设备时,OS通过LUT即可知道用户进程实际要使用的是哪个物理设备了,并且也能知道该设备的驱动程序入口地址。
逻辑设备表的设置问题:
整个OS只有一张LUT:各用户所使用的逻辑设备名不能重复,适用于单用户OS。
每个用户一张LUT:不同用户的逻辑设备名可重复,适用于多用户OS。
缓冲区管理
- 什么是缓冲区?有什么作用?
缓冲区是一个存储区域,可由专门的硬件寄存器组成,也可以利用内存作为缓冲区。
使用硬件作用缓冲区的成本较高,容量也较小,一般仅用在对速度要求非常高的场合(如存储器管理中多用的联想寄存器,由于对页表访问频率极高,因此使用速度很快的联想寄存器来存放页表项的副本)
一般情况下,更多的是利用内存作为缓冲区,“设备独立性软件”的缓冲区管理就是要组织管理好这些缓冲区。
缓冲区的作用:
- 缓和CPU与IO设备之间速度不匹配的矛盾。
CPU可以把要输出的数据快速地放入缓冲区,之后做别的事情。 - 减少对CPU的中断频率,放宽对CPU中断响应时间的限制。
只有缓冲区的数据被全部取走后IO设备才会中断CPU请求新的数据。 - 解决数据粒度不匹配的问题。
- 提高CPU与IO设备之间的并行性。
单缓冲
假设某用户进程请求某种设备读入若干块的数据。若采用单缓冲的策略,OS会在主存中为其分配一个缓冲区(若题目没有特别说明,一个缓冲区的大小就是一块)。
注:当缓冲区数据非空时,不能往缓冲区冲入数据,只能从缓冲区把数据传出;当缓冲区为空时,可以往缓冲区冲入数据,但必须把缓冲区充满后,才能从缓冲区把数据传出。
用户进程的内存空间中,会分出一片工作区来接受输入/输出数据(一般也默认工作区大小与缓冲区相同)
常考题型:计算每处理一块数据平均需要多久?
技巧:假设一个初始状态,分析下次到达相同状态需要多少时间,这就是处理一块数据平均所需时间。
假设初始状态为工作区满,缓冲区空。
上图中括号里面的是每一步的时间。
假设T > C:
CPU处理数据和块设备输入数据可以同时进行,总时间为T,再经过M后又达到工作区满,缓冲区空的状态。
所以平均时长为T + M。
假设T < C:
CPU处理数据和块设备输入数据可以同时进行,总时间为C,再经过M后又达到工作区满,缓冲区空的状态。
所以平均时长为C + M。
综上,采用单缓冲策略,处理一块数据平均耗时Max(C, T) + M.
双缓冲
假设某用户进程请求某种设备读入若干块的数据。若采用双缓冲的策略,OS会在主存中为其分配两个缓冲区(若题目没有特别说明,一个缓冲区的大小就是一块)。
双缓冲题目中,假设初始状态为:工作区空,其中一个缓冲区满,另一个缓冲区空。
假设T > C + M:
把缓冲区1中的数据传送给工作区,并交给CPU处理用时C + M,给缓冲区2充入数据用时T,T > C + M,所以处理一块数据平均用时T。
假设T < C + M:用同样的方法难以分析出平均用时,最终结论是C + M。
采用双缓冲策略,平均处理一块数据耗时Max(T, C+M).
若两个相互通信的机器只设置单缓冲区,在任意时刻只能实现数据的单向传输。
若两个相互通信的机器只设置双缓冲区,则在同一时刻可以实现双向的数据传输。
循环缓冲
将多个大小相等的缓冲区链接成一个循环队列。
OS管理两个指针:
in指针指向下一个可以冲入数据的空缓冲区。
out指针,指向下一个可以取出数据的满缓冲区。
缓冲池
缓冲池由系统中共用的缓冲区组成,这些缓冲区按使用状况可分为:空缓冲队列、装满输入数据的缓冲队列(输入队列)、装满输出数据的缓冲队列(输出队列)。
另外,根据一个缓冲区在实际运算中承担的功能不同,又设置了四种工作缓冲区:用于收容输入数据的工作缓冲区(hin)、用于提取输入数据的工作缓冲区(sin)、用于收容输出数据的工作缓冲区(hout)、用户提取输出数据的工作缓冲区(sout)。
- 输入进程请求输入数据:
从空缓冲队列中取出一块作为收容输入数据的工作缓冲区(hin)。充满数据后将缓冲区挂到输入队列队尾。 - 计算进程想要取得一块输入数据:
从输入队列中取得一块充满输入数据的缓冲区作为“提取数据数据的工作缓冲区(sin)”。缓冲区读空后挂到空缓冲队列。 - 计算进程想要将准备好的数据冲入缓冲区:
从空缓冲队列中取出一块作为“收容输出数据的工作缓冲区(hout)”。数据充满后将缓冲区挂到输出队列队尾。 - 输出进程请求输出数据:
从输出队列中取得一块充满输出数据的缓冲区作为“提取输出数据的工作缓冲区(sout)”。缓冲区读空后挂到空缓冲队列。
5.3 磁盘和固态硬盘
磁盘的结构
磁盘、磁道、扇区
- 磁盘:磁盘表面由一些磁性物质 组成,可以用这些磁性物质来记录二进制数据。
- 磁盘的盘面会被划分成一个个磁道。下图中一个圈就是一个磁道:
- 一个磁道又被分为一个个扇区,每个扇区就是一个“磁盘块”。各个扇区存放的数据量相同(如1KB).
如何在磁盘中读写数据
需要把“磁头”移动到想要读写的扇区所在的磁道。
磁盘会转起来,让目标扇区从磁头下面划过,才能完成对扇区的读写操作。
磁盘的物理地址
可用(柱面号,盘面号,扇区号)来定位任意一个“磁盘块”。在“文件的物理结构”小节中,我们经常提到文件数据存放在外存中的几号块,这个块号就是可以转换成(柱面号,盘面号,扇区号)的地址形式。
可根据地址读取一个“块”:
- 根据“柱面号”移动磁臂,让磁头指向指定柱面。
- 激活指定盘面对应的磁头。
- 磁盘旋转的过程中,指定的扇区会从磁头下面划过,这样就完成了对指定扇区的读/写。
磁盘的分类
- 根据磁头是否可移动:
磁头可以移动的称为活头磁盘/磁臂可以来回伸缩带动磁头定位磁道。
磁头不可移动的称为固定头磁盘。这种磁盘中每个磁道有一个磁头。 - 根据盘片是否可更换:
盘片可更换的是可换盘磁盘。
盘片不可更换的是固定盘磁盘。
磁盘调度算法
一次磁盘读/写操作需要的时间
- 寻找时间:
寻找时间(寻道时间)\(T_s\):在读/写数据前,将磁头移动到指定磁道所花的时间。
- 启动磁头臂是需要时间的。假设耗时为s;
- 移动磁头也需要时间。假设磁头匀速移动,每跨越一个磁道耗时为m,总共需要跨越n条磁道。则:
寻道时间\(T_s = s + m * n\)
- 延迟时间:
延迟时间\(T_R\):通过旋转磁盘,使磁头定位到目标扇区所需要的时间。设磁盘转速为r(单位:转/秒,或转/分),则平均所需的延迟时间\(T_R = (1/2) * (1/r) = 1/2r\) - 传输时间:
传输时间\(T_t\):从磁盘读出或磁盘写入数据所经历的时间,假设磁盘转速为r, 此次读写的字节数为b,每个磁道上的字节数为N,则:
传输时间\(T_t = (1/r)*(b/N) = b/(rN)\)
综上,一次磁盘读/写操作需要的时间为:
\(T_a = T_s + 1/2r + b/(rN)\)
延迟时间和传输时间都与磁盘转速相关,且为线性相关。而转速是硬件的固有属性,因此OS也无法优化延迟时间和传输时间。
但是OS的磁盘调度算法会直接影响寻道时间。
磁盘调度算法
先来先服务(FCFS)
根据进程请求访问磁盘的先后顺序进行调度。
假设磁头的初始位置是100号磁道,有多个进程先后陆续地请求访问55、58、39、18、90、160、150、38、184号磁道。
按照FCFS规则,按照请求到达顺序,磁头需要依次移动到55、58、39、18、90、160、150、38、184号磁道。
磁盘总共移动了45+3+19+21+72+70+10+112+146=498个磁道。
响应一个请求平均需要移动55.3个磁道(平均寻找长度)
优点:公平;如果请求访问的磁道比较集中的话,算法性能还可以。
缺点:如果有大量进程竞争使用磁盘,请求访问的磁道很分散,则FCFS在性能上很差,寻道时间长。
最短寻找时间优先(SSTF)
SSTF算法会优先处理的磁道是与当前磁头最近的磁道。可以保证每次的寻道时间最短,但是并不能保证总的寻道时间最短。(其实就是贪心算法的思想,只是选择眼前最优,但是总体未必最优)
假设磁头的初始位置是100号磁道,有多个进程先后陆续地请求访问55、58、39、18、90、160、150、38、184号磁道。
平均需要移动248/9=27.5个磁道
优点:性能较好,平均寻道时间短
缺点:可能产生“饥饿”现象。产生饥饿的原因在于:磁头在一个小区域内来回移动。
扫描算法(SCAN)
SSTF算法产生饥饿的原因在于:磁头在一个小区域内来回移动。为了防止这个问题,可以规定,只有磁头移动到磁道最外侧时才能往内移动,移动到最内侧磁道的时候才能往外移动。这就是扫描算法SCAN的思想。由于磁头移动方式很像电梯,因此也叫电梯算法。
假设某磁盘的磁道为0~200号,磁头的初始位置是100号磁道,且此时磁头正在往磁道号增大的方向移动。有多个进程先后陆续地请求访问55、58、39、18、90、160、150、38、184号磁道。
优点:性能好,平均寻道时间短,不会产生饥饿现象。
缺点:
- 只有到达最边上的磁道时才能改变磁头移动方向,事实上,处理了184号磁道的访问请求之后就不需要再往右移动磁头了。
- SCAN算法对于各个位置的磁道的响应速率不平均(如:假设此时磁头正在往右移动,且刚处理过90号磁道,那么下次处理90号磁道的请求就需要等磁头移动很长一段时间;而响应了184号磁道的请求之后,很快又可以再次响应184号磁道的请求了。)
LOOK调度算法
SCAN算法中,只有到达最边上的磁道时才能改变磁头移动方向。LOOK调度算法就是为了解决这个问题,如果在磁道的移动方向上已经没有别的请求,就可以立即改变磁头的移动方向。(边移动边观察,因此叫LOOK)
假设某磁盘的磁道为0~200号,磁头的初始位置是100号磁道,且此时磁头正在往磁道号增大的方向移动。有多个进程先后陆续地请求访问55、58、39、18、90、160、150、38、184号磁道。
优点:比起SCAN算法,不需要每次都移动到最外侧或者最内侧才改变磁头方向,使寻道时间进一步缩短。
循环扫描算法(C-SCAN)
SCAN算法对于各个位置的磁道的响应速率不平均,而C-SCAN算法就是为了解决这个问题。规定只有磁头超某个方向移动时才处理磁道访问请求,而返回时直接快速移动至起始端而不处理任何请求。
假设某磁盘的磁道为0~200号,磁头的初始位置是100号磁道,且此时磁头正在往磁道号增大的方向移动。有多个进程先后陆续地请求访问55、58、39、18、90、160、150、38、184号磁道。
优点:比起SCAN来说,对于各个位置磁道的响应频率很平均。
缺点:只有到达最边上的磁道时才能改变磁头移动方向,事实上,处理了184号磁道的访问请求之后就不需要再往右移动磁头了。并且,磁头返回时其实只需要返回到18号磁道即可,不需要返回到最边缘的磁道。另外,比起SCAN算法,平均寻道时间更长。
C-LOOK调度算法
C-LOOK算法与C-SCAN算法类似,只不过如果磁头移动方向上已经没有磁道访问请求了,就可以立即让磁头返回,并且磁头只需要返回到有磁道访问请求的位置即可。
假设某磁盘的磁道为0~200号,磁头的初始位置是100号磁道,且此时磁头正在往磁道号增大的方向移动。有多个进程先后陆续地请求访问55、58、39、18、90、160、150、38、184号磁道。
优点:与C-SCAN相比,平均寻道时间缩短。
减少磁盘延迟时间的方法
磁头读入一个扇区数据后需要一小段时间处理,如果逻辑上相邻的扇区在物理上也相邻,则读入几个连续的逻辑扇区,可能需要很长的“延迟时间”。
交替编号
若采用交替编号的策略,即让逻辑上相邻的扇区在物理上有一定的间隔,可以使读取连续的逻辑扇区所需要的延迟时间更小。
磁盘地址结构的设计
为什么磁盘的物理地址是(柱面号,盘面号,扇区号)而不是(盘面号,柱面号,扇区号)?
因为(柱面号,盘面号,扇区号)这种地址结构在按照地址顺序读取数据的过程中不需要频繁地移动磁头,速度更快。
错位命名
如果每个盘面的同一位置的扇区编号相同,则在上一个盘面的地址读完后换到下一个盘面时会发现物理地址是连续的,磁盘来不及读入数据。所以采用错位命名。
磁盘的管理
磁盘初始化
磁盘刚生产出来只划分了磁道,在正式出厂前需要初始化,初始化的过程如下:
- 低级格式化(物理格式化),将磁盘的各个磁道划分为扇区。一个扇区通常可分为头、数据区域(如512B大小)、尾三个部分组成。管理扇区所需要的各种数据结构一般存放在头、尾两个部分,包括扇区校验码(如奇校验、CRC循环冗余校验码等,校验码用于校验扇区中的数据是否发生错误。)
- 将磁盘分区,每个分区由若干个柱面组成。(即分为我们熟悉的C盘、D盘、E盘)
- 进行逻辑格式化,创建文件系统。包括创建文件系统的根目录、初始化存储空间管理所用的数据结构(如位示图、空闲分区表)
引导块
计算机开机时需要进行一系列初始化的工作,这些初始化工作是通过执行初始化程序(自举程序)完成的。
初始化程序可以放在ROM(只读存储器)中。ROM中的数据在出厂时就写入了,并且以后不能再修改。
ROM一般是出厂时就集成在主板上。
万一需要更新自举程序,直接写在ROM中会很不方便。目前的解决方法是:
ROM中存放很小的“自举装入程序”,完整的自举程序存放在磁盘的启动块(即引导块/启动分区)上,启动块位于磁盘的固定位置。
开机时计算机先运行“自举装入程序”,通过执行该程序就可以找到引导块,并将完整的“自举程序”读入内存,完成初始化。
坏块的管理
坏了、无法正常使用的扇区就是“坏块”。这属于硬件故障,OS无法修复。应该将坏块标记出来,以免错误地使用到它。
对于简单的磁盘,可以在逻辑格式化时(建立文件系统时)对整个磁盘进行坏块检查,标明哪些扇区是坏扇区,比如:在FAT表上标明。(在这种方式中,坏块对OS不透明)
对于复杂的磁盘,磁盘控制器(磁盘设备内部的一个硬件部件)会维护一个坏块链表。
在磁盘出厂前进行低级格式化时就将坏块链进行初始化。
磁盘控制器会保留一些“备用扇区”,用于替换坏块。这种方案称为扇区备用。且这种处理方式中,坏块对OS透明。