1 块设备介绍
块设备是I/O设备的一种,以数据流的形式进行数据传输,是将信息存储在固定大小的块中,每个块都有自己的地址,还可以在设备的任意位置读取一定长度的数据(因为使用了内存);而字符设备是以字节为单位进行数据传输,并且不需要涉及内存相关的内容,所以不可以读写任意位置上的数据,只能顺序读写当前数据。
块设备的主要特点是是,CPU和总线读写数据所花的时间与磁盘硬件的速度不匹配,块设备的平均访问时间很高,主要是因为磁盘控制器必须在磁盘表面将磁头移动到记录数据的确切位置。
2 块设备结构
Linux中访问块设备在不同的阶段会涉及到不同的数据结构,例如虚拟文件系统是以块为单位大小访问文件的,而在通用块层又会将相邻的块合并为段,在块设备驱动中磁盘控制器又是以扇区为大小访问具体的块设备的。接下来着重讨论一下三种数据结构:
扇区(sectors)
为了达到可接受的性能,硬盘和类似的设备每次都快速传输几个相邻字节的数据,我们称其为扇区。扇区是磁盘驱动器访问磁盘是的最小数据单元,如果一次访问的数据量小于一个扇区则补齐剩余空间,如果超过一个扇区大小,则需要将此次访问分割成多个扇区大小进行。因此扇区大小的选择也很重要,如果选择过小则会因为访问频繁而降低性能;扇区过大则会导致空间的浪费。Linux中默认扇区大小为512字节。因此,对于存放在磁盘上的数据是通过他们在磁盘上的位置来标识的,即数据所在的首个扇区标号和占用的扇区数目。
块(blocks)
扇区是硬件设备传输数据的基本单元,而块是虚拟文件系统传输数据的基本单元。块的大小必须是2的幂次,是扇区大小的倍数,且不能超过页的大小。块设备的大小不是唯一的,不同的磁盘文件系统拥有不同的块大小,用户访问磁盘数据时虚拟文件系统会根据具体的磁盘文件系统确定块大小,并调用磁盘文件系统相关函数进行数据的访问。
段(segments)
段是块的整数倍且不超过页的大小。当用户空间提交多个磁盘数据访问请求时,如果访问的数据在磁盘上是相邻的,那么Linux内核会将这些块合并为段,这样就可以提高磁盘的IO性能。
扇区、块、段三者的关系如下图所示:
3 块设备访问数据的优
1) 磁盘
磁盘上的数据可以通过(柱面号、盘面号、块号)来唯一标示。因此读写磁盘上某一数据步骤如下:
1、移动磁头至相关柱面;
2、确定盘面号后,盘片开始旋转至指定块号即可。
由于磁头的移动输入机械结构,导致速度比较慢,即从一个磁头的某个柱面上的某个扇区读到数据后,跳到另一个磁头的某柱面上的某个扇区去写,会导致效率低下。
块设备来进行数据写操作时(需要把扇区内容擦除,然后进行写),是按扇区大小来写数据的。文件系统会先找到该文件所在的扇区号,然后将这个扇区里的数据拷贝到buf数组中,然后修改数据,在将这个数据放到对应的某个扇区中,如果我们多次写入小于扇区大小的数据,就会重复不断地对扇区读出,写入,这样会浪费很多时间在读/写硬盘上。所以内核提供一种机制,在没有关闭文件之前,会将读写请求进行优化,排序,合并,从而提高访问硬盘的效率。因此有时候你在操作文件时,如果异常断电,数据会出现丢失,是因为此时的数据还在buf缓存区中,并没有写入内存。
4 块设备框架
ll_rw_block()函数()
void ll_rw_block(int rw, int nr, struct buffer_head *bhs[])
//rw:读写标志位, nr:bhs[]长度, bhs[]:要读写的数据数组
{
int i;
for (i = 0; i < nr; i++) {
struct buffer_head *bh = bhs[i]; //获取nr个buffer_head
... ...
if (rw == WRITE || rw == SWRITE) {
if (test_clear_buffer_dirty(bh)) {
... ...
submit_bh(WRITE, bh); //提交WRITE写标志的buffer_head
continue;
}}
else {
if (!buffer_uptodate(bh)) {
... ...
submit_bh(rw, bh); //提交其它标志的buffer_head
continue;
}}
unlock_buffer(bh); }
}
其中buffer_head结构体,就是我们的缓冲区描述符,存放缓存区的各种信息,结构体如下所示:
struct buffer_head {
unsigned long b_state; //缓冲区状态标志
struct buffer_head *b_this_page; //页面中的缓冲区
struct page *b_page; //存储缓冲区位于哪个页面
sector_t b_blocknr; //逻辑块号
size_t 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; //相关映射链表
/* mapping this buffer is associated with */