10.5.4 输入/输出在Linux中的实现

在Linux中I/O是通过一系列的设备驱动来实现的,每个设备类型对应一个设备驱动。设备驱动的功能是对系统的其他部分隔离硬件的细节。通过在驱动程序和操作系统其他部分之间提供一层标准的接口,使得大部分I/O系统可以被划归到内核的机器无关部分。

当用户访问一个特殊文件时,由文件系统提供此特殊文件的主设备号和次设备号,并判断它是一个块特殊文件还是一个字符特殊文件。主设备号用于索引存有字符设备或者块设备数据结构的两个内部散列表之一。定位到的数据结构包含指向打开设备、读设备、写设备等功能的函数指针。次设备号被当作参数传递。在Linux系统中添加一个新的设备类型,意味着要向这些表添加一个新的表项,并提供相应的函数来处理此设备上的各种操作。

图10-21展示了一部分可以跟不同的字符设备关联的操作。每一行指向一个单独的I/O设备(即一个单独的驱动程序)。列表示所有的字符驱动程序必须支持的功能。还有几个其他的功能。当一个操作要在一个字符特殊文件上执行时,系统通过检索字符设备的散列表来选择合适的数据结构,然后调用相应的功能来执行此操作。因此,每个文件操作都包含指向相应驱动程序的一个函数指针。

广告:个人专属 VPN,独立 IP,无限流量,多机房切换,还可以屏蔽广告和恶意软件,每月最低仅 5 美元

阅读 ‧ 电子书库
图 10-21 典型字符设备支持的部分文件操作

每个驱动程序都分为两部分。这两部分都是Linux内核的一部分,并且都运行在内核态。上半部分运行在调用者的上下文并且与Linux其他部分交互。下半部分运行在内核上下文并且与设备进行交互。驱动程序可以调用内存分配、定时器管理、DMA控制等内核过程。所有可以被调用的内核功能都定义在一个叫做驱动程序-内核接口(Driver-Kernel Interface)的文档中。编写Linux设备驱动的细节请参见文献(Egan和Teixeira,1992;Rubini等人,2005)。

I/O系统被划分为两大部分:处理块特殊文件的部分和处理字符特殊文件的部分。下面将依次讨论这两部分。

系统中处理块特殊文件(比如,磁盘)I/O的部分的目标是使必须要完成的传输次数最小。为了实现这个目标,Linux系统在磁盘驱动程序和文件系统之间放置了一个高速缓存(cache),如图10-22。在2.2版本内核之前,Linux系统完整地维护着两个单独的缓存:页面缓存(page cache)和缓冲器缓存(buffer cache),因此,存储在一个磁盘块中的文件可能会被缓存在两个缓存中。2.2版本以后的Linux内核版本只有一个统一的缓存。一个通用数据块层(generic block layer)把这些组件整合在了一起,执行磁盘扇区、数据块、缓冲区和数据页面之间必要的转换,并且激活作用于这些结构上的操作。

阅读 ‧ 电子书库
图 10-22 Linux I/O系统中一个文件系统的细节

cache是内核里面用来保存数以千计的最近使用的数据块的表。不管本着什么样的目的(i节点,目录或数据)而需要一个磁盘块,系统首先检查这个块是否在cache里面。如果在cache中,就可以从cache里直接得到这个块,从而避免了一次磁盘访问,这可以在很大程度上提高系统性能。

如果页面cache中没有这个块,系统就会从磁盘中把这个块读入到cache中,然后再从cache中复制到请求它的地方。由于页面cache的大小是固定的,因此,前面章节介绍的页面置换算法在这里也是需要的。

页面cache也支持写数据块,就像读数据一样。一个程序要回写一个块时,它被写到cache里,而不是直接写到磁盘上。当cache增长到超过一个指定值时,pdflush守护进程会把这个块写回到磁盘上。另外,为了防止数据块被写回到磁盘之前在cache里存留太长时间,每隔30秒系统会把所有的“脏块”都写回到磁盘上。

Linux依靠一个I/O调度器来保证磁头反复移动的延迟最小。I/O调度器的作用是对块设备的读写请求重新排序或对这些读写请求进行合并。有很多调度器变种,它们是根据不同类型的工作负载进行优化的结果。基本的Linux I/O调度器基于最初的Linus电梯调度器(Linus Elevator scheduler)。电梯调度器的操作可以这样总结:按磁盘请求的扇区地址的顺序将磁盘操作在一个双向链表中排序。新的请求以排序的方式插入到双向链表中。这种方法可以有效地防止磁头重复移动。请求列表经过合并后,相邻的操作会被整合为一条单独的磁盘请求。基本电梯调度器有一个问题是会导致饥饿的情况发生。因此,Linux磁盘调度器的修改版本包括两个附加的列表,维护按时限(deadline)排序的读写操作。读请求的缺省时限是0.5s,写请求的缺省时限是5s。如果最早的写操作的系统定义的时限要过期了,那么相对于任何在主双向链表中的请求来说,这个写请求会被优先服务。

除了正常的磁盘文件,还有其他的块特殊文件,也被称为原始块文件(raw block file)。这些文件允许程序通过绝对块号来访问磁盘,而不考虑文件系统。它们通常被用于分页和系统维护。

与字符设备的交互是很简单的。因为字符设备产生和接收的是字符流或字节数据,所以让字符设备支持随机访问是几乎没有意义的。不过行规则(line disciplines)的使用是个例外。一个行规则可以和一个终端设备联合在一起,通过tty_struct结构来表示,一般作为和终端交换的数据的解释器。例如,利用行规则可以完成本地行编辑(即擦除的字符和行可以被删除),回车可以映射为换行,以及其他的特殊处理能够被完成。然而,如果一个进程要跟每个字符交互,那么它可以把行设置为原始模式,此时行规则将被忽略。另外,并不是所有的设备都有行规则。

输出采用与输入类似的工作方式,如把tab扩展为空格,把换行转变为回车+换行,在慢的机械式终端的回车后面加填充字符等。像输入一样,输出可以通过(加工模式)行规则,或者忽略(原始模式)行规则。原始模式对于GUI和通过一个串行数据线发送二进制数据到其他的计算机的情况尤其有用,因为这些情况都不需要进行转换。