一、块设备简介
1>块设备
块设备将数据存储在固定大小的块中,每个块的大小通常为512字节到32768字节之间。磁盘、SD卡都是常见的块设备。
2>块设备&字符设备
块设备和字符设备最大的区别在于读写数据的基本单元不同。块设备读写数据的基本单元为块,例如磁盘通常为一个sector,而字符设备的基本单元为字节。
块设备能够随机访问,而字符设备则只能顺序访问。
3>体系架构
VFS:对各种具体文件系统的一种封装,为用户程序访问文件提供统一的接口。
Disk Cache:
当用户发起文件访问请求的时候,首先会到Disk Cache中寻找文件是否被缓存了,如果在cache中,则直
接从cache中读取。如果数据不在缓存中,就必须要到具体的文件系统中读取数据了。
mapping layer:首先确定文件系统的block size,然后计算所请求的数据包含多少个block。调用具体文件
系统的函数来访问文件的inode,确定所请求的数据再磁盘上的逻辑块地址。
generic block layer:Linux内核为块设备抽象了统一的模型,把块设备看作是由若干个扇区组成的数据
空间。上层的读写请求在通用块层(generic block layer)被构造成一个或多个bio结构。
I/O scheduler layer:I/O调度层负责将I/O操作进行排序,采用某种算法(如:电梯调度算法)来高效
地处理操作。
电梯调度算法原则:如果电梯现在朝上运动,如果当前楼层的上下方都有请求,则先响应所有上方的请求,然后向
下响应下方的请求;如果电梯向下运动,则相反。
block device driver:块设备驱动程序通过发送命令给磁盘控制器实现真正的数据传输。
二、块设备驱动程序的设计
1>设备描述
Linux内核使用struct gendisk(定义于)来描述块设备。
struct gendisk{
int major;
int first_minor;
int minor;
char disk_name[DISK_NAME_LEN];
struct block_device_operation *fops;
struct request_queue *queue;
..........................
int node_id;
};
2>设备注册
Linux内核使用add_disk函数想内核注册块设备。
void add_disk(struct gendisk *gd)
3>设备操作
字符设备通过file_operations结构来定义使它所支持的操作,块设备使用一个类似的结构:
struct_device_operations{
int(*open)(struct block_device*,fmode_t);
int(*release)(struct gendisk*,fmode_t);
int(*ioctl)(struct block_device*,fmode_t,unsigned,unsigned long);
.....................................
} ;
4>IO请求
在Linux内核中,使用struct request来表示等待处理的块设备IO请求
struct request
{
struct list_head queuelist;//链表结构
sector_t sector;//要操作的首个扇区
unsigned long nr_sector;//要操作的扇区数目
struct bio *bio;//请求的bio结构体的链表
struct bio *biotail;//请求的bio结构体的链表尾
..............................................
};
5 >请求队列
简单的讲,请求队列就是IO请求request所形成的队列,在Linux内核中struct request_queue描述。
内核提供了一系列函数用来操作请求队列:
struct request_queue *blk_init_queue(request_fn_proc *rfn,spinlock_t *lock)
初始化请求队列,一般在块设备驱动的模块加载函数中调用。
void blk_clean_queue(request_queue_t* q)
清除请求队列,这个函数完成将请求队列返回给系统的任务,一般在块设备驱动模块卸载函数中调用。
struct request *elv_next_request(request_queue_t *queue)
返回下一个要处理的请求(由IO调度器决定),如果没有请求则返回NULL。elv_next_request()不会清除请求,它任然将这个请求保留在队列上,因此连续调用它2次,2次会返回同一个请求结构体。
void blkdev_dequeue_request(struct request *req)
从队列中删除一个请求。
实例代码:
-
#include <linux/module.h>
-
#include <linux/moduleparam.h>
-
#include <linux/init.h>
-
#include <linux/sched.h>
-
#include <linux/kernel.h> /* printk() */
-
#include <linux/slab.h> /* kmalloc() */
-
#include <linux/fs.h> /* everything... */
-
#include <linux/errno.h> /* error codes */
-
#include <linux/timer.h>
-
#include <linux/types.h> /* size_t */
-
#include <linux/fcntl.h> /* O_ACCMODE */
-
#include <linux/hdreg.h> /* HDIO_GETGEO */
-
#include <linux/kdev_t.h>
-
#include <linux/vmalloc.h>
-
#include <linux/genhd.h>
-
#include <linux/blkdev.h>
-
#include <linux/buffer_head.h> /* invalidate_bdev */
-
#include <linux/bio.h>
-
#include <linux/major.h>
-
-
-
-
#define SIMP_BLKDEV_DEVICEMAJOR COMPAQ_SMART2_MAJOR
-
#define SIMP_BLKDEV_DISKNAME "simp_blkdev"
-
#define SIMP_BLKDEV_BYTES (16*1024*1024)
-
-
static struct request_queue *simp_blkdev_queue;
-
static struct gendisk *simp_blkdev_disk;
-
unsigned char simp_blkdev_data[SIMP_BLKDEV_BYTES];
-
-
static int simp_blkdev_make_request(struct request_queue *q, struct bio *bio)
-
{
-
struct bio_vec *bvec;
-
int i;
-
void *dsk_mem;
-
-
if ((bio->bi_sector << 9) + bio->bi_size > SIMP_BLKDEV_BYTES) {
-
printk(KERN_ERR SIMP_BLKDEV_DISKNAME
-
": bad request: block=%llu, count=%u\n",
-
(unsigned long long)bio->bi_sector, bio->bi_size);
-
-
bio_endio(bio, -EIO);
-
return 0;
-
}
-
-
dsk_mem = simp_blkdev_data + (bio->bi_sector << 9);
-
-
bio_for_each_segment(bvec, bio, i) {
-
void *iovec_mem;
-
-
switch (bio_rw(bio)) {
-
case READ:
-
case READA:
-
iovec_mem = kmap(bvec->bv_page) + bvec->bv_offset;
-
memcpy(iovec_mem, dsk_mem, bvec->bv_len);
-
kunmap(bvec->bv_page);
-
break;
-
case WRITE:
-
iovec_mem = kmap(bvec->bv_page) + bvec->bv_offset;
-
memcpy(dsk_mem, iovec_mem, bvec->bv_len);
-
kunmap(bvec->bv_page);
-
break;
-
default:
-
printk(KERN_ERR SIMP_BLKDEV_DISKNAME
-
": unknown value of bio_rw: %lu\n",
-
bio_rw(bio));
-
-
bio_endio(bio, -EIO);
-
return 0;
-
}
-
dsk_mem += bvec->bv_len;
-
}
-
-
-
bio_endio(bio, 0);
-
-
-
return 0;
-
}
-
-
-
struct block_device_operations simp_blkdev_fops = {
-
.owner = THIS_MODULE,
-
};
-
-
static int __init simp_blkdev_init(void)
-
{
-
int ret;
-
-
simp_blkdev_queue = blk_alloc_queue(GFP_KERNEL);
-
if (!simp_blkdev_queue) {
-
ret = -ENOMEM;
-
goto err_alloc_queue;
-
}
-
blk_queue_make_request(simp_blkdev_queue, simp_blkdev_make_request);
-
-
simp_blkdev_disk = alloc_disk(1);
-
if (!simp_blkdev_disk) {
-
ret = -ENOMEM;
-
goto err_alloc_disk;
-
}
-
-
strcpy(simp_blkdev_disk->disk_name, SIMP_BLKDEV_DISKNAME);
-
simp_blkdev_disk->major = SIMP_BLKDEV_DEVICEMAJOR;
-
simp_blkdev_disk->first_minor = 0;
-
simp_blkdev_disk->fops = &simp_blkdev_fops;
-
simp_blkdev_disk->queue = simp_blkdev_queue;
-
set_capacity(simp_blkdev_disk, SIMP_BLKDEV_BYTES>>9);
-
add_disk(simp_blkdev_disk);
-
-
return 0;
-
-
err_alloc_disk:
-
blk_cleanup_queue(simp_blkdev_queue);
-
err_alloc_queue:
-
return ret;
-
}
-
-
-
static void __exit simp_blkdev_exit(void)
-
{
-
del_gendisk(simp_blkdev_disk);
-
put_disk(simp_blkdev_disk);
-
blk_cleanup_queue(simp_blkdev_queue);
-
}
-
-
module_init(simp_blkdev_init);
-
module_exit(simp_blkdev_exit);
三、不使用IO调度器的程序实现
数据访问流程
generic_make_request()
__generic_make_request(bio)
q->make_request_fn(q,bio)
struct request_queue
{
...
make_request_fn *make_request_fn;
...
}
struct request_queue *blk_init_queue(request_fn_proc *rfn,spinlock_t *lock)
{
return blk_init_queue_node(rfn,lock,-1);
}
blk_init_queue_node(request_fn_proc *rfn,spinlock_t *lock,int node_id)
{
....................................................
blk_queue_make_request(q,__make_request);
......
}
void blk_queue_make_request(struct_queue *q,make_request_fn *mfn)
{
......
q->make_request_fn = mfn;
........
}
BIO
1个struct bio代表1次块设备IO请求,IO调度器可将连续的bio合并成1个请求struct request。
struct bio
{
sector_t bi_sector;//要访问的第一个扇区
unsigned int bi_size;//以字节为单位所需传输的数据大小
struct bio_vec *bi_io_vec;//实际的vec列表
............................
}
bi_io_vec
struct bio_vec{
struct page *bv_page;//页指针
unsigned int bv_len;//传输的数据长度
unsigned int bv_offset;//偏移量
};
__make_request
在__make_request函数中,使用了IO调度器(elecator)将多个bio的访问顺序进行优化,调整,合并为一个
request,然后提交给用户指定的函数处理。但是对于ramdisk,U盘,记忆棒之类的设备,并不存在磁盘所面临的
寻道时间。因此对这样的“块设备”而言,一个IO调度器不但发挥不了作用,反而其本身将白白耗掉不少内存和
CPU。
解决办法:驱动程序自己实现request_queue所需要的make_request_fn函数,不使用__make_request.
1、请求队列
request_queue_t *blk_alloc_queue(int gfp_mask)
分配“请求队列”,对于FLASH、RAM盘等完全随机访问的非机械设备,并不需要进行复杂的IO调度,这个时候,应该使用上述函数分配1个“请求队列”
void blk_queue_make_request(request_queue_t* q,make_request_fn *mfn)
绑定“请求队列”和"制造请求"函数
块设备驱动测试
1>insmod simple - blk.ko
2>ls /dev/simp_blkdev
3>mkfs.ext3 /dev/simp_blkdev
4>mkdir -p /mnt/blk
5> mount
6>cp /etc/init.d*/* /mnt/blk
7>ls /mnt/blk
8>umount /mnt/blk
9>ls /mnt/blk