分类: LINUX
2010-08-19 19:11:41
系统中能够随机访问固定大小数据片(chunk)的设备被称作块设备,这些数据片就称作块,最常见的块设备是硬盘。块设备都是以安装文件系统的方式使用的——这也是块设备通常的访问方式。
另一种基本的设备类型是字符设备。字符设备按照字符流的方式被有序访问,像串口和键盘就属于字符设备。
这两种类型的设备的根本区别在于它们是否可以被随机访问——换句话说,就是能否在访问设备时随意的从一个位置跳转到另一个位置。比如说,硬盘设备的驱动可 能需要读取磁盘上任意块的内容,然后又转去读取别的块的内容,而被读取的块在磁盘上位置不一定要连续,所以说硬盘可以被随机访问,而不是以流的方式访问, 显然它是一个块设备。
13.1 解剖一个块设备
块设备中最小的可寻址单元是扇区。扇区大小一般是2的整数倍,而最常见的大小是512个字节。扇区的大小是设备的物理属性,扇区是所有块设备的基本单元——块设备无法对比它还小的单元进行寻址和操作,不过许多块设备能够一次就传输多个扇区。
虽然各种软件的用途不同,但是它们都会用到自己的最小逻辑单元——块。块是文件系统的一种抽象——只能基于块来访问文件系统。虽然物理磁盘寻址是按照扇区级进行的,但是内核执行的所有磁盘操作都是按照块进行的。
扇区对内核的重要性在于所有设备的I/O操作都必须基于扇区来进行;反过来,块是内核使用的较高层概念,它是比扇区高一层的抽象。
13.2 缓冲区和缓冲区头
当一个块被调入内存(也就是在读入或等待写出)时,它要存储在一个缓冲区中。每个缓冲区与一个块对应,它相当于是磁盘块在内存中的表示。块包含一个或多个
扇区,但大小不能超过一个页面,所以一个页可以容纳一个或多个内存中的块。由于内核在处理数据时需要一些相关的控制信息,所以每个缓冲区都有一个对应的描
述符。该描述符用buffer_head结构体来表示,被称作缓冲区头,在文件
下面给出缓冲区头结构体和其中各个域的说明:
struct buffer_head {
ufsigned long b_state; /* 缓冲区状态标志 */
atomic_t b_count; /* 缓冲区使用计数 */
struct buffer_head *b_this_page /* 叶茂中的缓冲区 */
struct page *b_page; /* 存储缓冲区的页面 */
sector_t b_blocknr; /* 逻辑块号 */
u32 b_size; /* 块大小 */
char *b_data; /* 页面中的缓存区 */
struct block_device *b_bdev; /* 块设备 */
bh_end_io_t *b_end_io; /* I/O完成方法 */
void *b_private; /* 完成方法数据 */
struct list_head b_assoc_buffers; /* 相关的映射链表 */
};
b_state域表示缓冲区的状态,合法的标志存放在bh_state_bits枚举中,该枚举在
BH_Uptodata 该缓冲区包含可用数据
BH_Dirty 该缓冲区是脏的(缓存中的内容比磁盘中的块内容新,所以缓冲区内容必须被写回磁盘)
BH_Lock 该缓冲区正在被I/O操作使用,被锁定以防被并发访问
BH_Red 该缓冲区有I/O请求操作
BH_Mapped 该缓冲区是映射磁盘块的可用缓冲区
BH_New 缓冲区是通过get_block()刚刚映射的,尚且不能访问
BH_Async_Read 缓冲区正在通过end_buffer_asnc_read()被异步I/O读操作使用
BH_Async_Write 该缓冲区正在通过end_buffer_async_write()被异步I/O写操作使用
BH_Delay 该缓冲区尚未和磁盘块关联
BH_Boundary 该缓冲区处于连续块区的边界——下一个块不再连续
bh_state_bits列表还包含了一个特殊标志——BH_PrivateStart,该标志不是可用状态标志,使用它是为了指明可被其他代码使用的 起始位。块I/O层不会使用BH_PrivateStart或更高的位。驱动程序可以在这些位中定义自己的状态标志,只要保证自定义的状态标志不与块I /O层的专用位发生冲突就行。
显然一个缓冲区可以同时具有上述状态的几种。
块高速缓存的管理很复杂,下面先对空缓冲区、空闲缓冲区、正使用的缓冲区、缓冲区的大小以及缓冲区的类型作一个简短的介绍:
缓冲区可以分为两种,一种是包含了有效数据的,另一种是没有被使用的,即空缓冲区。
具有有效数据并不能表明某个缓冲区正在被使用,毕竟,在同一时间内,被进程访问的缓冲区(即处于使用状态)只有少数几个。当前没有被进程访问的有效缓冲区和空缓冲区称为空闲缓冲区。其实,buffer_head结构中的b_count就可以反映出缓冲区是否处于使用状态。如果它为0,则缓冲区是空闲的。大于0,则缓冲区正被进程访问。
缓冲区的大小不是固定的,当前Linux支持5种大小的缓冲区,分别是512、1024、2048、4096、8192字节。Linux所支持的文件系统都使用共同的块高速缓存,在同一时刻,块高速缓存中存在着来自不同物理设备的数据块,为了支持这些不同大小的数据块,Linux使用了几种不同大小的缓冲区。
b_count域表示缓冲区的使用计数,当它为0时,说明缓冲区是空闲的。
与缓冲区对应的磁盘物理块由b_blocknr域索引,该值是b_bdev域指明的块设备中的逻辑块号。
与 缓冲区对应的内存物理页由b_page域表示,另外,b_data域指向相应的块(它位于b_page域所指明的页面中的某个位置上),块的大小由 b_size域表示,所以块在内存中的起始位置在b_data处,结束位置在(b_data+b_size)处。
缓 冲区头的目的在于描述磁盘块和物理内存缓冲区之间的映射关系,这个结构体在内核中只扮演一个描述符的角色,说明从缓冲区到块的映射关系。但是仅仅把磁盘块 映射到物理内存是不够的,还要对映射过来的数据进行操作啊(要不然映射过来干什么呢),下面的bio结构体就实现了块I/O操作。 换句话说,缓冲区头负责映射,而bio负责操作,前者要面对的是磁盘块和内存缓冲区,后者只需面对缓冲区就可以了。 13.3 bio结构体
目
前内核中块I/O操作的基本容器由bio结构体表示,它定义在文件
struct bio {
sector_t bi_sector; /* I/O的起始扇区 */
struct bio *bi_next; /* request queue link */
struct block_device *bi_bdev; /* 指向实际执行的I/O设备 */
unsigned long bi_flags; /* status, command, etc */
unsigned long bi_rw; /* bottom bits READ/WRITE, top bits priority */
unsigned short bi_vcnt; /* how many bio_vec's */
unsigned short bi_idx; /* current index into bvl_vec */
unsigned short bi_phys_segments; /* Number of segments in this BIO after
* physical address coalescing is performed. */
unsigned short bi_hw_segments; /* Number of segments after physical and DMA remapping
* hardware coalescing is performed. */
unsigned int bi_size; /* residual I/O count */
/*
* To keep track of the max hw size, we account for the
* sizes of the first and last virtually mergeable segments
* in this bio
*/
unsigned int bi_hw_front_size;
unsigned int bi_hw_back_size;
unsigned int bi_max_vecs; /* max bvl_vecs we can hold */
struct bio_vec *bi_io_vec; /* the actual vec list */
bio_end_io_t *bi_end_io;
atomic_t bi_cnt; /* pin count */
void *bi_private;
bio_destructor_t *bi_destructor; /* destructor */
};
使用bio结构体的目的主要是代表正在现场执行的I/O操作,所以该结构体中的主要域都是用来管理相关信息的,其中最重要的几个域是
bi_io_vecx、bi_vcnt和bi_idx。bi_io_vecs指向一个bio_vec结构体数组,该结构体链表包含了一个特定I/O操作所
需要使用到的所有片段。每个bio_vec结构都是一个形式为
在每个给定的块I/O操作中,bi_vcnt域用来描述bi_io_vec所指向的bio_vec数组中的向量数目。当块I/O操作执行完成后,bi_idx域指向数组的当前索引。
每个块I/O请求都通过一个bio结构体表示。每个请求包含一个或多个块,这些块存储在bio_vec结构体数组中。这些结构体描述了每个片段在物理页中的实际位置,并且像向量一样被组织在一起。 13.4 请求队列
块
设备将它们挂起的块I/O请求保存在请求队列中,该队列由reques_queue结构体表示,定义在文件
请求
队
列中的请求由结构体request表示,它定义在文件
讨论了块设备的基本知识,考察了块I/O层所用到的数据结构:bio,表示活动的I/O操作;buffer_head,表示块到页的映射;还有请求结构,表示具体的I/O请求。 关于I/O请求和bio的关系,没有深入的了解。