Chinaunix首页 | 论坛 | 博客
  • 博客访问: 67767
  • 博文数量: 25
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 10
  • 用 户 组: 普通用户
  • 注册时间: 2013-04-25 16:11
个人简介

生命是个奇迹,我用时间来证明!

文章分类
文章存档

2013年(25)

我的朋友

分类: LINUX

2013-05-15 11:14:51

原文地址:块设备的处理及通用块层 作者:tq08g2z

块设备的主要特点是,CPU和总线读写数据所化时间与磁盘所花时间与磁盘硬件的速度不匹配。块设备的平均访问时间很高。每个操作都需要几个毫秒才能完成,主要是因为磁盘控制器必须在磁盘表面将磁头移动到记录数据的确切位置。但是,当磁头到达正确的位置时,数据传送就可以稳定在每秒几十MB的速率。

 

块设备的处理

Linux块设备处理程序的组织是相当复杂的,块设备驱动程序上的每个操作都涉及很多内核组件。其中的一些如下图所示:

 

例如,我们假设一个进程在某个磁盘文件上发出一个read()系统调用——write()系统调用本质上采用同样的方式。下面是内核对进程请求给予回应的一般步骤:

1read()系统调用的服务例程调用一个适当的函数,将文件描述符和文件内的偏移量传递给它。虚拟文件系统位于块设备处理体系结构的上层,他提供一个通用的文件模型,Linux支持的所有文件系统均采用该模型。

 

2VFS函数确定所请求的数据是否存在,比如文件指针的位置的合法性等,如果有必要的话,他决定如何执行read操作(但均通过具体的文件系统提供的文件操作来执行)。有时候没有必要访问磁盘上的数据,因为内核将大多数最近从块设备读出或写入其中的数据保存在RAM总,及通过磁盘高速缓存来获得数据。

 

3、我们假设内核从块设备读数据,那么它就必须确定数据的物理位置。为了做到这点,内核依赖映射层(mapping layer),主要执行下面两步:

 

a.内核确定该文件所在文件系统的块大小,并根据文件块的大小计算所请求数据的长度。本质上,文件被看作许多数据块的集合,因此内核确定请求数据所在的块号(文件开始位置的块索引)。

 

b.接下来,映射层调用一个具体文件系统的函数,他访问文件的磁盘索引节点,然后根据逻辑块号确定所请求数据在磁盘上的位置。事实上,磁盘也被看作数据块的数组,因此内核必须确定存放所请求数据的块对应的号(磁盘或分区开始位置的相对索引)。由于一个文件可能存储在磁盘上的不连续块中,因此存放在磁盘索引节点中的数据结构将每个文件块号映射为一个逻辑块号。

 

4、现在内核可以对块设备发出读请求。内核利用通用块层(generic block layer)启动I/O操作来传送所请求的数据。一般而言,每个I/O操作只针对磁盘上一组连续的块。由于请求的数据不必位于相邻的块中,所以通用块层可能启动几次I/O操作。每次I/O操作是由一个“块I/O”(简称“bio”)结构描述,它收集底层组件需要的所有信息以满足所发出的请求。

 

通用块层为所有的块设备提供了一个抽象视图,因而隐藏了硬件块设备间的差异性。几乎所有的块设备都是磁盘。

 

5、通用块层下面的“I/O调度程序”根据预先定义的内核策略将待处理的I/O数据传送请求归类。调度程序的作用是把物理介质上相邻的数据请求聚集在一起。

 

6、最后块设备驱动程序向磁盘控制器的硬件接口发送适当的命令,从而进行实际的数据传送。

 

块设备中的数据存储涉及到了许多内核组件,每个组件采用不同长度的块来管理磁盘数据:

 

硬件块设备控制器采用称为“扇区”的固定长度的快来传送数据。因此,I/O调度程序和块设备驱动程序必须管理数据扇区。为了达到可接受的性能,硬盘和类似的设备快速传送几个相邻字节的数据。块设备的每次数据传送操作都作用于一组称为扇区的相邻字节。尽管磁盘的物理构造很复杂,但是硬盘控制器接收到的命令将磁盘看成一大组扇区。应该把扇区作为数据传送的基本单元;不允许传送少于一个扇区的数据,尽管大部分磁盘设备都可以同时传送几个相邻的扇区。在Linux中,扇区大小按惯例都设为512字节;如果一个块设备使用更大的扇区,那么相应的底层设备驱动程序将做些必要的变换。对存放在块设备中的一组数据是通过他们在磁盘上的位置来标识,即其首个512字节扇区的下标以及扇区的数目。扇区的下标存放在类型为sector_t32位或64位的变量中。

 

虚拟文件系统、映射层和具体文件系统将磁盘数据存放在成为“块”的逻辑单元中。一个块对应文件系统中一个最小的磁盘存储单元。在Linux中,块的大小是2的次方倍,而且不能超过一个页框。此外,它必须是扇区大小的整数倍,因为每个块必须是包含整数个扇区。块设备的块大小不是唯一的,创建一个磁盘文件系统时,管理员可以选择合适的块大小。因此,同一个磁盘上的几个分区可能使用不同的块大小。此外,对块设备文件的每次读或写操作是一种“原始”访问,因为他绕过了磁盘文件系统;内核通过使用最大的块(4096字节)执行该操作。

 

每个块都需要自己的块缓冲区,它是内核用来存放块内容的RAM空间。当内核从磁盘读出一个块时,就用从硬件设备中所获得的数据来填充相应的缓冲区;同样,当内核向磁盘中写入一个块时,就用相关块缓冲区的数据来更新硬件设备上相应额一组相邻字节。块缓冲区的大小通常要与相应块的大小相匹配。

 

缓冲区首部是一个与每个缓冲区相关的buffer_head类型的描述符。它包含内核处理缓冲区需要了解的所有信息;因此,在对每个缓冲区进行操作之前,内核都要首先检查其缓冲区首部;因此,在对每个缓冲区进行操作之前,内核都要首先检查其缓冲区首部。

 

块设备驱动程序应该能够处理数据的“段”:一个段就是一个内存页或内存页的一部分,它们包含磁盘上物理相邻的数据块。

 

磁盘高速缓存作用于磁盘数据的“页”上,每页正好装在一个页框中。

 

通用块层将所有的上层和下层的组件组合在一起,因此它了解数据的扇区、块、段以及页。即使有许多不同的数据块,它们通常也是共享相同的物理RAM单元。例如,下图显示了一个具有4KB字节的页的构造。上层内核组件将页看成是41024字节组成的块缓冲区。块设备驱动程序正在传送页中的后三个块,因此这3块被插入到涵盖了后3072字节的段中。硬盘控制器将该段看成是由6512字节的扇区组成。

通用块层

通用块层是一个内核组件,它处理来自系统中的所有对块设备发出的请求。由于该层所提供的函数,内核可以容易的做到:

将数据缓冲区放在高端内存——仅当CPU访问其数据时,才将页框映射为内核中的线性地址空间,并在数据访问完后取消映射。

 

通过一些附加额手段,实现一个所谓的“零-复制”模式,将磁盘数据直接存放在用户态地址空间而不是首先复制到内核内存区;事实上,内核为I/O数据传送使用的缓冲区所在的页框就映射在进程的用户态线性地址空间中。

 

管理逻辑卷,例如有LVM(逻辑卷管理)RAID(磁盘冗余阵列)使用的逻辑卷:几个磁盘分区,即使位于不同的磁盘中,也可以被看做是一个单一的分区。

 

发挥大部分新磁盘控制器的高级特性。

 

bio结构

通用块层的核心数据结构是一个称为bio的描述符,它描述了块设备的I/O操作。在更上层的具体的文件系统的读写操作方法中,构造bio结构,并通过通用块层来提交各块设备驱动程序来进行实际的数据传输。每个bio结构都包含一个磁盘存储区表示符(存储区中的起始扇区号和扇区数目)和一个或多个描述与I/O操作相关的内存区的段。bio结构定义如下:

---------------------------------------------------------------------

include/linux/bio.h

62 struct bio {

 63      sector_t   bi_sector;      /* device address in 512 byte

 64                                                    sectors */

 65      struct bio          *bi_next;    /* request queue link */

 66      struct block_device     *bi_bdev;

 67      unsigned long     bi_flags;      /* status, command, etc */

 68      unsigned long    bi_rw;          /* bottom bits READ/WRITE,

 69                                        * top bits priority

 70                                        */

 71

 72      unsigned short    bi_vcnt;       /* how many bio_vec's */

 73      unsigned short    bi_idx;   /* current index into bvl_vec */

 74

 75         /* Number of segments in this BIO after

 76          * physical address coalescing is performed.

 77          */

 78      unsigned int      bi_phys_segments;

 79

 80      unsigned int      bi_size;        /* residual I/O count */

 81

 82      /*

 83       * To keep track of the max segment size, we account for the

 84       * sizes of the first and last mergeable segments in this bio.

 85       */

 86      unsigned int    bi_seg_front_size;

 87      unsigned int    bi_seg_back_size;

 88

 89      unsigned int    bi_max_vecs; /* max bvl_vecs we can hold */

 90

 91      unsigned int    bi_comp_cpu; /* completion CPU */

 92      /* bio 的引用计数器 */

 93      atomic_t     bi_cnt;      /* pin count */

 94

 95      struct bio_vec   *bi_io_vec;     /* the actual vec list */

 96      /* bioI/O操作结束时调用的方法  */

 97      bio_end_io_t     *bi_end_io;

 98      /* 通用块层和块设备驱动程序的I/O完成方法使用的指针 */

 99      void             *bi_private;

100 #if defined(CONFIG_BLK_DEV_INTEGRITY)

101     struct bio_integrity_payload *bi_integrity; /* data integrity */

102 #endif

103   /* 释放bio时调用的析构方法(通常是bio_fs_destructor()函数)*/

104     bio_destructor_t  *bi_destructor; /* destructor */

105

106     /*

107      * We can inline a number of vecs at the end of the bio, to

108      * avoid double allocations for a small number of bio_vecs.

109      * This member MUST obviously be kept at the very end of the bio.

110      */

111     struct bio_vec          bi_inline_vecs[0];

112 };

---------------------------------------------------------------------

bio结构中的每个段是由一个bio_vec数据结构描述的。其定义为:

---------------------------------------------------------------------

include/linux/bio.h

46 struct bio_vec {

 47         struct page  *bv_page;  /* 指向段的页框中也描述符的指针*

 48         unsigned int  bv_len; /* 段的字节长度 */

 49         unsigned int  bv_offset; /* 页框中段数据的偏移量 */

 50 };           

---------------------------------------------------------------------

在块I/O操作期间bio描述符的内容一直保持更新。例如,如果块设备驱动程序在一次分散-聚集DMA操作中不能完成全部的数据传送,那么bio中的bi_idx字段会不断更新来指向待传送的第一个段。为了从索引bi_idx指向的当前段开始不断重复bio中的段,设备驱动程可以执行宏bio_for_each_segment().

 

当通用块层启动一次新的I/O操作时,调用bio_alloc()函数分配一个新的bio结构。内核使用fs_bio_set结构类型来管理bio结构相关内存分配的缓冲区,这个结构由几个用于引用内存池或slab缓冲的指针组成。fs_bio_set中的一个成员bio_pool用于引用分配bio结构的内存池,而在这个缓冲区中分配的内存块的单位并不是sizeof(struct bio),而是sizeof(struct bio) + BIO_INLINE_VECS * sizeof(struct bio_vec)个字节。在分配了bio结构之后,通常要为bio分配bio_vec结构,当bio_vec结构数小于BIO_INLINE_VECS时则可以通过bio结构的最后一个成员bi_inline_vecs来引用这些bio_vec结构。

 

内核同时创建了6slab缓冲区用于分配数量不等的bio_vec结构,当所需的bio_vec结构数大于BIO_INLINE_VECS,则从这些缓冲区中分配内存。

 

磁盘和磁盘分区的表示

磁盘是一个由通用块层处理的逻辑块设备。通常一个磁盘对应一个硬件块设备,例如硬盘、软盘或光盘。但是,磁盘也可以是一个虚拟设备,它建立在几个物理磁盘分区之上或一些RAM专用页中的内存上面。在任何情形中,借助通用块层提供的服务,上层内核组件可以以同样的方式工作在所有的磁盘上。

 

磁盘是由gendisk对象描述的,其定义为:

---------------------------------------------------------------------

include/linux/genhd.h

struct gendisk {

/* major, first_minor and minors are input parameters only,

         * don't use directly.  Use disk_devt() and disk_max_parts().

 */

int major;          /* major number of driver */

int first_minor; /* 与磁盘关联的第一个次设备号 */

int minors;         /* maximum number of minors, =1 for

                             * disks that can't be partitioned. */

/* 磁盘的标准名称(通常是相应设备文件的规范名称 */

char disk_name[DISK_NAME_LEN]; /* name of major driver */

char *(*devnode)(struct gendisk *gd, mode_t *mode);

/* Array of pointers to partitions indexed by partno.

* Protected with matching bdev lock but stat and other

* non-critical accesses use RCU.  Always access through

* helpers.

*/

struct disk_part_tbl *part_tbl;

struct hd_struct part0;

 

const struct block_device_operations *fops;

struct request_queue *queue;

void *private_data; /* 块设备驱动程序的私有数据 */

/* 描述磁盘类型的标志 */

int flags;

struct device *driverfs_dev;  // FIXME: remove

struct kobject *slave_dir;

/* 该类型指向的这个数据结构记录磁盘中断的定时;由内核内置的

* 随机数发生器使用*/

struct timer_rand_state *random;

/* 写入磁盘的扇区数计数器 */

atomic_t sync_io;      /* RAID */

struct work_struct async_notify;

#ifdef  CONFIG_BLK_DEV_INTEGRITY

struct blk_integrity *integrity;

#endif

int node_id;

};               

---------------------------------------------------------------------

flags字段存放了关于磁盘的信息。其中最重要的标志是GENHD_FL_UP:如果该位置位,那么磁盘将被初始化并可以使用。另一个相关的标志是GENHD_FL_REMOVABLE,如果是诸如软盘或光盘这样可移动的磁盘,那么就要设置该标志。

 

gendisk对象的fops字段指向一个表block_device_operations,该表为块设备的主要操作存放了几个定制的方法。其定义为:

---------------------------------------------------------------------

include/linux/blkdev.h

 

1277 struct block_device_operations {

            /* 打开块设备文件 */

1278         int (*open) (struct block_device *, fmode_t);

            /* 关闭对块设备文件的最后一个引用 */

1279         int (*release) (struct gendisk *, fmode_t);

1280         int (*locked_ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);

            /* 在块设备文件上发出ioctl()系统调用(使用大内核锁) */

1281         int (*ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);

            /* 在块设备文件上发出ioctl()系统调用(不使用大内核锁) */

1282         int (*compat_ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);

1283         int (*direct_access) (struct block_device *, sector_t,

1284                                                 void **, unsigned long *);

             /* 检查可移动介质是否已经变化(例如软盘)*/

1285         int (*media_changed) (struct gendisk *);

1286         unsigned long long (*set_capacity) (struct gendisk *,

1287                                                 unsigned long long);

             /* 检查块设备是否持有有效数据 */

1288         int (*revalidate_disk) (struct gendisk *);

1289         int (*getgeo)(struct block_device *, struct hd_geometry *);

1290         struct module *owner;

1291 };

---------------------------------------------------------------------

 

通常硬盘被划分成几个逻辑分区。每个块设备文件要么代表整个磁盘,要么代表磁盘中的某一个分区。例如,一个主设备号为3的,次设备号为0的设备文件/dev/had代表的可能是一个主EIDE磁盘;该磁盘中的前两个分区分别由设备文件/dev/hda1/dev/hda2代表,它们的主设备号都是3,而此设备号分别为12。一般而言,磁盘中的分区是由连续的次设备号来区分的。

 

如果将一个硬盘分成了几个区,那么其分区表保存在disk_part_tbl结构中,由gendisk结构的part_tbl字段来引用。disk_part_tbl结构定义为:

---------------------------------------------------------------------

include/linux/genhd.h

129 struct disk_part_tbl {

130         struct rcu_head rcu_head;

131         int len;

132         struct hd_struct *last_lookup;

133         struct hd_struct *part[];

134 };

---------------------------------------------------------------------

各个分区信息保存在hd_struct结构数组中,由part字段引用。通过磁盘内分区的相对索引对该数组进行索引。hd_struct结构定义如下:

---------------------------------------------------------------------

include/linux/genhd.h

90 struct hd_struct {

 91         sector_t start_sect;

 92         sector_t nr_sects;

 93         sector_t alignment_offset;

 94         unsigned int discard_alignment;

 95         struct device __dev;

 96         struct kobject *holder_dir;

 97         int policy, partno;

 98 #ifdef CONFIG_FAIL_MAKE_REQUEST

 99         int make_it_fail;

100 #endif

101         unsigned long stamp;

102         int in_flight[2];

103 #ifdef  CONFIG_SMP

104         struct disk_stats __percpu *dkstats;

105 #else

106         struct disk_stats dkstats;

107 #endif

108         struct rcu_head rcu_head;

109 };

---------------------------------------------------------------------

当内核发现系统中一个新的磁盘时(在启动阶段,或将一个可移动介质插入一个驱动器中时,或在运行期间附加一个外置式磁盘时),就调用alloc_disk()函数,该函数分配并初始化一个新的gendisk对象,如果新磁盘被分成了几个分区,那么alloc_disk()还会分配一个适当的disk_part_tbl结构。然后,内核调用add_disk()函数将新的gendisk对象插入到通用块层的数据结构中。


提交请求

我们假设被请求的数据块在磁盘上是相邻的,并且内核已经知道了它们的物理位置。比如btrfs文件系统提交bio的过程,在函数submit_extent_page()中完成,它会调用extent_bio_alloc()来分配bioextent_bio_alloc()定义为:

---------------------------------------------------------------------

fs/btrfs/extent_io.c

1879 static struct bio *

1880 extent_bio_alloc(struct block_device *bdev, u64 first_sector, int

1881                  nr_vecs, gfp_t gfp_flags)

1882 {

1883         struct bio *bio;

1884

1885         bio = bio_alloc(gfp_flags, nr_vecs);

1886

1887         if (bio == NULL && (current->flags & PF_MEMALLOC)) {

1888                 while (!bio && (nr_vecs /= 2))

1889                         bio = bio_alloc(gfp_flags, nr_vecs);

1890         }

1891

1892         if (bio) {

1893                 bio->bi_size = 0;

1894                 bio->bi_bdev = bdev;

1895                 bio->bi_sector = first_sector;

1896         }

1897         return bio;

1898 }

---------------------------------------------------------------------

extent_bio_alloc()函数会调用bio_alloc()函数分配一个新的bio描述符。bio_alloc()函数同时分配bio_vec结构数组,并将bi_io_vec设为bio_vec结构数组的起始地址,数组中每个元素描述了I/O操作中的一个段(内存缓冲);此外,将bi_vcnt设为bio中总的段数。然后设置bio的一些字段来初始化bio描述符:

bi_bdev设为块设备描述符的地址。

bi_sector设为数据的起始扇区号(如果磁盘分成了几个区,那么扇区号是相对于分区的起始位置的)。

 

submit_extent_page()还会完成如下操作:

调用bio_add_page()来初始化bio_vec数组,也就是使bio_vec数组中各成员都指示数据的正确位置,都指向正确的页中的段。

设置bi_end_io为当bio上的I/O操作完成时所执行的完成程序的地址。

然后调用submit_one_bio()函数来提交bio,这个函数定义本质上主要调用submit_bio()函数来提交请求,而submit_bio()函数则主要为在设置了bi_rw为被请求的操作的标志之后,调用generic_make_request(bio)函数,它是通用块层的主要入口点。 其定义为:

---------------------------------------------------------------------

block/blk-core.c

1490 /*

1491  * We only want one ->make_request_fn to be active at a time,

1492  * else stack usage with stacked devices could be a problem.

1493  * So use current->bio_list to keep a list of requests

1494  * submited by a make_request_fn function.

1495  * current->bio_list is also used as a flag to say if

1496  * generic_make_request is currently active in this task or not.

1497  * If it is NULL, then no make_request is active.  If it is non-NULL,

1498  * then a make_request is active, and new requests should be added

1499  * at the tail

1500  */

1501 void generic_make_request(struct bio *bio)

1502 {

1503         struct bio_list bio_list_on_stack;

1504

1505         if (current->bio_list) {

1506                 /* make_request is active */

1507                 bio_list_add(current->bio_list, bio);

1508                 return;

1509         }

1510         /* following loop may be a bit non-obvious, and so deserves

1511          * some explanation.

1512          * Before entering the loop, bio->bi_next is NULL (as all

1513          * callers ensure that) so we have a list with a single bio.

1514          * We pretend that we have just taken it off a longer list,

1515          * so we assign bio_list to a pointer to the

1516          * bio_list_on_stack,thus initialising the bio_list of new

1517          * bios to be added.  __generic_make_request may indeed add

1518          * some more bios through a recursive call to

1519          * generic_make_request.  If it did, we find a non-NULL

1520          * value in bio_list and re-enter the loop from the top.  In

1521          * this case we really did just take the bio of the top of

1522          * the list (no pretending) and so remove it from bio_list,

1523          * and call into __generic_make_request again.*

1524          * The loop was structured like this to make only one call

1525          * to __generic_make_request (which is important as it is

1526          * large and inlined) and to keep the structure simple.

1527          */

1528         BUG_ON(bio->bi_next);

1529         bio_list_init(&bio_list_on_stack);

1530         current->bio_list = &bio_list_on_stack;

1531         do {

1532                 __generic_make_request(bio);

1533                 bio = bio_list_pop(current->bio_list);

1534         } while (bio);

1535         current->bio_list = NULL; /* deactivate */

1536 }

1537 EXPORT_SYMBOL(generic_make_request);

---------------------------------------------------------------------

generic_make_request(bio)函数又主要调用__generic_make_request()函数来完成工作,__generic_make_request()定义为:

---------------------------------------------------------------------

block/blk-core.c

1404 static inline void __generic_make_request(struct bio *bio)

1405 {

1406         struct request_queue *q;

1407         sector_t old_sector;

1408         int ret, nr_sectors = bio_sectors(bio);

1409         dev_t old_dev;

1410         int err = -EIO;

1411

1412         might_sleep();

1413

1414         if (bio_check_eod(bio, nr_sectors))

1415                 goto end_io;

1416

1417     /*

1418      * Resolve the mapping until finished. (drivers are

1419      * still free to implement/resolve their own stacking

1420      * by explicitly returning 0)

1421      *

1422      * NOTE: we don't repeat the blk_size check for each new device.

1423      * Stacking drivers are expected to know what they are doing.

1424      */

1425         old_sector = -1;

1426         old_dev = 0;

1427         do {

1428                 char b[BDEVNAME_SIZE];

1429

1430                 q = bdev_get_queue(bio->bi_bdev);

1431                 if (unlikely(!q)) {

1432                         printk(KERN_ERR

1433                          "generic_make_request: Trying to access "

1434                          "nonexistent block-device %s (%Lu)\n",

1435                           bdevname(bio->bi_bdev, b),

1436                           (long long) bio->bi_sector);

1437                      goto end_io;

1438                 }

1439

1440              if (unlikely(!bio_rw_flagged(bio, BIO_RW_DISCARD) &&

1441                 nr_sectors > queue_max_hw_sectors(q))) {

1442                 printk(KERN_ERR "bio too big device %s (%u > %u)\n",

1443                 bdevname(bio->bi_bdev, b),

1444                 bio_sectors(bio),

1445                 queue_max_hw_sectors(q));

1446                 goto end_io;

1447               }

1448

1449         if (unlikely(test_bit(QUEUE_FLAG_DEAD, &q->queue_flags)))

1450                   goto end_io;

1451

1452         if (should_fail_request(bio))

1453                         goto end_io;

1454

1455         /*

1456          * If this device has partitions, remap block n

1457          * of partition p to block n+start(p) of the disk.

1458          */

1459         blk_partition_remap(bio);

1460

1461        if (bio_integrity_enabled(bio) && bio_integrity_prep(bio))

1462                goto end_io;

1463

1464        if (old_sector != -1)

1465                trace_block_remap(q, bio, old_dev, old_sector);

1466

1467        old_sector = bio->bi_sector;

1468        old_dev = bio->bi_bdev->bd_dev;

1469

1470        if (bio_check_eod(bio, nr_sectors))

1471                 goto end_io;

1472

1473        if (bio_rw_flagged(bio, BIO_RW_DISCARD) &&

1474                !blk_queue_discard(q)) {

1475                 err = -EOPNOTSUPP;

1476                 goto end_io;

1477         }

1478

1479         trace_block_bio_queue(q, bio);

1480

1481         ret = q->make_request_fn(q, bio);

1482      } while (ret);

1483

1484      return;

1485

1486 end_io:

1487         bio_endio(bio, err);

1488 }

---------------------------------------------------------------------

__generic_make_request()函数提交一个bufferbuffer的设备驱动来进行I/O,它接收唯一的一个参数,即描述内存和设备上的数据的位置的bio结构。它不返回任何状态值,除了关于完成的通知外,请求的success/failure都是通过在其他地方定义的bio->bi_end_io函数来异步传送的。generic_make_request()的调用者必须确保bi_io_vec被正确的设置来描述内存缓冲,bi_devbi_sector被设置用来描述设备上的地址,bi_end_io和可选的bi_private字段被设置用来描述如何发送完成信号。

 

如果bio 碰巧和其他的bio合并了,则generic_make_request()和它调用的驱动可能会使用bi_next字段,并且可能改变bi_devbi_sector来重映射。所以generic_make_request()的调用者不应该依赖这些字段的值。

 

__generic_make_request()函数执行下列操作:

1、调用bio_check_eod(bio, nr_sectors)检查bio->bi_size没有超过块设备的扇区数。如果超过,则将设置bio->bi_flagsBIO_EOF位,然后打印一条内核错误消息,调用bio_endio()函数,并终止。bio_endio()清除bio->bi_flagsBIO_UPTODATE位,然后调用biobi_end_io()方法。bi_end_io()函数的实现本质上依赖于触发I/O数据传送的内核组件。

 

2、获取与块设备相关的请求队列q;其地址存放在块设备描述符的bd_disk->queue字段中,而块设备描述符地址则存放在bio->bi_bdev中。

 

3、调用blk_partition_remap(bio)函数检查块设备是否指向一个磁盘分区(bio->bi_bdev不等于bio->bdev->bd_contains)。如果是,则从bio->bi_bdev获取分区的hd_struct描述符,,从而执行下面的操作:

a.调整bio->bi_sector值使其原来的相对于分区的起始扇区号变为相对于整个磁盘的扇区号。

b.bio->bi_bdev设置为整个磁盘的块设备描述符(bdev->bd_contains)。

 

从现在开始,通用块层、I/O调度程序以及设备驱动程序将忘记磁盘分区的存在,直接作用于整个磁盘。

 

4、调用q->make_request_fn(q, bio)方法将bio插入请求队列q中。

 

5、返回。


阅读(1304) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~