引言

本文章是基于阅读完《操作系统导论》之后的总结,很推荐大家完整阅读这本书

I/O设备

I/O设备就是计算机接收数据和发送数据的设备

在I/O设备中,我们可以分为块设备和字符设备

块设备

我们常见的块设备包括硬盘(如HDDSSD)、蓝光光盘以及USB闪存盘
块设备的特点就是处理连续的数据块单位,每个数据块都可以单独的处理,可以进行读写的操作
块设备的缺点对于对块中的任意一部分数据的访问操作是比较慢,因为每一次的访问都要从块数据的开头开始寻址,所以要花费一些时间去遍历很多没有必要的数据

但是优点也很明显,就是在处理大量的数据的时候可以表现出更高的性能,因为不需要逐字符的处理

字符设备

字符设备的显著特点是不可寻址的,也就是说,他们没有像块设备那样的物理地址或块结构,因此不需要进行寻道的操作,使得在处理连续数据流的时候更加的高效

在实际应用中,我们可以找到许多字符设备的例子。例如,打印机通过接收字符流来打印文档;
络设备如网卡和调制解调器,它们通过字符流来传输和接收网络数据;鼠标则通过发送字符序列来报告 其在屏幕上的移动和点击操作;

设备控制器

设备控制器是计算机体系中的一个核心组件,它扮演着I/O设备与计算机主体之间数据交换的桥梁
角色。具体而言,设备控制器负责控制一个或多个I/O设备,确保它们能够按照CPU的指令进行工作。 作为CPUI/O设备之间的接口,设备控制器接收来自CPU的命令,并据此对I/O设备进行操控,从而减 轻了CPU在设备控制方面的负担。
当我们通过键盘输入文字时,字符设备控制器会接收CPU发来的指令,并据此控制键盘进行字符的
输入和传输。同样地,在硬盘读写数据时,块设备控制器则负责接收CPU的指令,并精确控制硬盘的读 写操作,确保数据的准确传输。

对于一个标准的设备,我们一般存在3个寄存器,一个是状态寄存器,可以读取查看当前设备的状态,一个是命令寄存器,用于通知设备执行某一个具体的命令,一个是数据寄存器,将数据传输给设备或者从设备接受数据。

那操作系统怎么与这些设备进行交互呢?

在一开始,我们选择的是最简单的轮询机制。

我们可以简单的模拟一下这个轮询的过程,CPU会不断的轮询这些正在处理数据的状态寄存器,看一看这些设备是个什么情况,如果数据处理完了,那么CPU就要进入执行命令。但是这样子的操作很明显非常的耗时间,这是肯定的,因为CPU在轮询的过程中啥都不可以干。所以对于这种要释放CPU的操作,我们一般会想到一个操作:“中断“

CPU不再轮询设备而是向每一个设备发出一个请求,并让对应的设备进入睡眠,自己去处理其他的任务,然后当设备完成了自身的操作之后,会发出一个硬件的中断,然后CPU会立刻停下手中的事情,屁颠屁颠地跑来处理中断,然后在中断处理地代码中结束之前的请求,并且唤醒等待的I/O进程继续执行。

所以这里的优势就是在进程1传输数据的时候,CPU可以运行进程2。当进程1传输数据之后,进程1会发出中断,引发CPU的注意,从而处理进程1。

但是这里也很有一个大问题,我们这里一直说的让设备控制器来传输设备,是因为设备控制器就像一个小型的计算机,里面有属于它自己的缓存,但是真正要把数据搬移到内存里面,还是要CPU的工作,因为设备控制器不知道内存地址。那这就很麻烦了,因为CPU如果要拷贝内容,那么CPU就不可以做其他的事情,为了释放CPU,所以我们引进了DMA。

DMA

DMA是一个操作系统地特殊设备,它可以协调完成内存和设备之间地传输,不需要CPU进行介入。相当于是一个小的CPU,而真正的CPU的操作只需要让DMA管理设备控制器。操作系统告诉DMA数据在内存的位置,要拷贝的大小和拷贝到哪个设备,在此之后,操作系统就可以处理其他的请求。当DMA完成任务之后,DMA会抛出一个硬件中断来告诉操作系统已经完成了数据传输。

设备交互的方式

或许你已经发现了,我们一直在讨论怎么使用设备,但是操作系统怎么与这些设备进行通信的?

比较老的一种方式就是用明确的I/O指令,这些指令明确的规定了操作系统把数据发送到特定的设备寄存器的方法。

第二种方法就是内存映射I/O,通过这种方式,硬件将设备寄存器作为内存地址提供,当访问到特定的内存地址,也就相当于访问到了特定的寄存器。当然,我们也想到了,这一块内存我们是访问不了的。

I/O的层次

这是一张非常重要的图片,这里将会慢慢的阐述其中的关系。

我们先简述一下之间的关系:

首先对于I/O回复,我们我们的硬件处理完了I/O操作之后,会发出中断,然后中断处理程序会接受到中断的信号,并向上提供信号,唤醒设备驱动程序,设备驱动程序要检查设备,寄存器的状态,然后向上提供接口,设备独立性操作系统软件来命名,阻塞,缓冲,分配,最后向上提供接口,给到用户。

那么对于I/O请求,我们的用户通知设备独立性操作系统软件,然后告诉设备驱动程序,然后交给硬件处理,这里就不需要中断处理程序了。

在接下来,我们会仔细分析每一个部分:

中断处理程序

我们这里就把整个中断的过程再复述一遍

当中断发生的时候,我们需要快速的保护现场,把寄存器里的内容保存到内存里面,用一个数据结构存储,也就是我们所知的PCB,然后我们通过查看页表的方式来配置中断处理程序的环境,并且同时为中断处理程序分配一个单独的栈,以免和之前的程序发生冲突,然后执行中断服务程序,该程序会从发出中断的设备控制器的寄存器中提取必要的信息 以处理中断。 当键盘控制器发出中断时,中断服务程序会从键盘控制器的寄存器中读取按键信息,并根据这些信息执行相应的操作(如将按键字符添加到输入缓冲区)。这个完成之后就开始回复原来的进程,将之前保存的寄存器内容从保存位置拷贝到进程表中对应的进程上下文中。这样, 当中断处理程序完成后,系统可以恢复该进程的上下文并继续执行。

当然,如果有新的进程优先级更高,就会运行新的进程,然后就要切换进程,更新TLB等寄存器的内容。最后开始运行新的进程。

设备驱动程序

设备驱动程序在系统中的位置至关重要,它通常是操作系统内核的组成部分,这使得它能够直接访
问设备的硬件资源。然而,随着技术的发展,也出现了在用户空间实现设备驱动程序的设计,这种设计 通过系统调用来完成数据的读写操作。这种做法的一个显著优势在于,即使设备驱动程序出现问题,也 不会直接影响到内核的稳定性,从而降低了系统崩溃的风险。因此,在用户空间实现设备驱动程序成为 提升系统稳定性的一个重要手段。
然而,大多数桌面操作系统要求驱动程序必须运行在内核中。

与设备无关的 I/O 软件

因为不同的设备之间有着不同的区别,但是我们作为用户,我们不需要知道这些设备之间的内容,所以在与设备无关的 I/O 软件中,处理的就是把这些差异统一,最后统一向上提供接口。

总结

所以硬件法没有发生中断只有中断程序知道,而中断程序并不会取干涉这个中断的请求,而是向上面汇报,告诉在内核里面的设备驱动程序,然后设备驱动程序会来解决设备寄存器的问题。所以如果发出I/O请求,根本不会涉及到中断处理程序,而是由设备驱动程序来控制控制器,由控制器去控制硬件。

磁盘

磁盘可能有一个或者多个盘片,每个盘片都有两个盘面。数据在扇区的同心圆中的每个表面上被编码,我们称这样的同心圆为一个磁道。一个表面有数以千计的磁道,数百个磁道只有头发的宽度。而我们访问数据,是靠磁头,而磁头的移动是靠磁臂。

但是注意的是:我们磁头是不可以左右移动的,只能前后移动。

那么我们怎么访问数据呢,肯定是磁盘在快速的转动。即使我们没有访问数据,那这个时候磁盘仍然在转动。

磁盘中寻道一直是一个很花费时间的事情,就像在生活之中,一旦你错过了那个的人,那么可能要再轮回才会相见,磁盘也是如此,如果你运气很不好,前脚刚走,你后脚就到了,那你只能苦苦等待一圈。为了避免这种情况的发生,我们不会吧数据整齐的排列在一起,而是把数据偏移的排放,根据磁盘的转速来偏移数据的位置。保证磁头刚到的时候,数据正好也到了。

对于布局来说,外面的扇区密度小,里面的扇区密度大,主要是因为每一个扇区存储内容的大小是一样的。而且外圈磁道比内圈磁道有着更多的扇区。

然后,我们需要思考另一个问题,我们怎么处理数据。这就不得不说到一个词了:“缓冲区”和“缓存区”。可以想象一下,如果没有缓冲区,我们如果隔几秒读一次数据,隔几秒写一次数据,那我们磁盘就会不停的寻道,甚至如果有时候,我们建立一个临时文件,写完就删除,根本不需要写在磁盘上。如果没有缓冲区,那这样的效率就很低,而缓冲区的作用就是把我们写的数据先放在缓冲区里面,等到积累到一定的数量,或者一定的时间,我们才会把这一大块的数据一下子全部写入磁盘;而缓存区的作用就是我们如果读一字节的数据,他会把一大块的数据全部存在缓存区里面,如果下次我们还需要读,那么直接从缓存区里面取就可以了。

总的来说都是为了减少不必要的I/O。

对于每一个扇区,我们可以简化一下它的结构:

在磁盘刚被制造出来时,它上面并没有存储任何 信息。为了使磁盘能够正常使用,它必须经过一个称为 低级格式化(low-level format) 的过程
而高级格式化也就是我们在另外一张博客要讲的文件系统。
扇区的开始位置由前导码(Preamble)标示。前导码通常以特定的位模式开始,用于帮助磁盘驱 动器定位扇区的起始点。此外,前导码中还包含了诸如 柱面号 、 扇区号 等关键信息,这些信息对于磁 盘驱动器来说至关重要,因为它们能够帮助驱动器准确地定位到磁盘上的每一个扇区。
紧随前导码之后的是数据区(Data Area),这是实际存储用户数据的地方。数据区的大小通常由 低级格式化程序在格式化过程中确定。在大多数磁盘中,每个扇区的数据部分大小为512字节,但这一 数值并非绝对,它可能会因磁盘制造商和型号的不同而有所变化。
数据区之后是ECCError Correction Code,数据纠错码)区域。ECC与普通的错误检测机制不 同,它不仅能够检测到数据中的错误,还能够通过一定的算法来纠正这些错误,从而恢复出正确的数据。ECC区域的大小通常由磁盘制造商根据自身的技术实力和市场需求来决定。设计者需要在提高磁盘 可靠性和节省磁盘空间之间做出权衡,同时还需要考虑ECC算法的复杂性和实现难度。

磁盘调度

说了这么多我们还是要面对一些棘手的问题,就是磁盘调度。

假设我们要读取扇区2,4,5,7,9,45,我们作为磁盘的设计者,我们必须要思考我们怎么样设计算法,才可以减少磁盘的寻道时间。

SSTF(最短寻道时间优先)

顾名思义,就是谁离我近,我就读谁的,假如我磁头一开始的磁道上就有数据,那么我磁头就不移动了,先把这个磁道的数据读完才会到下一个最近的磁道。

但是(唉,凡事都怕但是),如果一个磁道不断地来数据,本来我扇区5一开始就开始排队了,可是等了一年,还没有轮到我,因为一直有一些可恶的人卡机制来插队,这就会导致饿死。

所以这不是一个好的算法。

SCAN(电梯算法)

为了避免饥饿,我们首先就是要解决的是保证每一个数据都可以被访问到,但是同时要保证效率较高,所以这个算法,我们把磁臂模拟成电梯,直上直下,上到顶再下到底。每当一个请求处理完之后,我们会查看目前是up还是down,如果是up就继续向外寻道,如果在这个方向上已经没有请求了,那么就会变成down。

异常处理

在这片文章的最后,我们需要提到一个小的细节,就是如果磁盘发生了异常,比如这个磁盘不能写数据了,那怎么办。

利用备用扇区替换坏的扇区。

本文章到这里就结束了,感谢大家的阅读!!!!

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐