[Linux 驱动] -- Linux 设备驱动--块设备(二)之相关结构体

上回最后面介绍了相关数据结构,下面再详细介绍

块设备对象结构 block_device

内核用结构 block_device 实例代表一个块设备对象,如:整个硬盘或特定分区。如果该结构代表一个分区,则其成员bd_part指向设备的分区结构。如果该结构代表设备,则其成员bd_disk指向设备的通用硬盘结构gendisk。

当用户打开块设备文件时,内核创建结构block_device实例,设备驱动程序还将创建结构gendisk实例,分配请求队列并注册结构block_device实例。

块设备对象结构block_device列出如下(在include/linux/fs.h中)

struct block_device {
    dev_t bd_dev;    /* not a kdev_t - it's a search key */
    struct inode *bd_inode; /* 分区节点 */
    struct super_block *bd_super;
    int bd_openers;
    struct mutex bd_mutex; /* open/clos mutex 打开与关闭的互斥量 */
    struct semaphore bd_mount_sem; /* 挂载操作信号量 */
    struct list_head bd_inodes;
    void *bd_holder;
    int bd_holders;
    #ifdef CONFIG_SYSFS
        struct list_head bd_holder_list;
    #endif
    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;
    /* The counter of freeze processes */
    int bd_fsfreeze_cout;
    /* Mutex for freeze */
    struct mutex bd_fsfreeze_mutex;
};

通用硬盘结构 gendisk

结构体gendisk代表了一个通用硬盘(generic hard disk)对象,它存储了一个硬盘的信息,包括请求队列、分区链表和块设备操作函数集等。块设备驱动程序分配结构 gendisk 实例,装载分区表,分配请求队列并填充结构的其他域。

支持分区的块驱动程序必须包含 <linux/genhd.h> 头文件,并声明一个结构 gendisk,内核还维护该结构实例的一个全局链表 gendisk_head,通过函数 add_gendisk、del_gendisk 和 get_gendisk 维护该链表。

结构 gendisk列出如下(在 inlude/linux/genhd.h 中):

struct gendisk {
    int major; /* 驱动程序的主设备号 */
    int first_minor; /* 第一个次设备号 */
    int minors; /* 次设备号的最大数量,没有分区的设备,此值为1 */
    char disk_name[32]; /* 主设备号驱动程序的名字 */
    struct hd_struct **part; /* 分区列表,由次设备号排序 */
    struct block_device_operations *fops; /* 块设备操作函数集 */
    struct request_queue *queue; /* 请求队列 */
    struct blk_scsi_cmd_filter cmd_filter;
    void *private_data; /* 私有数据 */
    sector_t capacity; /* 函数 set_capacity 设置的容量,以扇区为单位 */
    int flags; /* 设置驱动器状态的标志,如:可移动介质为 GENHD_FL_REMOVABLE */
    struct device dev; /* 从设备驱动模型基类结构device继承 */
    struct kobject *holder_dir; 
    struct kobject *slave_dir;
    struct timer_rand_state *random;
    int policy;
    atomic_t sync_io; /* RAID */
    unsigned long stamp;
    int in_flight; 

    #ifdef CONFIG_SMP
        struct disk_stats *dkstats;
    #else
    /* 硬盘统计信息,如:读或写的扇区数、融合的扇区数、在请求队列的时间等 */
        struct disk_stats dkstats;
    #endif

    struct work_struct async_notify;

    #ifdef CONFIG_BLK_DEV_INTEGRITY
        struct blk_integrity *integrity; /* 用于数据完整性扩展 */
    #endif
};

Linux 内核提供了一组函数来操作gendisk,主要包括:

分配 gendisk:
        struct gendisk *alloc_disk(int minors);
        minors 参数是这个磁盘使用的次设备号的数量,一般也就是磁盘分区的数量,此后minors不能被修改。

增加 gendisk:
        gendisk 结构体被分配之后,系统还不能使用这个磁盘,需要调用如下函数来注册这个磁盘设备:
        void add_disk(struct gendisk *gd);
        特别要注意的是对 add_disk() 的调用必须发生在驱动程序的初始化工作完成并能相应磁盘的请求之后。

释放 gendisk:
        当不再需要一个磁盘时,应当使用如下函数释放 gendisk:
        void del_gendisk(struct gendisk *gd);

设置 gendisk 容量:
        void set_capacity(struct gendisk *disk, sector_t size);
        块设备中最小的可寻址单元是扇区,扇区大小一般是2的整数倍,最常见的大小是512字节。扇区的大小是设备的物理属性,扇区是所有块设备的基本单元,块设备无法对比它还小的单元进行寻址和操作,不过许多块设备能够一次就传输多个扇区。虽然大多数块设备的扇区大小都是512字节,不过其它大小的扇区也很常见,比如,很多CD-ROM盘的扇区都是2K大小。不管物理设备的真实扇区大小是多少,内核与块设备驱动交互的扇区都以512字节为单位。因此,set_capacity() 函数也是以512字节为单位。

分区结构hd_struct代表了一个分区对象,它存储了一个硬盘的一个分区的信息,驱动程序初始化时,从硬盘的分区表中提取分区信息,存放在分区结构实例中。

块设备操作函数集结构 block_device_operations

字符设备通过 file_operations 操作结构使它们的操作对系统可用。一个类似的结构用在块设备上是 struct block_device_operations,定义在 <linux/fs.h>。

int (*open)(struct inode *inode, struct file *filp);

int (*release)(struct inode *inode, struct file *filp);

就像它们的字符驱动对等体一样工作的函数;无论何时设备被打开和关闭都调用它们,一个字符驱动可能通过启动设备或者锁住门(为可移出的介质)来响应一个 open 调用。如果你将介质锁入设备,你当然应当在 release 方法中解锁。

int (*ioctl)(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg);

实现 ioctl 系统调用的方法。但是,块层首先解释大量的标准请求;因此大部分的块驱动 ioctl 方法相当短。

PS:在block_device_operations中没有实际读或写数据的函数。在块I/O子系统,这些操作由请求函数处理。

请求结构 request

结构 request 代表了挂起的 I/O 请求,每个请求用一个结构 request 实例描述,存放在请求对列链表中,由电梯算法进行排序,每个请求包含1个或多个结构bio实例

struct request {
    // 用于挂在请求队列链表的节点,使用函数 blkdev_dequeue_request 访问它,而不是直接访问
    struct list_head queuelist;
    struct list_head donelist; /* 用于挂载已完成请求链表的节点 */
    struct request_queue *q; /* 指向请求队列 */
    unsigned int cmd_flags; /* 命令标识 */
    /* 各种各样的扇区计数 */
    /* 为提交 i/o 维护 bio 横断面的状态信息,hard_*成员是块层内部使用的,驱动程序不应该改变它们 */
    sector_t sector; /* 将提交的下一个扇区 */
    sector_t hard_sector; /* 将完成的下一个扇区 */
    unsigned long nr_sectors; /* 整个请求还需要传送的扇区数 */
    unsigned long hard_nr_sectors; /* 将完成的扇区数 */
    /* 在当前 bio 中还需要传送的扇区数 */
    unsigned int current_nr_sectors;
    /* 在当前段中将完成的扇区数 */
    unsigned int hard_cur_sectors;
    struct bio *bio; /* 请求中第一个未完成操作的 bio */
    struct bio *biotail; /* 请求链表中末尾的 bio */
    struct hlist_node hash; /* 融合 hash */
    /* rb_node 仅用在 I/O 调度器中,当请求被移到分发队列中时,请求将被删除。因此,让 completion_data 与 rb_node 分享空间 */
    union {
        struct rb_node rb_node; /* 排序/查找 */
        void *completion_data;
    }
};

request 结构体的主要成员包括:

sector_t hard_sector;

unsigned long hard_nr_sectors;

unsigned int hard_cur_sectors;

上述3个成员标识还未完成的扇区,hard_sector 是第1个尚未传输的扇区,hard_nr_sectors 是尚待完成的扇区数,hard_cur_sectors 是并且当前 I/O 操作中待完成的扇区数。这些成员只用于内核块设备层,驱动不应当使用它们。

sector_t hard_sector;

unsigned long hard_nr_sectors;

unsigned int current_nr_sectors;

驱动中会经常与这3个成员打交道,这3个成员在内核和驱动交互中发挥这重大作用。它们以512字节大小为1个扇区,如果硬件的扇区大小不是512字节,则需要进行相应的调整。例如,如果硬件的扇区大小是2048字节,则在进行硬件操作之前,需要用4来除起扇区号。

hard_sector、hard_nr_sectors、hard_cur_sectors与sector、nr_sectors、current_nr_sectors之间可认为是 “副本” 关系。

struct bio *bio;

bio 是这个请求中包含的 bio 结构体的链表,驱动中不宜直接存取这个成员,而应该使用后文将介绍的 rq_for_each_bio()。

 

请求队列结构 request_queue

每个块设备都有一个请求队列,每个请求队列单独执行 I/O 调度,请求队列是由请求结构实例链接成的双向链表,链表以及整个队列的信息用结构 request_queue 描述,称为请求队列对象结构或请求队列结构。它存放了关于挂起请求的信息以及管理请求队列(如:电梯算法)所需要的信息。结构成员 request_fn 是来自设备驱动程序的请求处理函数。

请求队列结构 request_queue 列出如下 (在 /include/linux/blk_dev.h 中)

太长了,此处略,其实也看不懂,- - #

Bio 结构

通常1个bio对应1个I/O请求,IO调度算法可将连续的bio合并成1个请求。所以,1个请求可以包含多个bio。

内核中块 I/O 操作的基本容器由 bio 结构体表示,定义在 <linux/bio.h> 中,该结构体代表了正在现场的(活动的)以片段(segment)链表形式组织的块 I/O 操作。一个片段是一小块连续的内存缓冲去。这样的好处就是不需要保证单个缓冲区一定要连续。所以通过片段来描述缓冲区,即使一个缓冲区分散在内存的多个位置上,bio 结构体也能对内核保证 I/O 操作的执行,这样的就叫做聚散 I/O。bio 为通用层的主要数据结构,既描述了磁盘的位置,又描述了内存的位置,是上层内核 vfs 与下层驱动的连接纽带

struct bio {
    sector_t bi_sector; //该bio结构所要传输的第一个(512字节)扇区:磁盘的位置
    struct bio *bi_next; //请求链表
    struct block_device *bi_bdev; //相关的块设备
    unsigned long bi_flags; //状态和命令标志
    unsigned long bi_rw; //读写
    unsigned short bi_vcnt; //bio_vesc 偏移的个数
    unsigned short bi_idx; //bi_io_vec 的当前索引
    unsigned short bi_phys_segments; //结合后的片段数目
    unsigned short bi_hw_segments; //重映射后的片段数目
    unsigned int bi_size; //I/O计数
    unsinged int bi_hw_front_size; //第一个可合并的段大小
    unsinged int bi_hw_back_size; //最后一个可合并的段大小
    unsigned int bi_max_vecs; //bio_vecs 数目上限
    struct bio_vec *bi_io_vec; //bio_vec 链表:内存的位置
    bio_end_io_t *bi_end_io; //I/O 完成方法
    atomic_t bi_cnt; //使用计数
    void *bi_private; //拥有者的私有方法
    bio_destructor_t *bi_destructor; //销毁方法
};

内存数据段结构 bio_vec

结构 bio_vec 代表了内存中一个数据段,数据段用页、偏移和长度描述。I/O 需要执行的内存位置用段表示,结构 bio 指向了一个段的数组。结构 bio_vec 列出如下(在 include/linux/bio.h 中):

块设备各个结构体间的关系

猜你喜欢

转载自blog.csdn.net/u014674293/article/details/104407609
今日推荐