1、块设备是linux3大设备之一。其驱动模型主要针对磁盘,Flash等存储类设备。
2、块设备为什么要缓存?
针对带磁头设备,需读取扇区,分布可能随机。需优化读取顺序,减小磁头机械运转次数。
针对不带磁头设备,暂时先扇区(或者块)读取到ram中缓存,只修改ram中相应位置数据,在写回到块设备,减小擦写次数。
3、框架
4、函数调用流程
4、重要数据结构
bio结构简介
bio结构就是kernel中block I/O的基础容器,它是在”linux/bio.h”中被定义的。这个结构将当前激活的block I/O操作表示为一个segment的list。segment就是内存中一块连续的buffer。因此,个别的buffer在内存中不需要连续。为了使buffer可以组成块,bio结构可以将内存多处组成一个buffer,并使kernel在该buffer上进行block I/O操作。这样的I/O被称为scatter-gather I/O。
struct bio {
sector_t bi_sector; /* associated sector on disk */
struct bio *bi_next; /* list of requests */
struct block_device *bi_bdev; /* associated block device */
unsigned long bi_flags; /* status and command flags */
unsigned long bi_rw; /* read or write? */
unsigned short bi_vcnt; /* number of bio_vecs off */
unsigned short bi_idx; /* current index in bi_io_vec */
unsigned short bi_phys_segments; /* number of segments after coalescing */
unsigned short bi_hw_segments; /* number of segments after remapping */
unsigned int bi_size; /* I/O count */
unsigned int bi_hw_front_size; /* size of the first mergeable segment */
unsigned int bi_hw_back_size; /* size of the last mergeable segment */
unsigned int bi_max_vecs; /* maximum bio_vecs possible */
struct bio_vec *bi_io_vec; /* bio_vec list */
bio_end_io_t *bi_end_io; /* I/O completion method */
atomic_t bi_cnt; /* usage counter */
void *bi_private; /* owner-private method */
bio_destructor_t *bi_destructor; /* destructor method */
};
bio结构的主要作用就是表示一个正在使用的block I/O操作。因此,在这个结构中最重要的属性是与日常管理相关的,分别是bi_io_vecs, bi_vcnt和bi_idx。下图中展示了struct bio, strcut bio_vec与struct page之间的联系:
其中,bi_io_vecs属性指向了一个bio_vec结构的数组。这些结构用于表示一列特殊block I/O操作的特定segments。每一个bio_vec都是一个”page, offset, len”形式的向量,描述了一个特定的segment。其中的page表示这个segment所在的物理页,offset表示这个block相对于所在页的偏移量,len则是这个block从该偏移量开始的长度。由这些向量组成的整个数组表示了这个完整的buffer。
bio_vec结构在”linux/bio.h”中是这样被定义的:
struct bio_vec {
/* pointer to the physical page on which this buffer resides */
struct page *bv_page;
/* the length in bytes of this buffer */
unsigned int bv_len;
/* the byte offset within the page where the buffer resides */
unsigned int bv_offset;
};
---------------------------------------------------------------------
gendisk对应一个磁盘实物,结构体如下:
struct gendisk {
int major; //设备主设备号,等于register_blkdev()函数里的major
int first_minor; //起始次设备号,等于0,则表示此设备号从0开始的
int minors; //分区(次设备)数量,当使用alloc_disk()时,就会自动设置该成员
char disk_name[32]; //块设备名称, 等于register_blkdev()函数里的name
struct hd_struct **part; /*分区表的信息*/
int part_uevent_suppress;
struct block_device_operations *fops; //块设备操作函数
struct request_queue *queue; //请求队列,用于管理该设备IO请求队列的指针*
void *private_data; /*私有数据*/
sector_t capacity; /*扇区数,512字节为1个扇区,描述设备容量*/
....
};
request结构体如下:
struct request {
//用于挂在请求队列链表的节点,使用函数elv_next_request()访问它,而不能直接访问
struct list_head queuelist;
struct list_head donelist; /*用于挂在已完成请求链表的节点*/
struct request_queue *q; /*指向请求队列*/
unsigned int cmd_flags; /*命令标识*/
enum rq_cmd_type_bits cmd_type; //读写命令标志,为 0(READ)表示读, 为1(WRITE)表示写
sector_t sector; //要提交的下一个扇区偏移位置(offset)
... ...
unsigned int current_nr_sectors; //当前需要传送的扇区数(长度)
... ...
char *buffer; //当前请求队列链表的申请里面的数据,用来读写扇区数据(源地址)
... ...
};
步骤如下:
3.1在入口函数中:
3.2在申请队列的处理函数中
3.3在出口函数中
代码如下:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
static DEFINE_SPINLOCK(memblock_lock); //定义自旋锁
static request_queue_t * memblock_request; //申请队列
static struct gendisk *memblock_disk; //磁盘结构体
static int memblock_major;
#define BLOCKBUF_SIZE (1024*1024) //磁盘大小
#define SECTOR_SIZE (512) //扇区大小
static unsigned char *block_buf; //磁盘地址
static int memblock_getgeo(struct block_device *bdev, struct hd_geometry *geo)
{
geo->heads =2; // 2个磁头分区
geo->cylinders = 32; //一个磁头有32个柱面
geo->sectors = BLOCKBUF_SIZE/(2*32*SECTOR_SIZE); //一个柱面有多少个扇区
return 0;
}
static struct block_device_operations memblock_fops = {
.owner = THIS_MODULE,
.getgeo = memblock_getgeo, //几何,保存磁盘的信息(柱头,柱面,扇区)
};
/*申请队列处理函数*/
static void do_memblock_request (request_queue_t * q)
{
struct request *req;
unsigned long offset;
unsigned long len;
static unsigned long r_cnt = 0;
static unsigned long w_cnt = 0;
while ((req = elv_next_request(q)) != NULL) //获取每个申请
{
offset=req->sector*SECTOR_SIZE; //偏移值
len=req->current_nr_sectors*SECTOR_SIZE; //长度
if(rq_data_dir(req)==READ)
{
memcpy(req->buffer,block_buf+offset,len); //读出缓存
}
else
{
memcpy(block_buf+offset,req->buffer,len); //写入缓存
}
end_request(req, 1); //结束获取的申请
}
}
/*入口函数*/
static int memblock_init(void)
{
/*1)使用register_blkdev()创建一个块设备*/
memblock_major=register_blkdev(0, "memblock");
/*2) blk_init_queue()使用分配一个申请队列,并赋申请队列处理函数*/
memblock_request=blk_init_queue(do_memblock_request,&memblock_lock);
/*3)使用alloc_disk()分配一个gendisk结构体*/
memblock_disk=alloc_disk(16); //不分区
/*4)设置gendisk结构体的成员*/
/*->4.1)设置成员参数(major、first_minor、disk_name、fops)*/
memblock_disk->major = memblock_major;
memblock_disk->first_minor = 0;
sprintf(memblock_disk->disk_name, "memblock");
memblock_disk->fops = &memblock_fops;
/*->4.2)设置queue成员,等于之前分配的申请队列*/
memblock_disk->queue = memblock_request;
/*->4.3)通过set_capacity()设置capacity成员,等于扇区数*/
set_capacity(memblock_disk,BLOCKBUF_SIZE/SECTOR_SIZE);
/*5)使用kzalloc()来获取缓存地址,用做扇区*/
block_buf=kzalloc(BLOCKBUF_SIZE, GFP_KERNEL);
/*6)使用add_disk()注册gendisk结构体*/
add_disk(memblock_disk);
return 0;
}
static void memblock_exit(void)
{
/*1)使用put_disk()和del_gendisk()来注销,释放gendisk结构体*/
put_disk(memblock_disk);
del_gendisk(memblock_disk);
/*2)使用kfree()释放磁盘扇区缓存 */
kfree(block_buf);
/*3)使用blk_cleanup_queue()清除内存中的申请队列 */
blk_cleanup_queue(memblock_request);
/*4)使用unregister_blkdev()卸载块设备 */
unregister_blkdev(memblock_major,"memblock");
}
module_init(memblock_init);
module_exit(memblock_exit);
MODULE_LICENSE("GPL");
测试运行:
insmod ramblock.ko //挂载memblock块设备 mkdosfs /dev/memblock //将memblock块设备格式化为dos磁盘类型 mount /dev/ memblock /tmp/ //挂载块设备到/tmp目录下 cat /dev/memblock > /mnt/memblock.bin //在/mnt目录下创建.bin文件,然后将块设备里面的文件追加到.bin里面 cd /; umount /tmp/ //退出/mp,卸载,同时之前读写的文件也会消失