配套教材:
Operating Systems: Three Easy Pieces Remzi H. Arpaci-Dusseau Andrea C. Arpaci-Dusseau Peter Reiher
参考书目:
1、计算机操作系统(第4版) 汤小丹 梁红兵 哲凤屏 汤子瀛 编著 西安电子科技大学出版社
在线阅读:
http://pages.cs.wisc.edu/~remzi/OSTEP/
University of Wisconsin Madison 教授 Remzi Arpaci-Dusseau 认为课本应该是免费的。
————————————————————————————————————————
这是专业必修课《操作系统原理》的复习指引。
需要掌握的概念在文档中以蓝色标识,并用可读性更好的字体显示 Linux 命令和代码。代码部分语法高亮。
文档下载地址:
链接:https://pan.baidu.com/s/1ou8FyLueRqmTF8K0EfN_2g
提取码:0000
十一 I / O设备
一颗较新的Intel CPU通过各种各样的总线与其它设备连接,如图。
较新的CPU中已经集成了内存控制器(memory controller),负责和内存之间的通信。外围部件互连扩展(Peripheral Component Interconnect Express,PCI-E)总线一般用于连接显卡(video card / graphics card),用于处理图形;也可以连接固态硬盘(solid state drive,SSD)、网卡(network interface controller / network interface card,NIC,也称网络适配器,network adapter)甚至是声卡(sound card)等其它设备。CPU与专门的I / O处理芯片(Intel称为平台控制器集线器,Platform Controller Hub,PCH)通过直接媒体接口(Direct Media Interface,DMI)连接。通用串行总线(Universal Serial Bus,USB)通常用于连接计算机以外的设备,集成在计算机内的一些设备也可能通过USB连接。硬盘有SATA接口的;支持NVMe协议的SSD大都连接M.2(较少为U.2)接口,它们实际上都通过PCI-E进行传输;也有直接插在主板上的PCI-E插槽上的SSD。eSATA(外置SATA)和mSATA接口现在不多见。但是PCH提供的PCI-E的速率受DMI带宽的严重制约,其实际速率并不如直连CPU的PCI-E。PCH提供的配合各种技术规范(PCI-E、SATA、USB)的通道并不能全部同时工作,同一条通道在同一时刻只能支持一种设备。支持何种设备则通过PCH芯片内的MUX选择。
如图,Intel的某型号PCH共有30条HSIO(High-speed IO)通道(lane)。虽然它设计了24条PCI-E通道,但是它们并不能同时启用;有的型号的PCH芯片只允许同时启用16条甚至更少的PCI-E通道,导致带宽(bandwidth,BW,这里指数据的传输速率)骤降;一些通道选择作为USB或GbE(Gbps Ethernet,用于连接以太网网卡)或SATA通道后,它们就不能再作为PCI-E通道使用。而PCH提供的这些通道被用于哪些接口,则是主板(mainboard / motherboard)厂商的设计决定的。
总线具有层次结构(hierarchy),工作速率最高的部件在总线上的位置离CPU最近。层次总线是性能和成本上的一种折中,因为设计高性能总线是非常困难、非常昂贵的。
操作系统与I / O设备之间,一种比较原始的交互方式大致具有如下几个步骤:
(1)操作系统控制CPU反复读写I / O设备中的状态寄存器(称为轮询,polling),直到该寄存器的信息反映设备已就绪。
(2)操作系统将数据发送到设备的数据寄存器中。
(3)操作系统将需要执行的命令写入设备的命令寄存器。这一步告诉设备:数据已准备好,需要开始执行指定的命令。
(4)操作系统等待设备完成操作。这也是通过轮询完成的,最终读取到成功的信息或错误码。
在这个模式中,由于CPU直接参与数据传输,它被称为可编程I / O(programmed I / O,PIO),CPU执行I / O端口指令来进行数据的读写。PIO模式实现简单,但效率很低且不便。反复的轮询会导致很高的CPU占用率,不但浪费大量CPU时间,而且这段时间还不能在该核心上切换其它进程继续。当前,这种传输方式基本已经被淘汰。
中断可以降低I / O过程中的CPU占用率。当有线程请求I / O时,操作系统发出请求,并令该线程阻塞(进入睡眠),然后执行其它线程。当I / O完成后,会产生一个硬件中断,导致CPU执行相应的中断处理程序,然后让刚才请求I / O的进程继续。中断处理程序包含在操作系统中,它会完成相应的任务(例如,处理数据或来自设备的错误码)。
但中断并不是所有时候都是最好的方案。如果一个I / O设备执行任务非常快,可能在最初的几次轮询内就可以完成,那么如果使用中断,反而会降低系统的性能:线程切换、中断处理和从中断处理程序返回,都需要耗费不少的时间。如果不知道设备到底快还是慢,则可以把两种方法结合起来用:先轮询一段较短的时间,如果设备仍未完成操作,再使用中断。这种思想与之前讲过的两段锁类似。
此外,过多的中断可能还会引发活锁。例如:在网络应用中,有时会有大量的数据包发送给计算机。如果每来一个包都引发一次中断,导致频繁的切换动作,就增加了产生活锁的概率。这一点还可能会被黑客利用,造成网络服务器无法正常工作。更好的方法是:允许较短时间的轮询,并让服务器处理完一连串的包以后再去检查是否有新的数据包到来,而不是每来一个新的数据包都通过中断来让服务器立即处理它。
如果设备发起两次中断的间隔太短且中断频率又高,可以考虑将这些中断合并为单个中断,一次性处理较多的数据(操作)。当然,如果把太多的中断都合并到一起,可能会明显降低响应速率。这里也要做好权衡。
即使有了中断,CPU仍然需要亲自将要复制的数据传输给设备,然后设备才开始传输,CPU转而执行其它任务。这仍旧会带来一定的CPU占用率。解决的办法是引入直接存储访问(direct memory access,DMA)。
有了DMA机制以后,程序想传输数据时,操作系统就会令DMA控制器开始将指定位置的数据复制给设备,然后启动传输。整个传输过程交由DMA控制器控制。传输完毕后,DMA控制器产生一个中断,操作系统就知道传输完成了。DMA负责传输期间,CPU具有更多的时间可以执行其它任务。
一种进行设备通信的方式是I / O指令。这些指令让OS向设备的寄存器发送数据,并启动传输过程。x86的in和out指令用于读写端口(port)。通过端口可以读写设备的寄存器,以此正式对设备发出I / O请求。
当然,这些I / O指令都是特权指令,只有操作系统有权执行。如果所有程序都可以不经过操作系统而直接执行I / O指令,那么计算机很容易被恶意程序破坏。
另一种方式是内存映射I / O(memory mapped I / O,MMIO)。各设备的寄存器被映射到一定范围的内存地址中,通过读写这部分内存来进行I / O,就好像读写一般的内存空间那样。
使用内存映射I / O的方式进行设备通信,无需支持专门的I / O指令。不过两种方法到今天都一直有在使用。
每个设备都提供了不尽相同的接口,都具有各不相同的工作方式。怎样让不同的设备都能够与操作系统配合好呢?比如内置的机械硬盘(Hard disk drive,HDD)和SSD、U盘(USB flash drive)的工作原理不完全相同。有的SSD遵循AHCI(Advanced Host Controller Interface)协议,有的则遵循NVMe(Non-Volatile Memory Host Express)协议。我们希望它们在计算机上都能正常工作。这就需要在操作系统上安装设备驱动(device driver),简称驱动(driver)。 驱动也是一种抽象模型,能让设备的硬件细节对操作系统透明(不可见),令操作系统将同一类设备(例如:存储设备)基本一视同仁,减轻操作系统和上层应用程序设计者的负担:程序员们无需把各种各样的设备的硬件细节都掌握,就可以编写代码令它们正常工作。
下面是LINUX的文件系统栈的简化模型:
我们可以看到,应用程序通过调用POSIX API来访问文件系统,文件系统再通过块接口继续传送,最后相应的指令传到驱动层。一些特殊的应用程序,比如文件系统检查器(SFC)、磁盘碎片整理程序等,可以绕过文件系统进行读写。
硬盘是块设备的一种,因为数据的存取和传输都以块为单位。它们通过I / O接口的一种——块接口,与应用程序和操作系统进行通信。块设备的传输速率通常比其它设备高很多。
Linux内核中,超过70%的代码是设备驱动程序。当然,它们中的大部分代码都不经常被使用。受驱动编写者水平的影响,驱动导致内核崩溃的概率要更大。