Linux I/O Block--块设备的表示

块设备访问与Linux内核优化
本文深入探讨块设备的表示方式与Linux内核如何通过缓存与I/O请求重排列来提升访问效率,包括块设备数据结构、缓存机制、I/O调度策略等关键内容。
块设备的特点是其平均访问时间较长,因此为了提高块设备的访问效率,Linux内核用了很多的笔墨来设计和块设备相关的部分,这样一来,从代码的角度来看,访问一个文件的过程变得尤其的漫长……整个路径包含的过程基本可以概括为虚拟文件系统-->块设备实际文件系统-->通用块层-->I/O scheduler-->块设备驱动程序。为了提高块设备的访问效率,内核主要是在两个方面下功夫:

1.引入缓存,当用户空间要访问文件时,内核不可能每次都去访问块设备,内核会将块设备的内容读取到内存中,以便下次访问时可以直接在内存中找到相应的内容,这其中又涉及到了预读等相关的问题,当然这不是现在关注的重点……

2.对于I/O请求的重排列,I/O请求并不会立即被响应,而是会放在一个队列里进行一段延迟,以期能够和后来的I/O请求进行合并或者进行排序。因为像磁盘这样的块设备,其耗时主要是因为磁头的定位,因此内核会尽量保证磁头只往一个方向移动,而不是来回移动(可以和电梯的运作进行对比),简而言之,就是将存储介质上相邻的数据请求安排在一起,对于I/O请求的处理主要包括合并和排序,具体如何处理,由I/O scheduler决定。

 

首先,我们先来了解一个块设备是如何表示的。描述块设备的数据结构有两个,一个是struct block_device,用来描述一个块设备或者块设备的一个分区;另一个是struct gendisk,用来描述整个块设备的特性。对于一个包含多个分区的块设备,struct block_device结构有多个,而struct gendisk结构永远只有一个。

  1. struct block_device {  
  2.     dev_t           bd_dev;  /* not a kdev_t - it's a search key */  
  3.     struct inode *      bd_inode;   /* will die */  
  4.     struct super_block *    bd_super;  
  5.     int         bd_openers;  
  6.     struct mutex        bd_mutex;   /* open/close mutex */  
  7.     struct list_head    bd_inodes;  
  8.     void *          bd_holder;  
  9.     int         bd_holders;  
  10. #ifdef CONFIG_SYSFS   
  11.     struct list_head    bd_holder_list;  
  12. #endif   
  13.     struct block_device *   bd_contains;  
  14.     unsigned        bd_block_size;  
  15.     struct hd_struct *  bd_part;  
  16.     /* number of times partitions within this device have been opened. */  
  17.     unsigned        bd_part_count;  
  18.     int         bd_invalidated;  
  19.     struct gendisk *    bd_disk;  
  20.     struct list_head    bd_list;  
  21.     /* 
  22.      * Private data.  You must have bd_claim'ed the block_device 
  23.      * to use this.  NOTE:  bd_claim allows an owner to claim 
  24.      * the same device multiple times, the owner must take special 
  25.      * care to not mess up bd_private for that case. 
  26.      */  
  27.     unsigned long       bd_private;  
  28.   
  29.     /* The counter of freeze processes */  
  30.     int         bd_fsfreeze_count;  
  31.     /* Mutex for freeze */  
  32.     struct mutex        bd_fsfreeze_mutex;  
  33. };  

bd_dev:该设备(分区)的设备号

bd_inode:指向该设备文件的inode

bd_openers:一个引用计数,记录了该块设备打开的次数,或者说有多少个进程打开了该设备

bd_contains:如果该block_device描述的是一个分区,则该变量指向描述主块设备的block_device,反之,其指向本身

bd_part:如果该block_device描述的是一个分区,则该变量指向分区的信息

bd_part_count:如果是分区,该变量记录了分区被打开的次数,在进行分区的重新扫描前,要保证该计数值为0

bd_disk:指向描述整个设备的gendisk结构

 

  1. <SPAN style="FONT-SIZE: 12px">struct gendisk {  
  2.     /* major, first_minor and minors are input parameters only, 
  3.      * don't use directly.  Use disk_devt() and disk_max_parts(). 
  4.      */  
  5.     int major;          /* major number of driver */  
  6.     int first_minor;  
  7.     int minors;                     /* maximum number of minors, =1 for 
  8.                                          * disks that can't be partitioned. */  
  9.   
  10.     char disk_name[DISK_NAME_LEN];  /* name of major driver */  
  11.     char *(*devnode)(struct gendisk *gd, mode_t *mode);  
  12.     /* Array of pointers to partitions indexed by partno. 
  13.      * Protected with matching bdev lock but stat and other 
  14.      * non-critical accesses use RCU.  Always access through 
  15.      * helpers. 
  16.      */  
  17.     struct disk_part_tbl *part_tbl;  
  18.     struct hd_struct part0;  
  19.   
  20.     const struct block_device_operations *fops;  
  21.     struct request_queue *queue;  
  22.     void *private_data;  
  23.   
  24.     int flags;  
  25.     struct device *driverfs_dev;  // FIXME: remove   
  26.     struct kobject *slave_dir;  
  27.   
  28.     struct timer_rand_state *random;  
  29.   
  30.     atomic_t sync_io;       /* RAID */  
  31.     struct work_struct async_notify;  
  32. #ifdef  CONFIG_BLK_DEV_INTEGRITY   
  33.     struct blk_integrity *integrity;  
  34. #endif   
  35.     int node_id;  
  36. };</SPAN>  

major:块设备的主设备号

first_minor:起始次设备号

minors:描述了该块设备有多少个次设备号,或者说有多少个分区,如果minors为1,则表示该块设备没有分区

part_tbl:整个块设备的分区信息都包含在里面,其核心结构是一个struct hd_struct的指针数组,每一项都指向一个描述分区的hd_struct结构

fops:指向特定于设备的底层操作函数集

queue:块设备的请求队列,所有针对该设备的请求都会放入该请求队列中,经过I/O scheduler的处理再进行提交

 

块设备的分区信息由struct hd_struct结构描述,其中最重要的信息就是分区的起始扇区号和分区的大小。所有分区信息都一起保存在gendisk的part_tbl结构中,同时每个分区的block_device也可以通过bd_part来查询对应的分区信息。

下图描述了block_device,gendisk以及分区描述符之间的关系(块设备有两个分区)

 

下面通过打开一个块设备的过程,来理解这些结构之间的联系。

对于块设备文件的操作,通过block_dev伪文件系统来完成,open操作定义的函数为blkdev_open()

blkdev_open的主要任务有两个

1.获取设备的block_device信息

2.从gendisk中读取相关信息保存到block_device,同时建立数据结构之间的联系

  1. static int blkdev_open(struct inode * inode, struct file * filp)  
  2. {  
  3.     struct block_device *bdev;  
  4.     int res;  
  5.   
  6.     /* 
  7.      * Preserve backwards compatibility and allow large file access 
  8.      * even if userspace doesn't ask for it explicitly. Some mkfs 
  9.      * binary needs it. We might want to drop this workaround 
  10.      * during an unstable branch. 
  11.      */  
  12.     filp->f_flags |= O_LARGEFILE;  
  13.   
  14.     if (filp->f_flags & O_NDELAY)  
  15.         filp->f_mode |= FMODE_NDELAY;  
  16.     if (filp->f_flags & O_EXCL)  
  17.         filp->f_mode |= FMODE_EXCL;  
  18.     if ((filp->f_flags & O_ACCMODE) == 3)  
  19.         filp->f_mode |= FMODE_WRITE_IOCTL;  
  20.   
  21.     bdev = bd_acquire(inode);//获取block device实例   
  22.     if (bdev == NULL)  
  23.         return -ENOMEM;  
  24.   
  25.     filp->f_mapping = bdev->bd_inode->i_mapping;  
  26.   
  27.     res = blkdev_get(bdev, filp->f_mode);//通过gendisk获取信息并建立联系   
  28.     if (res)  
  29.         return res;  
  30.   
  31.     if (filp->f_mode & FMODE_EXCL) {  
  32.         res = bd_claim(bdev, filp);  
  33.         if (res)  
  34.             goto out_blkdev_put;  
  35.     }  
  36.   
  37.     return 0;  
  38.   
  39.  out_blkdev_put:  
  40.     blkdev_put(bdev, filp->f_mode);  
  41.     return res;  
  42. }  

 

bd_acquire()负责获取block_device的实例

  1. static struct block_device *bd_acquire(struct inode *inode)  
  2. {  
  3.     struct block_device *bdev;  
  4.   
  5.     spin_lock(&bdev_lock);  
  6.     bdev = inode->i_bdev;//如果这个设备之前被打开过则可以直接通过i_bdev获取   
  7.     if (bdev) {  
  8.         atomic_inc(&bdev->bd_inode->i_count);  
  9.         spin_unlock(&bdev_lock);  
  10.         return bdev;  
  11.     }  
  12.     spin_unlock(&bdev_lock);  
  13.   
  14.     bdev = bdget(inode->i_rdev);//通过设备号的信息来获取block device实例   
  15.     if (bdev) {  
  16.         spin_lock(&bdev_lock);  
  17.         if (!inode->i_bdev) {  
  18.             /* 
  19.              * We take an additional bd_inode->i_count for inode, 
  20.              * and it's released in clear_inode() of inode. 
  21.              * So, we can access it via ->i_mapping always 
  22.              * without igrab(). 
  23.              */  
  24.             atomic_inc(&bdev->bd_inode->i_count);  
  25.             inode->i_bdev = bdev;  
  26.             inode->i_mapping = bdev->bd_inode->i_mapping;  
  27.             list_add(&inode->i_devices, &bdev->bd_inodes);  
  28.         }  
  29.         spin_unlock(&bdev_lock);  
  30.     }  
  31.     return bdev;  
  32. }  
  1. struct block_device *bdget(dev_t dev)  
  2. {  
  3.     struct block_device *bdev;  
  4.     struct inode *inode;  
  5.   
  6.     /*这里先在inode的哈希表中进行查找与dev设备号对应的inode,如果没找到的话, 
  7.       则通过bdev伪文件系统创建bdev_inode(包含inode和block device的结构体)*/  
  8.     inode = iget5_locked(blockdev_superblock, hash(dev),  
  9.             bdev_test, bdev_set, &dev);  
  10.   
  11.     if (!inode)  
  12.         return NULL;  
  13.   
  14.     //通过inode获取bdev_inode,再通过bdev_inode获取block device实例   
  15.     bdev = &BDEV_I(inode)->bdev;  
  16.   
  17.     if (inode->i_state & I_NEW) {  
  18.         /*分别设置block device和inode的相关域*/  
  19.         bdev->bd_contains = NULL;  
  20.         bdev->bd_inode = inode;  
  21.         bdev->bd_block_size = (1 << inode->i_blkbits);  
  22.         bdev->bd_part_count = 0;  
  23.         bdev->bd_invalidated = 0;  
  24.         inode->i_mode = S_IFBLK;  
  25.         inode->i_rdev = dev;  
  26.         inode->i_bdev = bdev;  
  27.         inode->i_data.a_ops = &def_blk_aops;  
  28.         mapping_set_gfp_mask(&inode->i_data, GFP_USER);  
  29.         inode->i_data.backing_dev_info = &default_backing_dev_info;  
  30.         spin_lock(&bdev_lock);  
  31.         list_add(&bdev->bd_list, &all_bdevs);  
  32.         spin_unlock(&bdev_lock);  
  33.         unlock_new_inode(inode);  
  34.     }  
  35.     return bdev;  
  36. }  




blkdev_get()函数负责从gendisk中获取信息,并建立相关数据结构之间的联系

  1. int blkdev_get(struct block_device *bdev, fmode_t mode)  
  2. {  
  3.     return __blkdev_get(bdev, mode, 0);  
  4. }  


注意_blkdev_get()传递的最后一个参数为0,也就是说默认打开的是主设备

 获取到gendisk之后会分四种情况进行处理,也就是针对设备是不是第一次打开以及打开的设备是主设备还是分区来进行不同的处理,具体见代码注释

  1. <SPAN style="FONT-SIZE: 12px">static int __blkdev_get(struct block_device *bdev, fmode_t mode, int for_part)  
  2. {  
  3.     struct gendisk *disk;  
  4.     int ret;  
  5.     int partno;  
  6.     int perm = 0;  
  7.   
  8.     if (mode & FMODE_READ)  
  9.         perm |= MAY_READ;  
  10.     if (mode & FMODE_WRITE)  
  11.         perm |= MAY_WRITE;  
  12.     /* 
  13.      * hooks: /n/, see "layering violations". 
  14.      */  
  15.     if (!for_part) {  
  16.         ret = devcgroup_inode_permission(bdev->bd_inode, perm);  
  17.         if (ret != 0) {  
  18.             bdput(bdev);  
  19.             return ret;  
  20.         }  
  21.     }  
  22.   
  23.     lock_kernel();  
  24.  restart:  
  25.   
  26.     ret = -ENXIO;  
  27.     //获取该设备的gendisk实例,如果bd_dev对应的是一个分区设备的话,partno将会被修改   
  28.     disk = get_gendisk(bdev->bd_dev, &partno);  
  29.     if (!disk)  
  30.         goto out_unlock_kernel;  
  31.   
  32.     mutex_lock_nested(&bdev->bd_mutex, for_part);  
  33.     if (!bdev->bd_openers) {//如果是第一次打开设备   
  34.         bdev->bd_disk = disk;//建立block device和gendisk之间的联系   
  35.         bdev->bd_contains = bdev;  
  36.         if (!partno) {//partno为0,也就是说打开的是主设备而不是分区   
  37.             struct backing_dev_info *bdi;  
  38.   
  39.             ret = -ENXIO;  
  40.             bdev->bd_part = disk_get_part(disk, partno);//获取gendisk中的分区数组   
  41.             if (!bdev->bd_part)  
  42.                 goto out_clear;  
  43.   
  44.             if (disk->fops->open) {//gendisk中定义了open方式   
  45.                 ret = disk->fops->open(bdev, mode);//调用open针对具体的设备进行打开操作   
  46.                 if (ret == -ERESTARTSYS) {  
  47.                     /* Lost a race with 'disk' being 
  48.                      * deleted, try again. 
  49.                      * See md.c 
  50.                      */  
  51.                     disk_put_part(bdev->bd_part);  
  52.                     bdev->bd_part = NULL;  
  53.                     module_put(disk->fops->owner);  
  54.                     put_disk(disk);  
  55.                     bdev->bd_disk = NULL;  
  56.                     mutex_unlock(&bdev->bd_mutex);  
  57.                     goto restart;  
  58.                 }  
  59.                 if (ret)  
  60.                     goto out_clear;  
  61.             }  
  62.             if (!bdev->bd_openers) {  
  63.                 bd_set_size(bdev,(loff_t)get_capacity(disk)<<9);//从gendisk中提取容量信息设置到block device   
  64.                 bdi = blk_get_backing_dev_info(bdev);  
  65.                 if (bdi == NULL)  
  66.                     bdi = &default_backing_dev_info;  
  67.                 bdev->bd_inode->i_data.backing_dev_info = bdi;  
  68.             }  
  69.             //块设备上的分区改变导致分区在内核中的信息无效,则要重新扫描分区   
  70.             if (bdev->bd_invalidated)  
  71.                 rescan_partitions(disk, bdev);  
  72.         } else {//如果打开的是分区   
  73.             struct block_device *whole;  
  74.             whole = bdget_disk(disk, 0);//获取主设备的block device实例   
  75.             ret = -ENOMEM;  
  76.             if (!whole)  
  77.                 goto out_clear;  
  78.             BUG_ON(for_part);  
  79.             ret = __blkdev_get(whole, mode, 1);  
  80.             if (ret)  
  81.                 goto out_clear;  
  82.             bdev->bd_contains = whole;//设置分区的block device实例的bd_contains域到主设备   
  83.             bdev->bd_inode->i_data.backing_dev_info =  
  84.                whole->bd_inode->i_data.backing_dev_info;  
  85.             bdev->bd_part = disk_get_part(disk, partno);  
  86.             if (!(disk->flags & GENHD_FL_UP) ||  
  87.                 !bdev->bd_part || !bdev->bd_part->nr_sects) {  
  88.                 ret = -ENXIO;  
  89.                 goto out_clear;  
  90.             }  
  91.             bd_set_size(bdev, (loff_t)bdev->bd_part->nr_sects << 9);  
  92.         }  
  93.     }   else {//如果不是第一次打开   
  94.         module_put(disk->fops->owner);  
  95.         put_disk(disk);  
  96.         disk = NULL;  
  97.         if (bdev->bd_contains == bdev) {//打开的是主设备   
  98.             if (bdev->bd_disk->fops->open) {  
  99.                 ret = bdev->bd_disk->fops->open(bdev, mode);//调用定义的open   
  100.                 if (ret)  
  101.                     goto out_unlock_bdev;  
  102.             }  
  103.             if (bdev->bd_invalidated)  
  104.                 rescan_partitions(bdev->bd_disk, bdev);  
  105.         }  
  106.     }  
  107.     bdev->bd_openers++;//计数值加1   
  108.     if (for_part)//如果是分区则分区计数值也加1   
  109.         bdev->bd_part_count++;  
  110.     mutex_unlock(&bdev->bd_mutex);  
  111.     unlock_kernel();  
  112.     return 0;  
  113.   
  114.  out_clear:  
  115.     disk_put_part(bdev->bd_part);  
  116.     bdev->bd_disk = NULL;  
  117.     bdev->bd_part = NULL;  
  118.     bdev->bd_inode->i_data.backing_dev_info = &default_backing_dev_info;  
  119.     if (bdev != bdev->bd_contains)  
  120.         __blkdev_put(bdev->bd_contains, mode, 1);  
  121.     bdev->bd_contains = NULL;  
  122.  out_unlock_bdev:  
  123.     mutex_unlock(&bdev->bd_mutex);  
  124.  out_unlock_kernel:  
  125.     unlock_kernel();  
  126.   
  127.     if (disk)  
  128.         module_put(disk->fops->owner);  
  129.     put_disk(disk);  
  130.     bdput(bdev);  
  131.   
  132.     return ret;  
  133. }  
  134. </SPAN>  


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值