操作系统是如何管理各种设备的
作为程序员的我们,几乎每天都在与电脑的各种设备进行接触,可是计算机的操作系统是如何管理这些错综复杂的设备的。以及当使用键盘敲入按键时,操作系统都如何进行执行的呢?下面就针对这些问题进行分析。
设备控制器
我们的电脑同时与许多设备进行交互,尽管这些设备的用法和功能都大不相同,但是我们的操作系统都能很好的完成操作,这其中就依靠设备管理器。
-
设备管理器:为了屏蔽设备之间的差异,每个设备都有一个设备控制器的组件,进行交互。CPU只需要与设备管理器进行交互,就能实现对各种各样设备的管理。
-
作用:
-
- 通过写入这些寄存器,操作系统可以命令设备发送数据、接收数据、开启或关闭,或者执行某些其他操作。
- 通过读取这些寄存器,操作系统可以了解设备的状态,是否准备好接收一个新的命令等。
寄存器
设备控制器是有三类寄存器,他们分别是:
-
状态寄存器:
-
- 告诉CPU,现在的工作状态,只有上一个工作完成,状态寄存标记成已完成,CPU才能发送下一个字符和命令。
-
命令寄存器:
-
- CPU 发送一个命令,告诉 I/O 设备,要进行输入/输出操作,于是就会交给 I/O 设备去工作,任务完成后,会把状态寄存器里面的状态标记为完成。
-
数据寄存器:
-
- CPU 向 I/O 设备写入需要传输的数据,比如要打印的内容是「Hello」,CPU 就要先发送一个 H 字符给到对应的 I/O 设备。
CPU 通过读、写设备控制器中的寄存器来控制设备,这可比 CPU 直接控制输入输出设备,要方便和标准很多。
设备控制器
输入和输出设备可分为两大类:块设备和字符设备
- 块设备:把数据存储在固定大小的块中,每个块都有自己的地址,硬盘。其中USB是最常见的块设备
- 字符设备:以字符为单位发送或接收一个字符流,字符设备是不可寻址的,也没有任何寻道操作。其中鼠标是常见的字符设备。
由于块设备通常传输的数据量巨大,所以需要一个可读写的数据缓冲区
- CPU 写入数据到控制器的缓冲区时,当缓冲区的数据囤够了一部分,才会发给设备。
- PU 从控制器的缓冲区读取数据时,也需要缓冲区囤够了一部分,才拷贝到内存。
这样可以大大减少对设备的操作次数。
CPU是如何与设备的控制寄存器和数据缓冲区进行通信的呢?
- 端口I/O:每个设备寄存器被分配一个I/O端口,可以通过特殊的汇编执行操作这些寄存器。比如in/out类似的执行
- 内存映射I/O:将所有控制寄存器映射到内存空间中,这样就可以像读写内存一样读写数据缓冲区
I/O控制方式
通过前面的知识我们知道,每种设备都有一个设备控制器,可以自己处理一些事情,通过设备控制器,CPU可以去控制设备。但当设备完成指令后又如何通知CPU呢?
通知方式
针对此我们有三种方式来进行通知:
轮询等待
这个是比较简单的方法,由于控制器的寄存区中有状态标记位,用来标识输入或输出操作是否完成。针对此,CPU可以一直去监控寄存器的状态,直到状态标记为完成。
很明显这种方法虽然简单,但是有致命缺点就是会占用CPU的全部时间。
中断
针对轮询等待的弊端,我们想出了利用中断来通知操作系统。我们一般会有一个中断控制器,当设备完成任务后就去触发中断到中断控制器,然后就会通知CPU,然后让CPU停下手头的事情去处理这个中断。
中断的分类
- 软中断:例如代码调用INT指令触发
- 硬件中断:硬件通过中断控制器触发的。
缺点:
这种方式也存在一定程度上的缺陷,就是中断的方式对于频繁读写数据的磁盘很不友好,由于CPU经常被打断,会占用CPU大量的时间。
DMA
针对中断的问题,还可以使用DMA功能,可以使得在CPU不参与的情况下,能够自行完成吧设备I/O数据放到内存中。
要实现DMA功能,就需要相应的DMA控制器硬件的支持。
DMA的工作方式:
- CPU 需对 DMA 控制器下发指令,告诉它想读取多少数据,读完的数据放在内存的某个地方就可以了;
- 接下来,DMA 控制器会向磁盘控制器发出指令,通知它从磁盘读数据到其内部的缓冲区中,接着磁盘控制器将缓冲区的数据传输到内存;
- 当磁盘控制器把数据传输到内存的操作完成后,磁盘控制器在总线上发出一个确认成功的信号到 DMA 控制器;
- DMA 控制器收到信号后,DMA 控制器发中断通知 CPU 指令完成,CPU 就可以直接用内存里面现成的数据了
CPU 当要读取磁盘数据的时候,只需给 DMA 控制器发送指令,然后返回去做其他事情,当磁盘数据拷贝到内存后,DMA 控制机器通过中断的方式,告诉 CPU 数据已经准备好了,可以从内存读数据了。仅仅在传送开始和结束时需要 CPU 干预。
设备驱动程序
虽然设备控制器屏蔽了设备的众多细节,但是还是存在诸如寄存器、缓冲区等使用模式的不同。为了兼容这种不同,引入了设备驱动程序。
相比于设备控制器属于硬件,设备驱动程序属于操作系统的一部分,这样操作系统内核代码可以像本地调用代码一样使用设备驱动程序的接口,而设备驱动程序时面向设备控制器的代码,它发出操控设备器的指令后,才可以操作设备控制器
设备驱动程序的最大作用就是将不同的设备控制器进行整合提供一个统一的接口给操作系统。
另外设备驱动程序里面也会及时响应控制器发来的中断请求,此外在设备驱动程序初始化的时候,要先注册一个该设备的中断处理函数。
中断处理程序的处理流程
- 在I/O时,设备控制器如果已经准备好数据,则会通过中断控制器向CPU发送中断请求
- 保护被中断进程的CPU上下文
- 转入相应的设备中断处理函数
- 进行中断处理
- 恢复被中断进程的上下文
通用块层
Linux通过一个统一的通用块层,来统一管理不同的块设备。这些块设备又能很好的减少不同块设备的差异带来的影响。
通用块层是处于文件系统和磁盘驱动中的一个块设备抽象层,主要的功能有下:
- 向上为文件系统和应用程序,提供访问块是很的标准接口,向下把各种不同的磁盘设备抽象为统一的块设备,并在内核层面,提供一个框架来统一管理
- 通用层还会给文件系统和应用程序发来的 I/O 请求排队,接着会对队列重新排序、请求合并等方式,也就是 I/O 调度,主要目的是为了提高磁盘读写的效率。
Linux内存支持的五种I/O调度算法
- 无调度算法:此时不对文件系统和应用程序的I/O做任何处理,此时磁盘I/O调度算法交由物理机系统负责
- 先入先出调度算法
- 完全公平调度算法:大部分系统的默认算法,它为每个进程维护了一个 I/O 调度队列,并按照时间片来均匀分布每个进程的 I/O 请求。
- 优先级调度算法:适用于运行大量进程的系统
- 最终期限调度算法:分别为读、写请求创建了不同的 I/O 队列,这样可以提高机械磁盘的吞吐量,并确保达到最终期限的请求被优先处理,适用于在 I/O 压力比较大的场景,比如数据库等。
存储系统I/O软件分层
Linux存储系统的I/O由上往下可以分为三层,分别是:文件系统层、通用块层、设备层。层级关系如下所示
三个层级的作用
- 文件系统层:包括虚拟文件系统和其他文件系统的具体实现,向上为应用程序统一提供了标准的文件访问接口,向下会通过通用块层来存储和管理磁盘数据
- 通用块层:包括块设备的I/O队列和I/O调度器,会对文件系统的I/O请求进行排列,再通过I/O对的呀器,选择一个I/O发送给下一层的设备层
- 设备层:包括硬件设备、设备控制器和驱动程序,负责最终物理设备的I/O操作。
缓存机制提高I/O效率
- 使用页缓存、索引节点缓存、目录项缓存,等多种缓存机制,减少了对块设备的直接调用,大大提高了文件访问的效率
- 使用缓冲区来缓存块设备的数据,从而提高块设备的访问效率。
当键盘敲入字母时,发生了什么
通过前面的知识,我们已经大体知道了操作系统是如何管理设备的,那么下面我们就根据一个具体的例子来屡一下各个节点的顺序和发送了什么。
总览图
我们从左上到右下进行分析:CPU里面的内存接口,直接和系统总线通信,然后系统总线再接入一个I/O桥接器,这个桥接器一边通过内存总线连接到内存使得CPU和内存通信,一边通过I/O总线来连接I/O设备。
当用户输入了键盘字符,键盘控制器就会产生扫描码数据,并将其缓冲在键盘控制器的寄存器中,紧接着键盘控制器通过总线给 CPU 发送中断请求。CPU 收到中断请求后,操作系统会保存被中断进程的 CPU 上下文,然后调用键盘的中断处理程序。
键盘的中断处理程序是在键盘驱动程序初始化时注册的,那键盘中断处理函数的功能就是从键盘控制器的寄存器的缓冲区读取扫描码,再根据扫描码找到用户在键盘输入的字符,如果输入的字符是显示字符,那就会把扫描码翻译成对应显示字符的 ASCII 码。然后会把 ASCII 码放到「读缓冲区队列」。
接下来就是要把显示字符显示屏幕了,显示设备的驱动程序会定时从「读缓冲区队列」读取数据放到「写缓冲区队列」,最后把「写缓冲区队列」的数据一个一个写入到显示设备的控制器的寄存器中的数据缓冲区,最后将这些数据显示在屏幕里。显示出结果后,恢复被中断进程的上下文。
最后
- 如果觉得看完有收获,希望能给我点个赞,这将会是我更新的最大动力,感谢各位的支持
- 欢迎各位关注我的公众号【java冢狐】,专注于java和计算机基础知识,保证让你看完有所收获,不信你打我
- 如果看完有不同的意见或者建议,欢迎多多评论一起交流。感谢各位的支持以及厚爱。