块设备的处理
块设备驱动程序上的每个操作都涉及很多内核组件;其中最重要的一些如图14-1所示。
例如,我们假设一个进程在某个磁盘文件上发出一个read()系统调用
——我们将会看到处理write请求本质上采用同样的方式。
下面是内核对进程请求给予回应的一般步骤:
1. read()系统调用的服务例程调用一个适当的VFS函数,将文件描述符和文件内的偏移量传递给它。
虚拟文件系统位于块设备处理体系结构的上层,它提供一个通用的文件模型,Linux支持的所有文件系统均采用该模型。
我们在第十二章已经详细介绍了VFS层。
2. VFS函数确定所请求的数据是否已经存在,如果有必要的话,它决定如何执行read操作。
有时候没有必要访问磁盘上的数据,因为内核将大多数最近从块设备读出或写入其中的数据保存在RAM中。第十五章介绍了磁盘高速缓存机制,
而第十六章详细说明了VFS如何处理磁盘操作以及如何与磁盘高速缓存和文件系统交互。
3. 我们假设内核从块设备读数据,那么它就必须确定数据的物理位置。为了做到这点,内核依赖映射层(mapping layer),主要执行下面两步:
a.内核确定该文件所在文件系统的块大小,并根据文件块的大小计算所请求数据的长度。
本质上,文件被看作拆分成许多块,因此内核确定请求数据所在的块号(文件开始位置的相对索引)。
b.接下来,映射层调用一个具体文件系统的函数,它访问文件的磁盘节点,然后根据逻辑块号确定所请求数据在磁盘上的位置。
事实上,磁盘也被看作拆分成许多块,因此内核必须确定存放所请求数据的块对应的号(磁盘或分区开始位置的相对索引)。
由于一个文件可能存储在磁盘上的不连续块中,因此存放在磁盘索引节点中的数据结构将每个文件块号映射为一个逻辑块号(注1)。
我们将在第十六章中说明映射层的功能,在第十八章中将介绍一些典型的磁盘文件系统。
4. 现在内核可以对块设备发出读请求。内核利用通用块层(generic block Inyer)启动I/O操作来传送所请求的数据。
一般而言,每个I/O操作只针对磁盘上一组连续的块。
由于请求的数据不必位于相邻的块中,所以通用块层可能启动几次I/O操作。
每次I/O操作是由一个“块I/O”(简称“bio”)结构描述,它收集底层组件需要的所有信息以满足所发出的请求。
通用块层为所有的块设备提供了一个抽象视图,因而隐藏了硬件块设备间的差异性。
几乎所有的块设备都是磁盘,所以通用块层也提供了一些通用数据结构来描述“磁盘”或“磁盘分区”。
我们将在本章的“通用块层”一节中讨论通用块层和bio数据结构。
5. 通用块层下面的“I/O调度程序”根据预先定义的内核策略将待处理的I/O数据传送请求进行归类。
调度程序的作用是把物理介质上相邻的数据请求聚集在一起。我们将在本章后面的“I/O调度程序”一节中介绍调度程序。
6. 最后,块设备驱动程序向磁盘控制器的硬件接口发送适当的命令,从而进行实际的数据传送。
我们将在后面的“块设备驱动程序”一节介绍通用块设备驱动程序的总体组织结构。
如你所见,块设备中的数据存储涉及了许多内核组件;每个组件采用不同长度的块来管理磁盘数据:
6.1.硬件块设备控制器采用称为“扇区”的固定长度的块来传送数据。因此,I/O调度程序和块设备驱动程序必须管理数据扇区。
6.2.虚拟文件系统、映射层和文件系统将磁盘数据存放在称为“块”的逻辑单元中。
6.3.一个块对应文件系统中一个最小的磁盘存储单元。
我们很快会看到,块设备驱动程序应该能够处理数据的“段”:
一个段就是一个内存页或内存页的一部分,它们包含磁盘上物理相邻的数据块。
5.4.磁盘高速缓存作用于磁盘数据的“页”上,每页正好装在一个页框中。
通用块层将所有的上层和下层的组件组合在一起,因此它了解数据的扇区、块、段以及页。
即使有许多不同的数据块,它们通常也是共享相同的物理RAM单元。
例如,图14-2显示了一个具有4096字节的页的构造。
上层内核组件将页看成是由4个1024字节组成的块缓冲区。
块设备驱动程序正在传送页中的后3个块,因此这3块被插入到涵盖了后3072 字节的段中。
硬盘控制器将该段看成是由6个512字节的扇区组成。
本章我们介绍处理块设备的下层内核组件:通用块层、I/O调度程序以及块设备驱动程序,因此我们将注意力集中在扇区、块和段上。
扇区
为了达到可接受的性能,硬盘和类似的设备快速传送几个相邻字节的数据。
块设备的每次数据传送操作都作用于一组称为扇区的相邻字节。
在下面的讨论中,我们假定字节按相邻的方式记录在磁盘表面,这样一次搜索操作就可以访问到它们。
尽管磁盘的物理构造很复杂,但是硬盘控制器接收到的命令将磁盘看成一大组扇区。
在大部分磁盘设备中,扇区的大小是512字节,但是一些设备使用更大的扇区(1024和2048字节)。
注意,应该把扇区作为数据传送的基本单元;不允许传送少于一个扇区的数据,尽管大部分磁盘设备都可以同时传送几个相邻的扇区。
在Linux中,扇区大小按惯例都设为512字节;
如果一个块设备使用更大的扇区,那么相应的底层块设备驱动程序将做些必要的变换。
因此,对存放在块设备中的一组数据是通过它们在磁盘上的位置来标识,
即其首个512字节扇区的下标以及扇区的数目。
扇区的下标存放在类型为sector_c的32位或64位的变量中。