Linux 块设备驱动分析

块设备:

IO设备中的一类,应用层对块设备进行读写时,按照扇区大小读写数据。对于IO请求有相应的缓冲区。

 

块设备结构:

段(segments):由若干个块组成,是Linux内存管理中一个内存页或内存页的一部分。

块(Blocks):由Linux制定对内核或文件系统等数据处理的基本单位,通常由一个或多个扇区组成。

扇区(Sectors):块设备的基本单位,一般默认为512字节

 

Linux块设备驱动结构:

1、block_device_operations结构体——块设备操作结构体

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(*unlocked_ioctl)(struct file *, unsigned, unsigned long);
  long(*compat_ioctl)(struct file *, unsigned, unsigned long);
  int(*direct_access)(struct block_device *, sector_t, unsigned long*);
  int(*media_changed)(struct gendisk*); //检查驱动器的介质是否已经改变,如果是,返回非0值,否则,返回0
  int(*revalidate_disk)(struct gendisk*); //使介质有效,响应一个介质改变
  int(*getgeo)(struct block_device *, struct hd_geometry*);//驱动器的几何信息填充hd_geometry结构体,包含磁头、扇区、柱面等信息
  struct module *owner; //模块拥有者
};

 

 

 

2、gendisk(通用磁盘)结构体 ——表示一个独立的磁盘设备(或分区)

struct gendisk
 {
 int major; //主设备号,同一磁盘的各个分区共享一个主设备号 
 int first_minor; /*第 1 个次设备号*/
 int minors; /* 最大的次设备数,如果不能分区,则为 1*/
 char disk_name[32]; /* 设备名称 */
 struct hd_struct **part; /* 磁盘上的分区信息 */
 struct block_device_operations *fops; /*块设备操作结构体*/
 struct request_queue *queue; /*管理设备IO设备请求队列的指针*/
 void *private_data; /*私有数据*/
 sector_t capacity; /*设备容量,扇区数, 512 字节为 1 个扇区*/
 int flags;
 char devfs_name[64];
 int number;
 struct device *driverfs_dev;
 struct kobject kobj;
 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
 };

 

gendisk结构体相关函数:

1、分配gendisk

gendisk结构体是动态分配的结构体,由特殊的内核操作来初始化,使用下列函数来分配gendisk;

struct gendisk * alloc_gendisk(int minors);

minors代表次设备号的数量,也是磁盘分区的数量,设置好之后不可更改

2、增加gendisk

分配好gendisk结构体之后,需要注册磁盘设备,使用下列函数:

void add_disk(struct gendisk *gd);

注意:add_disk()的调用发生在初始化工作完成并能响应磁盘的请求之后

3、释放gendisk

void del_gendisk(struct gendisk *gd);

4、gendisk引用计数

gendisk中包含一个kobject成员,通过get_disk()和put_disk()函数可用来操作引用计数。通常,del_gendisk()的调用会去掉gendisk的最终计数。

5、设置gendisk容量

void set_capacity(struct gendisk *disk,sector_t size);

set_capacity()以512字节为单位。

 

request与bio结构体

(1)请求 ——使用request结构体来表征等待进行的IO请求

struct request
 {
 struct list_head queuelist; /*链表结构*/
 unsigned long flags; /* REQ_ */

/*驱动使用*/
 sector_t sector; /* 要传送输的下一个扇区 */
 unsigned long nr_sectors; /*要传送的扇区数目*/
 unsigned int current_nr_sectors; /*当前要传送的扇区数目*/

/*内核块设备使用*/
 sector_t hard_sector; /*要完成的下一个扇区*/
 unsigned long hard_nr_sectors; /*要被完成的扇区数目*/
 unsigned int hard_cur_sectors;/*当前要被完成的扇区数目*/

 struct bio *bio; /*请求的 bio 结构体的链表*/
 struct bio *biotail; /*请求的 bio 结构体的链表尾*/
 void *elevator_private;
 unsigned short ioprio;
 int rq_status;
 struct gendisk *rq_disk;
 int errors;
 unsigned long start_time;

 unsigned short nr_phys_segments;
 /*请求在物理内存中占据的不连续的段的数目, scatter/gather 列表的尺寸*/
 unsigned short nr_hw_segments;
/*与 nr_phys_segments 相同,但考虑了系统 I/O MMU 的 remap */
 int tag;
 char *buffer; /*传送的缓冲,内核虚拟地址*/
 int ref_count; /* 引用计数 */
 };

(2)请求队列 —— 一个块请求队列是一个块IO请求的队列

struct request_queue
 {
 /* 保护队列结构体的自旋锁 */
 spinlock_t _ _queue_lock;
 spinlock_t *queue_lock;

 /* 队列 kobject */
 struct kobject 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;
 int node;

 struct list_head drain_list;
 struct request *flush_rq;
 unsigned char ordered;
 };

 请求队列跟踪等候的块I/O请求,存储描述这个设备的相关信息,请求队列操作函数:

 

1>初始化请求队列

request_queue_t *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock);

第一个参数为请求处理函数的指针,第二个参数为控制访问队列权相的自旋锁,需要检查函数返回值

2>清除请求队列

void blk_cleanup_queue(request_queue_t *q);

#define blk_put_queue(q) blk_cleanup_queue((q))

3>分配"请求队列"

request_queue_t *blk_alloc_queue(int gfp_mask);

使用如下函数来绑定请求队列和“制造请求”函数

void blk_queue_make_request(request_queue_t *q,make_request_fn *mfn);

4>提取请求

struct request *elv_next_request(request_queue_t *queue);

上述函数用于返回下一个要处理的请求,没有请求返回NULL.

5>去除请求

void blkdev_dequeue_request(struct request *req);

上述函数从队列中去除一个函数,如果需要将已经去除的请求归还到队列中,使用如下函数:

void elv_requeue_request(request_queue_t *queue,struct request *req);

驱动还具有如下可控制请求队列操作的函数:

6>启停请求队列

void blk_stop_queue(request_queue_t *queue);

void blk_start_queue(request_queue_t *queue);

7>参数设置

void blk_queue_max_sectors(request_queue_t *queue, unsigned short max);

void blk_queue_max_phys_segments(request_queue_t *queue, unsigned short max);

void blk_queue_max_hw_segments(request_queue_t *queue, unsigned short max);

void blk_queue_max_segment_size(request_queue_t *queue, unsigned int max);

(3)块IO —— 一个bio对应一个I/O请求

struct bio
 {
 sector_t bi_sector; /* 要传输的第一个扇区 */
 struct bio *bi_next; /* 下一个 bio */
 struct block_device *bi_bdev;
 unsigned long bi_flags;
/* 状态,命令,如果为写请求,最低有效位被置位,使用	bio_data_dir(bio)宏获取读写方向 */

 unsigned long bi_rw; /*低位表示READ/WRITE,高位表示优先级*/

 unsigned short bi_vcnt; /* bio_vec 数量 */
 unsigned short bi_idx; /* 当前 bvl_vec 索引 */

 unsigned short bi_phys_segments; /*不相邻的物理段的数目*/
 unsigned short bi_hw_segments;
 /*物理合并和 DMA remap 合并后不相邻的物理段的数目*/

 unsigned int bi_size;
/* 以字节为单位所需传输的数据大小 ,驱动中使用bio_sectors(bio)宏获得以扇区为单位的大小*/

 unsigned int bi_hw_front_size;
 unsigned int bi_hw_back_size;
 /* 为了明了最大的 hw 尺寸,我们考虑这个 bio 中第一个和最后一个虚拟的可合并的段的尺寸 */

 unsigned int bi_max_vecs; /* 我们能持有的最大bvl_vecs数 */

 struct bio_vec *bi_io_vec; /* 实际的 vec 列表 */

 bio_end_io_t *bi_end_io;
 atomic_t bi_cnt;
 void *bi_private;
 bio_destructor_t *bi_destructor; /* destructor */
 };

 

其中,bio_sec结构体的定义如下:

struct bio_vec
{
 struct page *bv_page; /* 页指针 */
 unsigned int bv_len; /* 传输的字节数 */
 unsigned int bv_offset; /* 偏移位置 */
 };

 

 

 

 bio操作函数(宏):

struct page *bio_page(struct bio *bio) ;

这个函数可用于获得目前的页指针

int bio_offset(struct bio *bio) ;

返回操作对应的当前页内的偏移,通常块I/O操作本身就是页对齐的。

int bio_cur_sectors(struct bio *bio) ;

这个函数返回当前 bio_vec 要传输的扇区数

char *bio_data(struct bio *bio) ;

这个函数返回数据缓冲区的内核虚拟地址

 

Linux块设备驱动的模块加载函数:

模块加载函数中需要完成以下工作:

① 分配、初始化请求队列,绑定请求队列和请求函数。

② 分配、初始化 gendisk,给 gendisk 的 major、 fops、 queue 等成员赋值,最后添加 gendisk。

③ 注册块设备驱动

 

块设备驱动模块加载函数模板:

 static int _ _init xxx_init(void)
 {
 //分配 gendisk
 xxx_disks = alloc_disk(1);
 if (!xxx_disks)
 {
 goto out;
 }

 //块设备驱动注册
 if (register_blkdev(XXX_MAJOR, "xxx"))
 {
 err = - EIO;
 goto out;
 }

 //“请求队列”分配
 xxx_queue = blk_alloc_queue(GFP_KERNEL);
 if (!xxx_queue)
 {
 goto out_queue;
 }

 blk_queue_make_request(xxx_queue,&xxx_make_request); 		//绑定“制造请求”函数
 blk_queue_hardsect_size(xxx_queue,xxx_blocksize); //硬件扇区尺寸设置

 //gendisk 初始化
 xxx_disks->major = XXX_MAJOR;
 xxx_disks->first_minor = 0;
 xxx_disks->fops = &xxx_op;
 xxx_disks->queue = xxx_queue;
 sprintf(xxx_disks->disk_name, "xxx%d", i);
 set_capacity(xxx_disks, xxx_size); // 以512bytes 为单位
 add_disk(xxx_disks); //添加 gendisk

 return 0;
 out_queue: unregister_blkdev(XXX_MAJOR, "xxx");
 out: put_disk(xxx_disks);
 blk_cleanup_queue(xxx_queue);

 return - ENOMEM;
 }

 

 

 

Linux块设备驱动的I/O请求处理

块设备驱动请求处理函数的原型为:

void request(request_queue_t *queue);

将请求从请求队列中剥离:

void end_request(struct request *req, int uptodate);

当设备已经完成一个I/O请求的部分或全部扇区传输后,使用end_that_request_first()通告块设备层,其原型为:

int end_that_request_first(struct request *req, int success, int count);

返回值是一个标志位,指示是否请求的所有扇区已经被传送,返回值为0表示所有的扇区已经被传送并且请求完成,之后,使用blkdev_dequeue_request()将请求从请求队列中清除,最后将请求传递给end_that_request_last()函数,其原型为:

void end_that_request_last(struct request *req);

通知所有正在等待这个请求完成的对象请求已经完成并回收这个请求结构体。

 

请求函数遍历请求、bio和段

 

static void xxx_full_request(request_queue_t *q)
 {
 struct request *req;
 int sectors_xferred;
 struct xxx_dev *dev = q->queuedata;
 /* 遍历每个请求 */
 while ((req = elv_next_request(q)) != NULL)
 {
 if (!blk_fs_request(req))
 {
 printk(KERN_NOTICE "Skip non-fs request\n");

 end_request(req, 0);
 continue;
 }
 sectors_xferred = xxx_xfer_request(dev, req);
 if (!end_that_request_first(req, 1, sectors_xferred))
 {
 blkdev_dequeue_request(req);
 end_that_request_last(req);
 }
 }
 }
 /* 请求处理 */
 static int xxx_xfer_request(struct xxx_dev *dev, struct request *req)
 {
 struct bio *bio;
 int nsect = 0;
 /* 遍历请求中的每个 bio */
 rq_for_each_bio(bio, req)
 {
 xxx_xfer_bio(dev, bio);
 nsect += bio->bi_size / KERNEL_SECTOR_SIZE;
 }
 return nsect;
 }
 /* bio 处理 */
 static int xxx_xfer_bio(struct xxx_dev *dev, struct bio *bio)
 {
 int i;
 struct bio_vec *bvec;
 sector_t sector = bio->bi_sector;

 /* 遍历每一段 */
 bio_for_each_segment(bvec, bio, i)
 {
 char *buffer = _ _bio_kmap_atomic(bio, i, KM_USER0);
 xxx_transfer(dev, sector, bio_cur_sectors(bio), buffer, bio_data_dir(bio)
 == WRITE);
 sector += bio_cur_sectors(bio);
 _ _bio_kunmap_atomic(bio, KM_USER0);
 }
 return 0;
 }

 

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值