一.块设备驱动框架
app: open,read,write "hello.txt"
--------------------------------------------- 文件的读写
文件系统: vfat, ext2, ext3, yaffs2, jffs2 (把文件的读写转换为扇区的读写)
-----------------ll_rw_block----------------- 扇区的读写(暂时先不分析)
1. 把"读写"放入队列
2. 调用队列的处理函数(优化/调顺序/合并)
块设备驱动程序
---------------------------------------------
硬件: 硬盘,flash
与字符设备相比什么区别:
1.块设备只能以块为单位接受输入和返回输出,而字符设备则以字节为单位。大多数设备是字符设备,因为它们不需要缓冲而且不以固定块大小进行操作。
2.块设备对于I/O请求有对应的缓冲区,因此它们可以选择以什么顺序进行响应,字符设备无需缓冲且被直接读写。对于存储设备而言调整读写的顺序作用巨大,因为在读写连续的扇区比分离的扇区更快。
3.符设备只能被顺序读写,而块设备可以随机访问。虽然块设备可随机访问,但是对于磁盘这类机械设备而言,顺序地组织块设备的访问可以提高性能。
二.驱动代码
/* 参考: * 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) 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, //一个指向拥有该结构的模块指针,通常设为 THIS_MODULE .getgeo = ramblock_getgeo, //几何属性 }; static void do_ramblock_request(request_queue_t * q) { static int r_cnt = 0; static int w_cnt = 0; struct request *req; //printk("do_ramblock_request %d\n", ++cnt); while ((req = elv_next_request(q)) != NULL) //一旦有请求req就不为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); //将内存中数据拷贝到buffer中 } else //如果是写 { //printk("do_ramblock_request write %d\n", ++w_cnt); memcpy(ramblock_buf+offset, req->buffer, len); //将buffer中的数据拷到内存中 } 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); ramblock_disk->queue = ramblock_queue; //把队列放入结构体 /* 2.2 设置其他属性: 比如容量 */ major = register_blkdev(0, "ramblock"); /* 向内核注册块设备,如果是0,则分配一个新的主设备号给设备,并将设备号返回给调用者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");
三.驱动分析
我们的驱动程序要实现的功能就是将内存模拟硬盘进行操作。
1. 分配gendisk结构体
2. 设置
2.1 分配/设置队列,并将它放入结构体中,用来提供读写能力
2.2 设置gendisk其他信息(主设备号、次设备号、名字、操作函数、容量)
操作函数:这里面主要调用了ramblock_getgeo函数用来设置硬盘的属性(虽然是RAM模拟的,为了使用 老的工具还是要假装设置一下)
3.硬件相关的操作(就是分配一块内存,比较简单)
4. 注册: add_disk
4.请求处理函数(块设备驱动的核心)
当内核需要驱动程序处理读取、写入以及其他的操作时,就会调用改函数。
static void do_ramblock_request(request_queue_t * q) { static int r_cnt = 0; static int w_cnt = 0; struct request *req; //printk("do_ramblock_request %d\n", ++cnt); while ((req = elv_next_request(q)) != NULL) //一旦有请求req就不为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); //将内存中数据拷贝到buffer中 } else //如果是写 { //printk("do_ramblock_request write %d\n", ++w_cnt); memcpy(ramblock_buf+offset, req->buffer, len); //将buffer中的数据拷到内存中 } end_request(req, 1); } }
参考:韦东山视频第二期
LDD3
原文:http://blog.csdn.net/ruoyunliufeng/article/details/25240899