块设备的定义
块设备是针对存储设备的,比如 SD 卡、EMMC、NAND Flash、Nor Flash、SPI Flash、机械硬盘、固态硬盘等。因此块设备驱动其实就是这些存储设备驱动,块设备驱动相比字符设备相比,块设备有以下几个特殊之处:
- 块设备可以从数据的任何位置进行访问
- 块数据总是以固定长度进行传输,即便请求的这是一个字节
- 对块设备的访问有大量的缓存。当进行读时,如果已经缓存了,就直接使用缓存中的数据,而不再读设备,对于写也通过缓存来进行延迟处理。
块设备读写是按块(所以叫块设备)进行的,使用缓冲区来存放暂时的数据,待条件成熟后,从缓存一次性写入设备或者从设备一次性读到缓冲区。作为存储设备,块设备驱动的核心问题就是哪些page->segment->block->sector与哪些sector有数据交互,下图是Linux内核中块设备的驱动模型。
块设备驱动框架
虚拟文件系统
虚拟文件系统(VFS)隐藏了各种硬件的具体细节,为用户操作不同的硬件提供了一个统一的接口。其基于不同的文件系统格式,比如EXT,FAT等。用户程序对设备的操作都通过VFS来完成,在VFS上面就是诸如open、close、write和read的函数API。
映射层
映射层(mapping layer):这一层主要用于确定文件系统的block size,然后计算所请求的数据包含多少个block。同时调用具体文件系统函数来访问文件的inode,确定所请求的数据在磁盘上面的逻辑地址。
通用块层
通用块层是 Linux 内核中的中间层,位于块设备驱动层和文件系统层之间。通用块层负责维持一个I/O请求在上层文件系统与底层物理磁盘之间的关系。在通用块层中,通常用一个bio结构体来对应一个I/O请求。通用块层隐藏了底层硬件的细节,使上层的软件可以独立于具体的硬件实现。它还负责管理块设备的缓存、缓冲区和数据传输,以提高性能。
IO调度层
IO调度层是通用块层的一部分,它负责决定块设备上的IO请求的执行顺序。当多个IO请求同时到达块设备时,IO调度层使用调度算法来决定哪个IO请求应该首先执行。调度算法的目标是优化系统的性能和响应速度。常见的IO调度算法包括:
- CFQ(完全公平队列):根据进程的IO请求历史和权重来调度IO请求。它试图公平地分配磁盘带宽给不同的进程。
- Deadline(截止时间):使用截止时间来调度IO请求。它将IO请求分为读取和写入,并尽量在截止时间之前完成IO请求。
- NOOP(无操作):简单地将IO请求按照先到先服务的方式进行调度,不做任何优化。
块设备驱动层
在Linux中,驱动对块设备的输入或输出(I/O)操作,都会向块设备发出一个请求,在驱动中用request结构体描述。但对于一些磁盘设备而言请求的速度很慢,这时候内核就提供一种队列的机制把这些I/O请求添加到队列中(即:请求队列),在驱动中用request_queue结构体描述。在向块设备提交这些请求前内核会先执行请求的合并和排序预操作,以提高访问的效率,然后再由内核中的I/O调度程序子系统来负责提交 I/O 请求, 调度程序将磁盘资源分配给系统中所有挂起的块 I/O 请求,其工作是管理块设备的请求队列,决定队列中的请求的排列顺序以及什么时候派发请求到设备。
页、段、块、扇区关系
- **扇区(Sectors):**任何块设备硬件对数据处理的基本单位。通常,1个扇区的大小为512byte。(对设备而言)
- **块 (Blocks):**由Linux制定对内核或文件系统等数据处理的基本单位。通常,1个块由1个或多个扇区组成。(对Linux操作系统而言)
- 段(Segments): 是一个内存页或内存页的一部分,它包含磁盘上物理相邻的几个数据块的内容,是块驱动的传送单位,大小不定(取决于通用块层,因为它把传送的数据下发给块驱动,而通用块还可以把几个段合并成物理段、硬件段(专门总线电路);同时也取决于用户所访问的大小。注意这里所指的段与内存中的段也有些联系,因为内存的段可能是一个对象/变量的大小;而用户程序也可能以变量/对象作为单位来访问设备。
- 页(page):在这里仅仅是把4096B的连续数据称为一个页。也可以指内存的数据组织 单位(有些体系还在使用段,但是多数已经将段屏蔽了,即内部使用,而外部"看不见"),大小通常为4096B。但内存本身的访问/传送单位并不是页,这是取决于内存的编址方法,现在内存通常是按字节编址的,而甚至部分可以按位编址,所以内存的访问单位可以更小。
- 页、段、块、扇区之间的关系图如下:
本文参考
https://zhuanlan.zhihu.com/p/507214979
https://zhuanlan.zhihu.com/p/504763396
https://zhuanlan.zhihu.com/p/87566255