块设备分析

本文深入探讨了Linux内核中的块设备,包括扇区、块、块设备缓冲区(buffer_head结构体)、通用块层核心数据结构bio、磁盘对象描述符gendisk、请求队列request_queue以及请求描述符request等关键概念。详细解析了这些结构的功能和作用,如数据传输的基本单位、内存管理、I/O操作的调度和控制等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、磁盘上的数据时通过其首个扇区号和扇区数目表示的

2、扇区是硬件传送数据的基本单位,块是vfs和文件系统传送数据的基本单位

3、linux中块大小必须是2的幂且不能超过一页大小

4、块设备的块大小不是唯一的创建文件系统时可以指定

5、每个块都需要有块设备缓冲区,缓冲区首部是一个buffer_head结构体

struct buffer_head {
        /* First cache line: */
        unsigned long b_state;          /* buffer state bitmap (see above) */
        struct buffer_head *b_this_page;/* circular list of page's buffers */
        struct page *b_page;            /* the page this bh is mapped to */
        atomic_t b_count;               /* users using this block */
        u32 b_size;                     /* block size */

        sector_t b_blocknr;             /* block number */
        char *b_data;                   /* pointer to data block */

        struct block_device *b_bdev;
        bh_end_io_t *b_end_io;          /* I/O completion */
        void *b_private;                /* reserved for b_end_io */
        struct list_head b_assoc_buffers; /* associated with another mapping */
};
 

6、通用块层核心数据结构bio

struct bio {
        sector_t                bi_sector; //块io操作的第一个磁盘扇区
        struct bio              *bi_next;   //链接到请求队列中的下一个bio
        struct block_device     *bi_bdev;//指向块设备描述符的指针
        unsigned long           bi_flags;     //bio的状态标志
        unsigned long           bi_rw;       //io操作的标志。或读-0或写-1
                                                

        unsigned short          bi_vcnt;       //bi_io_vec数组中段的数目
        unsigned short          bi_idx;         //bi_io_vec数组中当前段的索引

        /* Number of segments in this BIO after
         * physical address coalescing is performed.
         */
        unsigned short          bi_phys_segments;//合并之后bio中物理段的数目
        unsigned short          bi_hw_segments;//合并之后硬件段的数目

        unsigned int            bi_size;    //需要传送的字节数
        unsigned int            bi_hw_front_size;//硬件段合并算法使用
        unsigned int            bi_hw_back_size;//硬件段合并算法使用

        unsigned int            bi_max_vecs;    //bi_io_vec数组中允许最大段的数目

        struct bio_vec          *bi_io_vec;     //指向bio数组中段的指针

        bio_end_io_t            *bi_end_io;//bio中io操作结束时回调的方法
        atomic_t                bi_cnt;        //此bio引用计数器

        void                    *bi_private;//通用块层完成方法使用的指针

        bio_destructor_t        *bi_destructor; //释放bio回调方法
        struct bio_set          *bi_set;        //内存池相关
};

7、当通用块层启动一个新的io操作时候会调用bio_alloc分配一个新的bio结构且会分配bio_vec数组所使用空间

8、磁盘对象描述符gendisk

struct gendisk {
        int major;                //主设备号
        int first_minor;      //与磁盘关联的第一个次设备号
        int minors;               //次设备数量
                                       
        char disk_name[32];             //磁盘的标准名称
        struct hd_struct **part;        //磁盘分区描述符数组
        struct block_device_operations *fops;//指向块设备操作表的指针
        struct request_queue *queue;//指向磁盘请求队列的指针
        void *private_data;//块设备驱动程序的私有数据
        sector_t capacity;//磁盘内存区大小(扇区数目)

        int flags;//描述磁盘类型标志
        char devfs_name[64];            //devfs特殊文件系统中文件名称
        int number;                     //不再使用
        struct device *driverfs_dev;//指向磁盘的硬件设备device结构体
        struct kobject kobj;//内嵌的kobject结构体

        struct timer_rand_state *random;//用于随机数发生器
        int policy;//磁盘只读设置为1,否则为0

        atomic_t sync_io;              //写入磁盘扇区数计数,仅为raid使用
        unsigned long stamp, stamp_idle;//时间戳
        int in_flight;//正在进行的io操作数
#ifdef  CONFIG_SMP
        struct disk_stats *dkstats;//统计每个cpu使用的情况的指针用于smp系统
#else
        struct disk_stats dkstats;//统计每个cpu使用的情况
#endif
};

struct block_device_operations {
        int (*open) (struct inode *, struct file *);//打开块设备文件
        int (*release) (struct inode *, struct file *);//关闭对块设备最后一个引用的时候
        int (*ioctl) (struct inode *, struct file *, unsigned, unsigned long);//在块设备上发出ioctl调用(使用大内核锁)
        long (*compat_ioctl) (struct file *, unsigned, unsigned long);//在块设备上发出ioctl调用(不使用大内核锁)
        int (*media_changed) (struct gendisk *);//检查可移动介质是否变化
        int (*revalidate_disk) (struct gendisk *);//检查块设备是否持有有效数据
        struct module *owner;//模块所属
};

分区描述符:

struct hd_struct {
        sector_t start_sect;//磁盘中分区起始扇区
        sector_t nr_sects;//分区的长度
        struct kobject kobj;//内嵌的kobject结构
        unsigned reads, read_sectors, writes, write_sectors;//读写次数、读写扇区数统计计数
        int policy, partno;//policy如果分区只读则设置为1,如果分区可写设置为0;partno表示磁盘分区的相对索引,近似于分区号
};

9、调用alloc_disk 分配新的gendisk描述符,如果调用参数大于1则还会分配分区空间并初始化bi_io_vec

10、通用块层通常使用generic_make_request下发io请求void generic_make_request(struct bio *bio)

        a、获取设备的最大扇区号maxsector与此次的请求扇区数比较以及请求的起始扇区号,如果不合理则打印错误消息并且设置BIO_EOF标志,并且调用bio_endio结束此次io请求

        b、获取请求队列q,如果请求队列获取为空则打印错误消息并且调用bio_endio结束此次io请求

         c、判断请求的扇区数是否大于请求队列的最大物理扇区限制,如果大于则打印错误信息并且调用bio_endio结束此次io请求

         d、接着调用block_wait_queue_running函数等待io调度器重新调度

         e、调用q->make_request_fn函数下发io请求

11、请求队列描述符request_queue

struct request_queue
{
       
        struct list_head        queue_head;//待处理请求链表
        struct request          *last_merge;//指向队列中第一个可以合并的请求描述符
        elevator_t              *elevator;//指向elevator对象

  
        struct request_list     rq; //为分配请求描述符使用的数据结构

        request_fn_proc         *request_fn;//实现驱动层序的策略入口函数
        merge_request_fn        *back_merge_fn;//检查是否可以将bio合并到请求队列最后一个请求中去
        merge_request_fn        *front_merge_fn;//检查是否可以将bio合并到请求队列第一个请求中去
        merge_requests_fn       *merge_requests_fn;//试图合并请求中两个相邻请求的方法
        make_request_fn         *make_request_fn;//将一个新的请求插入到请求队列里的方法
        prep_rq_fn              *prep_rq_fn;//此函数将处理请求命令发送到硬件
        unplug_fn               *unplug_fn;//移除块设备的方法
        merge_bvec_fn           *merge_bvec_fn;//一般不用
        activity_fn             *activity_fn;//将某个请求加入到请求队列的时候定义的方法
        issue_flush_fn          *issue_flush_fn;//刷新请求队列调用的方法
        prepare_flush_fn        *prepare_flush_fn;//刷新请求队列前回调方法
        end_flush_fn            *end_flush_fn;//刷新请求队列后回调方法

 
        struct timer_list       unplug_timer;//插入设备使用的动态定时器
        int                     unplug_thresh;  //阈值,如果请求队列中待处理的请求大于该值则失活该请求队列
        unsigned long           unplug_delay;   //失活之后再一次激活的间隔时间
        struct work_struct      unplug_work;   //失活请求队列的工作队列

        struct backing_dev_info backing_dev_info;  //稍后解释

         void                    *queuedata;  //指向块设备驱动程序的私有指针

        void                    *activity_data;//activity_fn函数的私有数据

        /*
         * queue needs bounce pages for pages above this limit
         */
        unsigned long           bounce_pfn;//在大于改页框号时必须使用回弹缓冲区
        unsigned int            bounce_gfp;//回弹缓冲区分配标志

      
        unsigned long           queue_flags;//描述请求队列状态的标志

      
        spinlock_t              __queue_lock;//队列自旋锁
        spinlock_t              *queue_lock;//队列自旋锁

     
        struct kobject kobj;//内嵌的kobj结构体

     
        unsigned long           nr_requests;   //请求队列中允许最大的请求数
        unsigned int            nr_congestion_on;//阈值,当高于此阈值则认为队列时拥挤的
        unsigned int            nr_congestion_off;//阈值,当低于此阈值则认为队列时不拥挤的
        unsigned int            nr_batching;//当满的时候特殊进程还能插入的请求数

        unsigned short          max_sectors;//单个请求所能处理最大的扇区数
        unsigned short          max_hw_sectors;//单个请求硬件上所能处理最大的扇区数
        unsigned short          max_phys_segments;//单个请求所能处理最大的段数
        unsigned short          max_hw_segments;//单个请求硬件上所能处理最大的段数
        unsigned short          hardsect_size;//扇区中以字节为单位的大小
        unsigned int            max_segment_size;//物理段的最大长度

        unsigned long           seg_boundary_mask;//段合并的内存边界屏蔽字
        unsigned int            dma_alignment;//dma缓冲区的起始地址和长度对齐字节的掩码

        struct blk_queue_tag    *queue_tags;//队列空闲忙标记位图

        atomic_t                refcnt;//队列使用计数

        unsigned int            in_flight;//请求队列中待处理的请求数

    
        unsigned int            sg_timeout; //用户定义命令的超时时间
        unsigned int            sg_reserved_size;//没有使用

        struct list_head        drain_list;//临时延时的请求链表首部

        /*
         * reserved for flush operations
         */
        struct request          *flush_rq;
        unsigned char           ordered;
};
struct backing_dev_info {
        unsigned long ra_pages; /* max readahead in PAGE_CACHE_SIZE units */
        unsigned long state;    /* Always use atomic bitops on this */
        unsigned int capabilities; /* Device capabilities */
        congested_fn *congested_fn; /* Function pointer if device is md/dm */
        void *congested_data;   /* Pointer to aux data for congested func */
        void (*unplug_io_fn)(struct backing_dev_info *, struct page *);
        void *unplug_io_data;
};
 

12、请求描述符

struct request {
        struct list_head queuelist;//请求队列链表指针
        unsigned long flags;       //请求标志

  

        sector_t sector;                //下一个要传送的扇区号
        unsigned long nr_sectors;       //剩余要阐传输的扇区数
        /* no. of sectors left to submit in the current segment */
        unsigned int current_nr_sectors;//当前段中要传输的剩余扇区数

        sector_t hard_sector;           //需要完成的下一个扇区号
        unsigned long hard_nr_sectors;  //需要传输的扇区数,通用块层更新
        /* no. of sectors left to complete in the current segment */
        unsigned int hard_cur_sectors;当前bio当前段中的要传输的扇区数,通用块层更新

        struct bio *bio;//请求中未完成的第一个bio
        struct bio *biotail;//请求链表末尾bio

        void *elevator_private;//指向io调度程序的私有数据

        int rq_status;  //请求状态
        struct gendisk *rq_disk;//指向此请求关联的磁盘设备
        int errors;//用于记录此次传送中的发生的io错误数
        unsigned long start_time;//请求的起始时间


        unsigned short nr_phys_segments;//请求的物理段数

        unsigned short nr_hw_segments;//请求的物理硬件段数

        int tag;//与请求相关的标记
        char *buffer;//指向当前数据传输缓冲区的指针

        int ref_count;//引用计数
        request_queue_t *q;//指向包含请求队列描述符的指针
        struct request_list *rl;//指向request_list指针

        struct completion *waiting;//等待数据传输结束的completion
        void *special;//对硬件发出特殊指令所使用的缓冲区指针

       
        unsigned int cmd_len;//命令长度
        unsigned char cmd[BLK_MAX_CDB];//命令缓冲区

        unsigned int data_len;//数据长度
        void *data;//驱动层序跟踪传输数据的指针

        unsigned int sense_len;//sense长度
        void *sense;//指向sense命令的缓冲区

        unsigned int timeout;//请求的超时时间

  
        struct request_pm_state *pm;//电源管理结构

     
        rq_end_io_fn *end_io;//请求接收回调函数
        void *end_io_data;//结束回调函数的参数
};
每个请求包含一个或多个bio,最开始通用块层创建包含一个bio的请求,然后io调度程序会添加新的段或者将别的bio链接到此请求中。rq_for_each_bio宏会遍历请求中的所有bio。一旦bio请求总的数据被传输完成则会立即更新rq->bio。

struct request_list {
        int count[2];//两个计数器分别用于read、write请求的请求描述符
        int starved[2];//两个标志用于读取或者写入传输是否失败
        mempool_t *rq_pool;//指向描述分配的内存池
        wait_queue_head_t wait[2];//用于读或者写获取请求描述符睡眠的队列
        wait_queue_head_t drain;//存放一个等待请求队列被刷新的进程
};

13、blk_get_request函数用于获取请求队列struct request *blk_get_request(request_queue_t *q, int rw, int gfp_mask)

14、请求队列拥塞控制

        a、QUEUE_FLAG_READFULL和QUEUE_FLAG_WRITEFULL标志分别表示队列读/写满

15、blk_congestion_wait判断队列如果拥塞则进程睡眠等待,long blk_congestion_wait(int rw, long timeout)此函数也就是睡眠在congestion_wqh专用队列等待唤醒或者超时

16、顺便说下请求队列的plug和unplug

     在io处理中当一个请求被加入到请求队列并没有立即下发到底层设备,等待一定时间或者定时器超时后会调用blk_remove_plug将请求队列移除并下发请求。

17、generic_make_request函数最后会调用到__make_request函数static int __make_request(request_queue_t *q, struct bio *bio)

      a、如果需要则调用blk_queue_bounce建立回弹缓冲区

       b、调用elv_queue_empty 函数判断请求队列是否为空,如果为空则调用blk_plug_device插入请求队列等待新的请求插入到队列,申请一个新的req,并将req插入到请求队列中。

       c、调用elv_merge函数去合并请求,此函数会返回三种结果ELEVATOR_BACK_MERGE、ELEVATOR_FRONT_MERGE、ELEVATOR_NO_MERGE,如果类似ELEVATOR_BACK_MERGE、ELEVATOR_FRONT_MERGE可以合并的则将bio挂入返回的req里然后直接返回,如果是ELEVATOR_NO_MERGE则需要进行下一步

       d、调用get_request申请一个req,将bio里的信息填充到req里

       e、调用add_request函数将请求加入到队列中

      add_request函数比较简单,如果存在  q->activity_fn则调用,在调用调度器的顺序插入函数。

       f、判断bio是否设置了BIO_RW_SYNC标志,如果设置了则调用__generic_unplug_device拔出设备开始执行q->request_fn下发请求的操作

18、块设备描述符block_device

struct block_device {
        dev_t                   bd_dev;  //块设备主次设备号
        struct inode *          bd_inode;   // 指向bdev文件系统中块设备对应的文件索引节点的指针
        int                     bd_openers;//计数器,统计块设备被打开多少次
        struct semaphore        bd_sem; //保护块设备打开和关闭的信号量
        struct semaphore        bd_mount_sem;   //禁止在块设备进行新安装的信号量
        struct list_head        bd_inodes;//已经打开的块设备里的文件索引节点的链表
        void *                  bd_holder;//块设备描述符的当前所有者
        int                     bd_holders;//计数器统计对bd_holder设置的次数
        struct block_device *   bd_contains;//如果设备是个分区则指向整个磁盘的描述符否则指向磁盘描述符
        unsigned                bd_block_size;//块大小
        struct hd_struct *      bd_part;//分区描述符指针
        unsigned                bd_part_count;//计数器,统计在块中的分区设备别的打开多少次
        int                     bd_invalidated;//当需要读取块设备的分区表时设置此值
        struct gendisk *        bd_disk;//指向块设备对应的磁盘描述符
        struct list_head        bd_list;//用于块设备描述符链表的指针
        struct backing_dev_info *bd_inode_backing_dev_info;//通常为空
     
        unsigned long           bd_private;//指向持有块设备的所有者私有的数据指针
};

所有的块设备都被链入全局链表all_bdevs里,bd_claim函数会设置bdev->bd_holder,bd_release函数则是释放。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值