2015年(65)
分类: LINUX
2015-01-06 12:13:09
如果您记性好的话,应该记得我在linux设备驱动实例帖中说的最多的就是字符设备驱动程序,那么今天的块I/O层是一个和字符设备驱动相对应的设备。两者最根本的区别就是看它们能否被随机访问,换句话说就是看它们能否在访问设备时从一个位置随意地调到另外一个位置,如果可以就是块设备,否则就字符设备。
块设备中最小的可寻址单元是扇区。扇区的大小一般是2的整数倍,最常见的大小是512个字节。扇区的大小是设备的物理属性,扇区是所有块设备的基本单元,块设备无法对比它还小的单元进行寻址和操作,不过许多块设备能够一次就传输多个扇区。从软件角度来讲,最小的逻辑可寻址单元却是块,块是文件系统的一种抽象-----只能基于块来访问文件系统。虽然物理磁盘寻址是按照扇区级进行的,但是执行的所有磁盘操作都是按照块进行的。前边已经说过,扇区是设备的最小可寻址单元,所以块不能比扇区还小,只能数倍于扇区大小。另外内核还要求块大小是2的整数倍,=而且不能超过一个页的长度,所以大小的最终要求是,必须是扇区大小的2的整数倍,并且要小于页面大小。所以通常块大小是512字节,1k或4k。
当一个块被调入内存时,它要存储在一个缓冲区中,每个缓冲区与一个块对应,它相当于是磁盘块在内存中的表示。另外,由于内核在处理数据时需要一些相关的控制信息,所以每个缓冲区都有一个叫做buffer_head的描述符来表示,被称为缓冲区头,在linux/buffer_head.h中定义,它包含了内核操作缓冲区所需要的全部信息,如下:
01 struct buffer_head {
02 unsigned long b_state; /* buffer state flags */
03 atomic_t b_count; /* buffer usage counter */
04 struct buffer_head *b_this_page; /* buffers using this page */
05 struct page *b_page; /* page storing this buffer */
06 sector_t b_blocknr; /* logical block number */
07 u32 b_size; /* block size (in bytes) */
08 char *b_data; /* buffer in the page */
09 struct block_device *b_bdev; /* device where block resides */
10 bh_end_io_t *b_end_io; /* I/O completion method */
11 void *b_private; /* data for completion method */
12 struct list_head b_assoc_buffers; /* list of associated mappings */
13 };
其中的b_state域表示缓冲区的状态,下表给出一种标志或多种标志的组合,在linux/buffer_head.h中定义了所有合法标志的bh_state_bite列表,如下所示:
bh_state_bits列表包含了一个特殊标志----BH_PrivateStart,该标志不是可用状态标志,使用它是为了指明可能其它代码使用的起始位。块I/O层不会使用BH_PrivateStart或更高的位,那么某个驱动程序希望通过b_state域存储信息时就可以地使用这些位。驱动程序可以在这些位中定义自己的状态标志,只要保证自定义的状态标志不会与块IO层的专用位发生冲突就可以了。b_count域表示缓冲区的使用计数,可通过两个定义在文件linux/buffer_head.h中的内联函数对此域进行增减:
1 static iine void get_bh(struct buffer_ *bh)
2 {
3 atomic_inc(&bh->b_count);
4 }
5 static inline void put_bh(struct buffer_head *bh)
6 {
7 atomic_dec(&bh->b_count);
8 }
在操作缓冲区头之前,应该先使用get_bh()函数增加缓冲区头的引用计数,确保缓冲区头不会再被分配出去,当完成对缓冲区头的操作之后,还必须使用put_bh()函数减少引用计数。与缓冲区对应的磁盘物理块由b_blocknr域索引,该值是b_bdev域指明的块设备中的逻辑块号。与缓冲区对应的物理页由b_page域表示,另外,b_data域直接指向相应的块(它位于b_page域所指明的页面的某个位置上),块的大小由b_size域表示,所以块在内存中的起始位置在b_data处,结束位置在(b_data+b_size)处。缓冲区头的目的在于描述磁盘块和物理内存缓冲区(在特定页面上的字节序列)之间的映射关系。这个结构体在内核中扮演一个描述符的角色,说明从缓冲区到块的映射关系。使用缓冲区头作为I/O操作有它的弊端,这里不细说,你明白就好。我们只需知道现在的内核采用了一种新型,灵活而且轻量级的容器---bio结构体。
bio结构体定义在linux/bio.h中,该结构体代表了正在现场的(活动)以片断(segment)链表形式组织的块I/O操作。一个片断是一小块连续的内存缓冲区。这样的话,就不需要保证单个缓冲区一定要连续,所有通过片断来描述缓冲区,即使一个缓冲区分散在内存的多个位置上,bio结构体也能保证I/O操作的执行。下面给出bio结构体和各个域的描述,如下:
01 struct bio {
02 sector_t bi_sector; /* associated sector on disk */
03 struct bio *bi_next; /* list of requests */
04 struct block_device *bi_bdev; /* associated block device */
05 unsigned long bi_flags; /* status and command flags */
06 unsigned long bi_rw; /* read or write? */
07 unsigned short bi_vcnt; /* number of bio_vecs off */
08 unsigned short bi_idx; /* current index in bi_io_vec */
09 unsigned short bi_phys_segments; /* number of segments after coales