块设备驱动之二
来源:互联网 发布:查股指的历年数据 编辑:程序博客网 时间:2024/06/08 04:46
一、将块设备添加到系统
register_blkdev并没有真正将设备添加到系统中,想要将设备添加到系统中,需要使用如下API:void blk_register_region(dev_t devt, unsigned long range, struct module *module, struct kobject *(*probe)(dev_t, int *, void *), int (*lock)(dev_t, void *), void *data)该函数会将块设备添加到bdev_map中,这是一个由内核维护的数据库,包含了系统所有的块设备。在打开块设备时,必然会调用blkdev_get,而blkdev_get会查询该数据库来获取块设备,这个过程类似于字符设备,字符设备在内核中也有一个数据库cdev_map,在打开字符设备时会查询cdev_map。不过很少需要直接调用该函数,add_disk会自动调用该函数。
1.1 添加磁盘和分区到系统中
为了将一个磁盘添加到系统中,对系统可用,必须初始化磁盘数据结构并调用add_disk方法。需要特别注意的是一旦调用了add_disk,磁盘就被“激活”了,系统随时都可能会调用该磁盘提供的各种方法,甚至在该函数返回之前就会调用,因而在完成磁盘结构的初始化之前,不要调用add_disk。add_disk的原型如下:void add_disk(struct gendisk *disk);
它完成的工作主要包括:
- 根据磁盘的主次设备号信息为磁盘分配设备号
- 调用disk_alloc_events初始化磁盘的事件(alloc|add|del|release)处理机制。在最开始磁盘事件会被设置为被阻塞的。
- 调用bdi_register_dev将磁盘注册到bdi
- 调用blk_register_region将磁盘添加到bdev_map中
- 调用register_disk将磁盘添加到系统中。主要完成
- 将主设备的分区(第0个分区)信息标记设置为分区无效
- 调用device_add将设备添加到系统中
- 在sys文件系统中为设备及其属性创建目录及文件
- 发出设备添加到系统的uevent事件(如果能获取分区的信息,则也为分区发送uevent事件)。
- 调用blk_register_queue注册磁盘的请求队列。主要是为队列和队列的调度器在设备的sys文件系统目录中创建相应的sys目录/文件,并且发出uevent事件。
- 调用__disk_unblock_events完成
- 在/sys文件系统的设备目录下创建磁盘的事件属性文件
- 将磁盘事件添加到全局链表disk_events中
- 解除对磁盘事件的阻塞。
当扫描到一个分区时,需要将它添加到磁盘中,这是通过以下API实现的:
struct hd_struct *add_partition(struct gendisk *disk, int partno,sector_t start, sector_t len, int flags,struct partition_meta_info *info);
- disk:分区所属的磁盘
- partno:分区在磁盘的分区号
- start:起始扇区号
- len:该分区包括多少个扇区
- flags:该扇区的标志
- info:该分区的partition_meta_info信息
- 扩展磁盘的分区表
- 分配分区数据结构并进行初始化
- 调用device_initialize初始化分区的设备数据结构
- 设置分区的设备号
- 调用device_add将分区添加到系统中
- 创建分区设备相关的sys文件系统文件
- 发送添加分区的uevent事件
- 初始化分区的引用计数
二、块设备操作
2.1 打开块设备
在所有的文件系统的实现中,在获取文件的inode时,对于不是常规文件、目录文件、连接文件的特殊文件都会调用init_special_inode,该函数的代码在学习字符设备时已经贴出来过,对于块设备文件,该函数会将inode的文件操作函数结构设置为def_blk_fops,其中的打开文件函数为blkdev_open。其原型为:int blkdev_open(struct inode * inode, struct file * filp);参数的含义很明显。它完成的工作有:
- 调用bd_acquire获取块设备文件的block_device结构。该函数会调用bdget尝试从bdev文件系统中查找设备文件对应的inode,如果有就直接返回,如果没有会分配一个新的inode并且初始化该inode再返回。设备文件的inode会被添加到block_device的bd_inodes链表中。块设备对应的block_device也会在这一步被添加到全局的all_bdevs中。
- 设置file结构的f_mapping为bdev->bd_inode->i_mapping。bdev->bd_inode在inode的创建和初始化中北初始化,具体的函数为alloc_inode和bdget。其中的address_space_operations被设置为def_blk_aops,这是后续要用到的函数,这是和设备交互的接口。
- 调用blkdev_get。该函数最主要的工作时完成块设备的打开动作,同时根据传入的模式还可能声明设备的持有者。
- 调用get_gendisk获取块设备所对应的通用磁盘结构,这里可能需要查询bdev_map数据库。
- 阻塞磁盘的事件处理
- 如果是第一次打开该块设备,则
- 填充块设备数据结构的bd_disk,bd_queue,bd_contains(它会设置为自身)
- 如果是主设备(即不是分区),则
- 设置块设备数据结构的bd_part
- 如果提供了disk->fops->open,则调用它
- 如果分区无效,则调用rescan_partitions重新扫描分区
- 如果打开设备时返回了ENOMEDIUM错误,则调用invalidate_partitions将所有分区设置为无效
- 否则,如果是分区设备,则
- 获取主设备的块设备数据结构
- 递归调用__blkdev_get,但是这次传入的是主设备的块设备数据结构。本次调用会走第一次打开设备并且是主设备的分支,由于是第一次打开,因而分区信息应该是无效的,这就会走到重新扫描分区的分支。
- 设置块设备数据结构的bd_contains(它被设置为主设备的block_device),bd_part
- 调用bd_set_size设置分区的大小信息
- 否则如果不是第一次打开设备,则
- 如果是主设备(这里是通过bdev->bd_contains == bdev判断的,因为根据该函数的前边流程,只有主设备的这个条件才能成立),则
- 如果提供了disk->fops->open,则调用它
- 如果分区无效,则调用rescan_partitions重新扫描分区
- 如果打开设备时返回了ENOMEDIUM错误,则调用invalidate_partitions将所有分区设置为无效
- 如果是主设备(这里是通过bdev->bd_contains == bdev判断的,因为根据该函数的前边流程,只有主设备的这个条件才能成立),则
- 增加设备的打开计数
- 解除对设备事件的阻塞
2.2 读写操作
在打开块设备文件后,块设备文件的操作函数集也被设置为def_blk_fops,随后即可用其中的函数进行读写。其读函数为do_sync_read,写函数为do_sync_write,但是它们最终分别调用generic_file_aio_read和blkdev_aio_write来完成实际的读写操作。generic_file_aio_read的原型为:
ssize_t generic_file_aio_read(struct kiocb *iocb, const struct iovec *iov, unsigned long nr_segs, loff_t pos);
- iocb:内核I/O控制块
- iov:I/O请求向量
- nr_segs:I/O请求向量中有多少个请求
- pos:当前文件位置
- 如果是直接IO,则调用filp->f_mapping->a_ops->direct_IO进行直接IO。在open时已经将filp->f_mapping->a_ops设置def_blk_aops了。
- 对于请求向量中的每一个请求,创建一个read_descriptor_t并调用do_generic_file_read进行处理
ssize_t blkdev_aio_write(struct kiocb *iocb, const struct iovec *iov, unsigned long nr_segs, loff_t pos);其参数和读的类似,其处理流程为:
- 调用__generic_file_aio_write进行处理。该函数也会分别对待直接IO和常规的写。流程和读类似。
无论是读写都是和缓冲区交互,缓冲区位于文件数据结构的struct address_space类型的变量f_mapping中,并以radix树的形式被管理。内核在合适的时机会向设备发起实际的IO操作,这是通过文件数据结构的struct address_space类型的成员变量f_mapping中的address_space_operations类型的成员中的函数来实现的,在打开块设备文件时,该成员被设置为了def_blk_aops。对于读会调用该地址空间操作集的readpage(对于块设备readpage成员函数为blkdev_readpage)成员函数或者其它读成员函数,对于写会调用该地址空间操作集的writepage(对于块设备为blkdev_writepage)成员函数或者其它相关成员函数函数。在def_blk_aops提供的这些函数中会将读写转变成IO请求提交给设备,到了此时才真正是要和设备进行数据交换。
因此对于块设备来说,用户是和缓冲区交互(直接IO除外),而内核负责在合适的时机完成缓冲区和设备之间的交互。操作缓冲区的函数,缓冲区本身,以及缓冲区与设备之间的交互方式都保存在file结构中。
2.3 请求结构
当内核通过address_space_operations中的成员函数向设备发起读写操作时,读写操作都会被转变成一个对块设备的IO请求提交给设备。内核使用数据结构struct bio来表示一个对块设备的IO,其定义如下:struct bio {sector_tbi_sector;/* device address in 512 byte sectors */struct bio*bi_next;/* request queue link */struct block_device*bi_bdev;unsigned longbi_flags;/* status, command, etc */unsigned longbi_rw;/* bottom bits READ/WRITE, * top bits priority */unsigned shortbi_vcnt;/* how many bio_vec's */unsigned shortbi_idx;/* current index into bvl_vec *//* Number of segments in this BIO after * physical address coalescing is performed. */unsigned intbi_phys_segments;unsigned intbi_size;/* residual I/O count *//* * To keep track of the max segment size, we account for the * sizes of the first and last mergeable segments in this bio. */unsigned intbi_seg_front_size;unsigned intbi_seg_back_size;unsigned intbi_max_vecs;/* max bvl_vecs we can hold */atomic_tbi_cnt;/* pin count */struct bio_vec*bi_io_vec;/* the actual vec list */bio_end_io_t*bi_end_io;void*bi_private;#if defined(CONFIG_BLK_DEV_INTEGRITY)struct bio_integrity_payload *bi_integrity; /* data integrity */#endifbio_destructor_t*bi_destructor;/* destructor *//* * We can inline a number of vecs at the end of the bio, to avoid * double allocations for a small number of bio_vecs. This member * MUST obviously be kept at the very end of the bio. */struct bio_vecbi_inline_vecs[0];};关键域及其含义:
- bi_sector:传输开始的扇区号
- bi_next:将与一个请求相关的bio连接到同一个链表中
- bi_bdev:与请求相关联的设备的数据结构
- bi_phys_segments:在经过合并之后,该BIO所对应的的段数目
- bi_size:该BIO涉及到的数据的长度
- bi_io_vec:指向了包含了实际的IO数据结构的数组。
- bi_end_io:当IO完成时,将被调用用于完成此次IO
- bi_destructor:析构函数,当从内存删除一个BIO结构时被调用
bi_io_vec的每个数组项都指向一个内存页,这个内存页用于从设备读取数据或者向设备传输数据。这些内存页可以是连续的也可以不是连续的。其结构如图所示:
BIO是内核用于表示一个IO请求的结构,它会被提交给设备,当需要和设备交互时,内核会先准备BIO结构,然后通过目标设备的请求队列上的make_request_fn函数将BIO转变成一个请求,内核使用数据结构struct request来表示一个对块设备的请求,其数据结构定义如下:
struct request {struct list_head queuelist;struct call_single_data csd;struct request_queue *q;unsigned int cmd_flags;enum rq_cmd_type_bits cmd_type;unsigned long atomic_flags;int cpu;/* the following two fields are internal, NEVER access directly */unsigned int __data_len;/* total data len */sector_t __sector;/* sector cursor */struct bio *bio;struct bio *biotail;struct hlist_node hash;/* merge hash *//* * The rb_node is only used inside the io scheduler, requests * are pruned when moved to the dispatch queue. So let the * completion_data share space with the rb_node. */union {struct rb_node rb_node;/* sort/lookup */void *completion_data;};/* * Three pointers are available for the IO schedulers, if they need * more they have to dynamically allocate it. Flush requests are * never put on the IO scheduler. So let the flush fields share * space with the elevator data. */union {struct {struct io_cq*icq;void*priv[2];} elv;struct {unsigned intseq;struct list_headlist;rq_end_io_fn*saved_end_io;} flush;};struct gendisk *rq_disk;struct hd_struct *part;unsigned long start_time;#ifdef CONFIG_BLK_CGROUPunsigned long long start_time_ns;unsigned long long io_start_time_ns; /* when passed to hardware */#endif/* Number of scatter-gather DMA addr+len pairs after * physical address coalescing is performed. */unsigned short nr_phys_segments;#if defined(CONFIG_BLK_DEV_INTEGRITY)unsigned short nr_integrity_segments;#endifunsigned short ioprio;int ref_count;void *special;/* opaque pointer available for LLD use */char *buffer;/* kaddr of the current segment if available */int tag;int errors;/* * when request is used as a packet command carrier */unsigned char __cmd[BLK_MAX_CDB];unsigned char *cmd;unsigned short cmd_len;unsigned int extra_len;/* length of alignment and padding */unsigned int sense_len;unsigned int resid_len;/* residual count */void *sense;unsigned long deadline;struct list_head timeout_list;unsigned int timeout;int retries;/* * completion callback. */rq_end_io_fn *end_io;void *end_io_data;/* for bidi */struct request *next_rq;};
- queuelist:用于将请求连接到请求队列上
- q:请求所属的请求队列
- cmd_flags:请求的标志
- cmd_type:请求的类型
- bio:该请求的多个bio中当前正被处理的bio
- biotail:该请求的最后一个bio。一个请求上的所有BIO会保存在一个链表中。
- __data_len:请求所涉及到的数据的总长度
- __sector:扇区游标
- elv:IO调度器相关信息。
- rq_disk:请求对应的磁盘
- part:请求所对应的磁盘分区
- end_io:该请求被完成时被调用,用于完成该请求
- end_io_data:回调end_io时的参数
请求所支持的标志及其含义如下:
enum rq_flag_bits {/* common flags */__REQ_WRITE,/* not set, read. set, write */__REQ_FAILFAST_DEV,/* no driver retries of device errors */__REQ_FAILFAST_TRANSPORT, /* no driver retries of transport errors */__REQ_FAILFAST_DRIVER,/* no driver retries of driver errors */__REQ_SYNC,/* request is sync (sync write or read) */__REQ_META,/* metadata io request */__REQ_PRIO,/* boost priority in cfq */__REQ_DISCARD,/* request to discard sectors */__REQ_SECURE,/* secure discard (used with __REQ_DISCARD) */__REQ_NOIDLE,/* don't anticipate more IO after this one */__REQ_FUA,/* forced unit access */__REQ_FLUSH,/* request for cache flush *//* bio only flags */__REQ_RAHEAD,/* read ahead, can fail anytime */__REQ_THROTTLED,/* This bio has already been subjected to * throttling rules. Don't do it again. *//* request only flags */__REQ_SORTED,/* elevator knows about this request */__REQ_SOFTBARRIER,/* may not be passed by ioscheduler */__REQ_NOMERGE,/* don't touch this for merging */__REQ_STARTED,/* drive already may have started this one */__REQ_DONTPREP,/* don't call prep for this one */__REQ_QUEUED,/* uses queueing */__REQ_ELVPRIV,/* elevator private data attached */__REQ_FAILED,/* set if the request failed */__REQ_QUIET,/* don't worry about errors */__REQ_PREEMPT,/* set for "ide_preempt" requests */__REQ_ALLOCED,/* request came from our alloc pool */__REQ_COPY_USER,/* contains copies of user pages */__REQ_FLUSH_SEQ,/* request for flush sequence */__REQ_IO_STAT,/* account I/O stat */__REQ_MIXED_MERGE,/* merge of different types, fail separately */__REQ_NR_BITS,/* stops here */};请求的类型及其含义如下:
enum rq_cmd_type_bits {REQ_TYPE_FS= 1,/* fs request */REQ_TYPE_BLOCK_PC,/* scsi command */REQ_TYPE_SENSE,/* sense request */REQ_TYPE_PM_SUSPEND,/* suspend request */REQ_TYPE_PM_RESUME,/* resume request */REQ_TYPE_PM_SHUTDOWN,/* shutdown request */REQ_TYPE_SPECIAL,/* driver defined type *//* * for ATA/ATAPI devices. this really doesn't belong here, ide should * use REQ_TYPE_SPECIAL and use rq->cmd[0] with the range of driver * private REQ_LB opcodes to differentiate what type of request this is */REQ_TYPE_ATA_TASKFILE,REQ_TYPE_ATA_PC,};
2.4 提交请求
当内核需要和设备进行交互时,它都会首先准备相关的bio,然后调用submit_bio将bio提交给设备。该函数最终会调用设备相关连的请求队列上的make_request_fn函数将BIO转变成一个请求,其处理逻辑很简单:- 更新统计信息
- 调用generic_make_request提交bio
- 做合法性检查
- 如果current->bio_list不为NULL,则将新的bio添加到current->bio_list上并返回
- 将current->bio_list 初始化为 &bio_list_on_stack
- 获取所请求设备的请求队列
- 调用请求队列上的make_request_fn产生一个请求
- 如果current->bio_list不为空,就回到第四步
- 将current->bio_list设置为NULL
如果没有修改过队列的make_request_fn,则它使用内核提供的默认版本blk_queue_bio。
blk_queue_bio的大致处理流程:
- 调用blk_queue_bounce进行一些特殊处理(如果底层驱动表示它想要将在某个限制之上的页地址回弹到低地址)
- 调用attempt_plug_merge尝试将新的请求同已经被plugged的请求进行合并,已经被plugged的请求会被保存在current->plug链表中
- 调用elv_merge判断新的bio是否可以同请求队列上已经存在的请求的bio进行合并,如果可以合并,就进行合并。这里的是否可以合并以及如何合并都取决于所采用的IO调度算法。
- 走到这一步就说明无法进行合并,开始创建一个新的请求,调用get_request_wait来获取一个新的请求结构
- 调用init_request_from_bio来使用bio中的数据来初始化这个新的请求。
- 如果current->plug不空,则表示当前队列是plug的,如果该链表上已经有足够数目的请求,则调用blk_flush_plug_list进行处理(该函数会调用__elv_add_request将 请求添加到请求队列,还可能调用queue_unplugged进行实际的请求处理),最后会将新的请求添加到current->plug上并更新统计信息。
- 如果current->plug为空,则调用__blk_run_queue直接处理请求,这会调用请求队列上的request_fn,也就是要求驱动必须提供的那个函数来进行请求的处理。
到了这一步,IO的读写已经被提交给了硬件,由驱动所提供的request_fn进行处理。
0 0
- 块设备驱动之二
- Android 块设备驱动之二
- Linux 驱动之块设备结构体 (二)
- Linux设备驱动之块设备驱动
- linux之块设备驱动
- 驱动之路-块设备驱动
- 块设备驱动之NOR FLASH驱动
- linux驱动之块设备驱动框架
- Linux设备驱动--块设备(二)之相关结构体
- Linux设备驱动--块设备(二)之相关结构体
- Linux设备驱动--块设备(二)之相关结构体
- Linux设备驱动--块设备(二)之相关结构体
- Linux设备驱动--块设备(二)之相关结构体
- LINUX之块设备整理(二) EMMC 驱动,emmc驱动
- 设备驱动之二----字符设备驱动
- 乾坤合一~Linux设备驱动之块设备驱动
- 乾坤合一:Linux设备驱动之块设备驱动
- 块设备驱动应用之文件读写
- —cleanup_rollback_entries
- KMP——Period
- JS技巧归纳
- IntelliJ IDEA 12 创建Web项目 教程 超详细版
- SVN最简单的迁移方法
- 块设备驱动之二
- error target id is not valid use android list targets to get the target ids
- oracle官方文档之和体系结构相关的文档
- easy ui 简单使用
- [原创]网址记录
- NYOJ 599 奋斗的小蜗牛
- 简单的echo服务器与客户端的实现
- java利用正则表达式判断是否是浮点数
- Eclipse中的maven插件初步使用心得