块设备驱动程序引入
本文形成于韦东山部分教学视频笔记及个人理解。
我们常见的存储设备比如硬盘的内部结构一般是由柱面、扇区等部分组成,切换不同柱面读写都需要硬件机械结构的切换,所以如果按照常规字符设备的读写方法会频繁的切换柱面扇区,导致程序执行效率低下且容易降低硬盘的机械寿命。flash设备类似,flash的基本结构是块、扇区;flash设备再写操作之前必须先擦住整个块再写,如果写同一个块里面的不同扇区就需要拷贝修改擦除写入四个步骤执行两边,效率低下。所以块设备驱动应运而生,相对于字符设备驱动块设备驱动的区别在于:
1.读写操作先不执行,而是放入队列 2:优化操作后再执行(包括调整顺序、合并请求)
块设备程序的框架如下:文件系统将文件的读写转换为扇区的读写以后会通过通用的读写接口ll_rw_block调用到具体的块设备驱动程序。
ll_rw_block的代码调用流程,其中优化队列的过程中多次使用了elv_merge类似电梯算法去优化队列,保证每一次读写的效率达到最高
从上面的框架和调用流程分析,如何写一个块设备驱动程序:
1.分配一个gendisk结构体
2.1.分配设置队列(request_queue_t),提供读写能力
2.2.设置gendisk的其他信息(比如容量)
3.注册(add_disk)
示例:
struct gendisk *ramblock_disk = alloc_disk(16) //参数代表次设备号,该磁盘可以有几个分区(16-1)
//do_ramblock_request 为处理请求队列的处理函数
request_queue_t *ramblock_queue = = blk_init_queue(do_ramblock_request, &ramblock_lock);
内存模拟块设备驱动程序
下面将实现利用一段内存来模拟块设备驱动程序加载到系统。
/* 参考:
* drivers\block\xd.c
* drivers\block\z2ram.c
*/
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/interrupt.h>
#include <linux/mm.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/timer.h>
#include <linux/genhd.h>
#include <linux/hdreg.h>
#include <linux/ioport.h>
#include <linux/init.h>
#include <linux/wait.h>
#include <linux/blkdev.h>
#include <linux/blkpg.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <asm/system.h>
#include <asm/uaccess.h>
#include <asm/dma.h>
static struct gendisk *ramblock_disk;
static request_queue_t *ramblock_queue;
static int major;
static DEFINE_SPINLOCK(ramblock_lock);
#define RAMBLOCK_SIZE (1024*1024) //1M
static unsigned char *ramblock_buf;
static int ramblock_getgeo(struct block_device *bdev, struct hd_geometry *geo)
{
/* 容量=heads*cylinders*sectors*512 */
geo->heads = 2; //磁头个数,有多少面
geo->cylinders = 32; //柱面个数,有多少环
geo->sectors = RAMBLOCK_SIZE/2/32/512; //一个柱面里面有多少扇区
return 0;
}
static struct block_device_operations ramblock_fops = {
.owner = THIS_MODULE,
.getgeo = ramblock_getgeo,
};
//处理请求队列的数据操作,硬件扇区的读写操作
static void do_ramblock_request(request_queue_t * q)
{
static int r_cnt = 0, w_cnt = 0;
struct request *req;
//printk("do_ramblock_request %d\n", ++cnt);
while ((req = elv_next_request(q)) != NULL) {
/* 数据传输三要素: 源,目的,长度 */
/* 源/目的: */
unsigned long offset = req->sector * 512;
/* 目的/源: */
// req->buffer
/* 长度: */
unsigned long len = req->current_nr_sectors * 512;
if (rq_data_dir(req) == READ)
{
//printk("do_ramblock_request read %d\n", ++r_cnt);
memcpy(req->buffer, ramblock_buf+offset, len);
}else{
//printk("do_ramblock_request write %d\n", ++w_cnt);
memcpy(ramblock_buf+offset, req->buffer, len);
}
end_request(req, 1);//请求队列操作完成
}
}
static int ramblock_init(void)
{
/* 1. 分配一个gendisk结构体 */
ramblock_disk = alloc_disk(16); /* 次设备号个数: 分区个数+1 */
/* 2. 设置 */
/* 2.1 分配/设置队列: 提供读写能力 */
ramblock_queue = blk_init_queue(do_ramblock_request, &ramblock_lock);//do_ramblock_request为处理请求队列的处理函数
ramblock_disk->queue = ramblock_queue;
/* 2.2 设置其他属性: 比如容量 */
major = register_blkdev(0, "ramblock"); /* cat /proc/devices 提供主设备号 */
ramblock_disk->major = major;
ramblock_disk->first_minor = 0;
sprintf(ramblock_disk->disk_name, "ramblock");
ramblock_disk->fops = &ramblock_fops;
set_capacity(ramblock_disk, RAMBLOCK_SIZE / 512); //设置容量,是以扇区为单位
/* 3. 硬件相关操作 */
ramblock_buf = kzalloc(RAMBLOCK_SIZE, GFP_KERNEL);
/* 4. 注册 */
add_disk(ramblock_disk);
return 0;
}
static void ramblock_exit(void)
{
unregister_blkdev(major, "ramblock");
del_gendisk(ramblock_disk);
put_disk(ramblock_disk);
blk_cleanup_queue(ramblock_queue);
kfree(ramblock_buf);
}
module_init(ramblock_init);
module_exit(ramblock_exit);
MODULE_LICENSE("GPL");