一、块设备与字符设备的I/O操作比较
n 块设备只能以块为单位接受输入和返回输出。字符设备则以字节为单位。(linux中的块可以是字节)
n 块设备对于I/O请求有缓冲区。可以对读写的顺序进行调整。字符设备只能顺序读写。
二、相关概念与重要结构
n 块设备处理的I/O请求用request结构体表征。由于此结构体过于庞大,只列出部分重要成员。
struct request {
……
sector_t sector;
unsigned long nr_sectors;
struct bio *bio;
struct gendisk *rq_disk;
char *buffer;
unsigned char *cmd;
……
}
sector,I/O请求中要处理的扇区号
sectors,要处理的扇区数目
rq_disk,操作对应的磁盘指针
buffer,读写缓冲区
cmd,可以控制语法方向
bio,I/O调度算法可以将连续的bio合并成一个request。request是bio经由块层调整后的结果。
n 请求队列request_uqueue
n 上层传递给块层的I/O请求bio
n 块设备操作集合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);
long (*unlocked_ioctl) (struct file *, unsigned, unsigned long);
long (*compat_ioctl) (struct file *, unsigned, unsigned long);
int (*direct_access) (struct block_device *, sector_t,
void **, unsigned long *);
int (*media_changed) (struct gendisk *);
int (*revalidate_disk) (struct gendisk *);
int (*getgeo)(struct block_device *, struct hd_geometry *);
struct module *owner;
};
open和release方法通常在块设备挂载、卸载文件系统到相应目录时调用。
ioctl,一些设备的特殊控制请求
media_changed,介质改变
revalidate_disk,使介质有效
getgeo,获得驱动器信息
n gendisk结构体
gendisk结构体用来表示一个独立的磁盘设备
struct gendisk {
int major; /* major number of driver */
int first_minor;
int minors; /* maximum number of minors, =1 for
* disks that can't be partitioned. */
char disk_name[32]; /* name of major driver */
struct hd_struct **part; /* [indexed by minor] */
struct block_device_operations *fops;
struct request_queue *queue;
void *private_data;
sector_t capacity;
……
};
part,设备分区
fops,块设备操作集合
private_data,一般会放置设备的结构体。
三、结构体相关操作
n gendisk
1. 分配gendisk
struct gendisk *alloc_disk(int minors);
minors为磁盘分区的数量
2. 增加gendisk
void add_disk(struct gendisk *disk);
初始化工作完成后,并能响应磁盘请求时,才用此函数来注册设备
3. 释放gendisk
void del_gendisk(struct gendisk *gp);
4. gendisk引用计数
struct kobject *get_disk(struct gendisk *disk);
void put_disk(struct gendisk *disk);
n request
1.遍历request中的bio
__rq_for_each_bio(struct bio *_bio,struct request *rq)
#define __rq_for_each_bio(_bio, rq) \
if ((rq->bio)) \
for (_bio = (rq)->bio; _bio; _bio = _bio->bi_next)
_bio是rq的一个结构体成员
2.request传送的数据方向
rq_data_dir(struct request *req);
n request_queue
1.初始化请求队列
request_queue_t *blk_init_queue(request_fn_proc *rfn,spinlock_t *lock);
2.清除请求队列
void blk_cleanup_queue(request_queue_t *q);
3.分配“请求队列”
request_queue_t *blk_alloc_queue(int gfp_mask);
4.绑定请求队列和“制造请求”函数
void blk_queue_make_request(request_queue_t *q,make_request_fn *mfn);
5. 提取请求
struct request *elv_next_request(struct request_queue *q);
6. 去除请求
void blkdev_dequeue_request(struct request *req);
n bio
1. 遍历bio中的每个段
#define bio_for_each_segment(bvl, bio, i)
__bio_for_each_segment(bvl, bio, i, (bio)->bi_idx)
__bio_for_each_segment(struct bio_vec *bvl, struct bio *bio, i,start_idx)
bvl, 由bvl=bio_iovec_idx(bio,start_idx)转化而来,意思为将bio结构体中,成员bio_io_vec[start_idx]的值取出。值的结构即struct bio_vec。
start_idx,当前bvl_vec的索引 (bio)->bi_idx
i,一个符号整形的变量。
2. 结合request中的__rq_for_each_bio()函数,遍历一个request中的segment(段)函数
#define rq_for_each_segment(bvl, _rq, _iter) \
__rq_for_each_bio(_iter.bio, _rq) \
bio_for_each_segment(bvl, _iter.bio, _iter.i)
_iter,结构为struct req_iterator
n 块设备
1.注册
int register_blkdev(unsigned int major,const char *name);
2.注销
int unregister_blkdev(unsigned int major,const char *name);
register_blkdev,完成的两件事情
a.如果需要,分配一个动态设备号
b.在/proc/devices中创建一个入口
四、说明与使用
request_queue队列中存储了多个request,I/O调度器可以合并邻近request,排序request,使其高效率的被响应。每个request里面有多个来自上层的bio请求。每个bio中,又有多个bio_vec数组成员。每个bio_vec成员 记录了要访问的页的确实位置。
块设备的驱动程序编写分为以下两种:
使用请求(request)
不使用请求
使用请求对于一个机械的磁盘设备而言,有助于提高系统的性能。对于如数码相机的存在卡,RAM盘等完全可真正随机访问的设备而言,使用请求队列是无益的。在这种情况下,驱动必须提供一个“制造请求”函数。
块设备加载函数模板(无使用请求)
static int __int xxx_init(void)
{
//申请磁盘,注册块设备。它们是通过初始化磁盘联系到一起的
xxx_disks = alloc_disk(1);
if(!xxx_disk)
goto out;
if(register_blkdev(XXX_MAJOR,”xxx”)){
err=-EIO;
goto out;
}
/*申请请求求队列(request_queue),因为请求队列中无请求,所以要与“制造请求函数绑定”*/
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);
//磁盘初始化
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*2);
add_disk(xxx_disks);
return 0;
out_queue:unregister_blkdev(XXX_MAJOR,”xxx”);
out:put_disk(xxx_disks);
blk_cleanup_queue(xxx_queue);
return –ENOMEM;
}
若是使用请求,只需要将申请请求队列和绑定“制造请求”函数用以下语句替代
xxx_queue = blk_init_queue(xxx_request,xxx_lock);
if(!xxx_queue)
goto out_queue;
卸载函数模板
static void __exit xxx_exit(void)
{
if(bdev){
invalidate_bdev(xxx_bdev,1);
blkdev_put(xxx_bdev);
}
del_gendisk(xxx_disks);
put_disk(xxx_disks);
blk_cleanup_queue(xxx_queue[i]);
unregister_blkdev(XXX_MAJOR, “xxx”);
}
块设备的打开与释放
static int xxx_open(struct block_device *bdev,fmode_t mode)
{
struct xxx_dev *dev = bdev->bd_disk->private_data;
……
return 0;
}
static int xxx_release(struct gendisk *disk,fmode_t mode)
{
struct xxx_dev *dev = disk->private_data;
……
return 0;
}