设备控制器不属于操作系统范畴,它是属于硬件,而设备驱动程序属于操作系统的一部分,操作系统的内核代码可以像本地调用代码一样使用设备驱动程序的接口,而设备驱动程序是面向设备控制器的代码,它发出操控设备控制器的指令后,才可以操作设备控制器。 硬盘有硬盘控制器、显示器有视频控制器。设备控制器里有芯片,它可执行自己的逻辑,也有自己的寄存器,用来与 CPU 进行通信,比如:
- 通过写入这些寄存器,操作系统可以命令设备发送数据、接收数据、开启或关闭,或者执行某些其他操作。
- 通过读取这些寄存器,操作系统可以了解设备的状态,是否准备好接收一个新的命令等。
实际上,控制器是有三类寄存器,它们分别是
- 数据寄存器,CPU 向 I/O 设备写入需要传输的数据,比如要打印的内容是「Hello」,CPU 就要先发送一个 H 字符给到对应的 I/O 设备。
- 命令寄存器,CPU 发送一个命令,告诉 I/O 设备,要进行输入/输出操作,于是就会交给 I/O 设备去工作,任务完成后,会把状态寄存器里面的状态标记为完成。
- 状态寄存器,目的是告诉 CPU ,现在已经在工作或工作已经完成,如果已经在工作状态,CPU 再发送数据或者命令过来,都是没有用的【硬气】,直到前面的工作已经完成,状态寄存标记成已完成,CPU 才能发送下一个字符和命令。
输入输出设备可分为两大类 :块设备(Block Device)和字符设备(Character Device)。
块设备,把数据存储在固定大小的块中,每个块有自己的地址,硬盘、USB 是常见的块设备。 字符设备,以字符为单位发送或接收一个字符流,字符设备是不可寻址的,也没有任何寻道操作,鼠标是常见的字符设备。
块设备通常传输的数据量会非常大,于是控制器设立了一个可读写的数据缓冲区。
CPU 写入数据到控制器的缓冲区时,当缓冲区的数据囤够了一部分,才会发给设备。 CPU 从控制器的缓冲区读取数据时,也需要缓冲区囤够了一部分,才拷贝到内存。 这样做是为了,减少对设备的频繁操作。
CPU读写数据的途径
那 CPU 是如何与设备的控制寄存器和数据缓冲区进行通信的?存在两个方法: 端口 I/O,每个控制寄存器被分配一个 I/O 端口,可以通过特殊的汇编指令操作这些寄存器,比如 in/out 类似的指令。 内存映射 I/O,将所有控制寄存器映射到内存空间中,这样就可以像读写内存一样读写数据缓冲区。
I/O 控制方式
轮询等待
外设:你在这等着,我去拿数据给你 CPU:你脑袋不清醒?你搞那么慢,让我在这等着?
中断
中断有两种,一种软中断,例如代码调用 INT 指令触发,一种是硬件中断,就是硬件通过中断控制器触发的。
CPU:你去拿数据,拿到了告诉我,我来取。
中断的方式对于频繁读写数据的磁盘,并不友好,这样 CPU 容易经常被打断,会占用 CPU 大量的时间。
DMA
CPU:你,拿了数据就放在内存的那个角落。我要xxMB数据,办完了再叫我,我直接拿着用。
中断处理
设备完成了事情,会发送中断来通知操作系统。那操作系统就需要有一个地方来处理这个中断,这个地方也就是在设备驱动程序里,它会及时响应控制器发来的中断请求,并根据这个中断的类型调用响应的中断处理程序进行处理。
通用块层
通用块层是处于文件系统和磁盘驱动中间的一个块设备抽象层,为了减少不同块设备的差异带来的影响,它主要有两个功能:
- 向上为文件系统和应用程序,提供访问块设备的标准接口,向下把各种不同的磁盘设备抽象为统一的块设备,并在内核层面,提供一个框架来管理这些设备的驱动程序;
- 给文件系统和应用程序发来的 I/O 请求排队,接着会对队列重新排序、请求合并等方式,也就是 I/O 调度,主要目的是为了提高磁盘读写的效率。【诶,磁盘调度算法不是用在这里吗。】
I/O 调度算法
Linux 内存支持 5 种 I/O 调度算法,分别是: 没有调度算法 先入先出调度算法 完全公平调度算法 优先级调度 最终期限调度算法
第一种,没有调度算法,是的,你没听错,它不对文件系统和应用程序的 I/O 做任何处理,这种算法常用在虚拟机 I/O 中,此时磁盘 I/O 调度算法交由物理机系统负责。
第二种,先入先出调度算法,这是最简单的 I/O 调度算法,先进入 I/O 调度队列的 I/O 请求先发生。
第三种,完全公平调度算法,大部分系统都把这个算法作为默认的 I/O 调度器,它为每个进程维护了一个 I/O 调度队列,并按照时间片来均匀分布每个进程的 I/O 请求。
第四种,优先级调度算法,顾名思义,优先级高的 I/O 请求先发生, 它适用于运行大量进程的系统,像是桌面环境、多媒体应用等。
第五种,最终期限调度算法,分别为读、写请求创建了不同的 I/O 队列,这样可以提高机械磁盘的吞吐量,并确保达到最终期限的请求被优先处理,适用于在 I/O 压力比较大的场景,比如数据库等。
存储系统 I/O 软件分层
有了文件系统接口之后,不但可以通过文件系统的命令行操作设备,也可以通过应用程序,调用 read、write 函数,就像读写文件一样操作设备,所以说设备在 Linux 下,也只是一个特殊的文件。
但是,除了读写操作,还需要有检查特定于设备的功能和属性。于是,需要 ioctl 接口,它表示输入输出控制接口,是用于配置和修改特定设备属性的通用接口。
缓冲区
另外,存储系统的 I/O 是整个系统最慢的一个环节,所以 Linux 提供了不少缓存机制来提高 I/O 的效率。
为了提高文件访问的效率,会使用页缓存、索引节点缓存、目录项缓存等多种缓存机制,目的是为了减少对块设备的直接调用。 为了提高块设备的访问效率, 会使用缓冲区,来缓存块设备的数据
键盘敲入字母时,期间发生了什么?
键盘突然有输入了! 赶紧把数据编个号 存到寄存器!叫CPU来处理啊!【没有用DMA】 CPU放下手里的事,去键盘驱动程序那边取了对应的中断处理函数【身为老板,居然要干这样的杂活】 CPU拿着那个函数,扫寄存器里的编码,然后那个函数吐出转换好的ASCII码 ASCII码要先放到「读缓冲区队列」,如果要显示的话,就得再挪去「写缓冲区队列」,直到看到字母显示出来,CPU才能回去接着干活。
|