一、概念:
Linux操作系统有两类主要的设备文件:
1.字符设备:以字节为单位进行顺序I/O操作的设备,无需缓冲区且被直接读写。
2.块设备:只能以块单位接收输入返回,对于I/O请求有对应的缓冲区,可以随机访问,块设备的访问位置必须能够在介质的不同区间前后移动。在块设备中,最小的可寻址单元是扇区,扇区的大小一般是2的整数倍,常见的大小为512个字节。
二、驱动分析
由于硬件的限制,我们用JZ2440开发板的虚拟内存模拟硬盘进行操作。
2.1 驱动程序的编写框架
1. 分配gendisk结构体
2. 设置
2.1 分配/设置队列,并将它放入结构体中,用来提供读写能力
2.2 设置gendisk其他信息(主设备号、次设备号、名字、操作函数、容量)
操作函数:这里面主要调用了ramblock_getgeo函数用来设置硬盘的属性(虽然是RAM模拟的,为了使用 老的工具还是要假装设置一下)
3.硬件相关的操作(就是分配一块内存,比较简单)
4. 注册: add_disk
5. 请求处理函数(块设备驱动的核心)
当内核需要驱动程序处理读取、写入以及其他的操作时,就会调用该函数
//参考 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;
#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 DEFINE_SPINLOCK(ramblock_lock);
static struct block_device_operations ramblock_fops = {
.owner = THIS_MODULE, /*一个指向拥有该结构的模块指针,通常设为THIS_MODULE */
.getgeo = ramblock_getgeo, /*8几何属性*/
};
static void do_ramblock_request (request_queue_t * q)
{
static int cnt = 0;
struct request *req;
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) //读
{
memcpy(req->buffer, ramblock_buf+offset, len); /*将内存中数据拷贝到buffer中*/
}
else //写
{
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,由系统分配主设备号*/
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); /*扇区是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");
三、测试
- insmod ramblock.ko
- 格式化: mkdosfs /dev/ramblock
- 挂接: mount /dev/ramblock /tmp/
- 读写文件: cd /tmp, 在里面vi文件
- cd /; umount /tmp/
- cat /dev/ramblock > /mnt/ramblock.bin
- 在PC上查看ramblock.bin
sudo mount -o loop ramblock.bin /mnt
也可以 . fdisk /dev/ramblock进行分区测试