http://www.cnblogs.com/lifexy/p/7661454.html
http://www.cnblogs.com/lifexy/p/7661239.html
通过上节的块设备驱动分析,本节便通过内存来模拟块设备驱动,方便我们更加熟悉块设备驱动框架
参考内核自带的块设备驱动程序:
drivers/block/xd.c
drivers/block/z2ram.c
1、本节需要的结构体如下:
1.1 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个扇区,描述设备容量
... ...
}
1.2 requeset申请结构体:
struct request {
//用于挂在请求队列链表的节点,使用函数elv_next_request()访问它,而不能直接访问
struct list_head queuelist;
struct list_head donelist; //用于挂在已完成请求链表的节点
request_queue_t *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;//当前请求队列链表的申请里面的数据,用于读写扇区数据(源地址)
... ...
};
2、本节需要的函数如下:
int register_blkdev(unsigned int major, const char *name);
创建一个块设备,当major==0时,表示动态创建,创建成功,会返回一个主设备号
unregister_blkdev(unsigned int major, const char *name);
卸载一个块设备,在出口函数中使用,major:主设备号,name:名称
struct gendisk *alloc_disk(int minors);
分配一个gendisk结构,minors为分区数,填1表示不分区
void del_gendisk(struct gendisk *disk);
释放gendisk结构,在出口函数中使用,也就是不需要这个硬盘了
request_queue *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock);
分配一个request_queue请求队列,分配成功返回一个request_queue结构体
rfn:request_fn_proc结构体,用来执行放置在队列中的请求的处理函数
lock:队列访问权限的自旋锁(spinlock),该锁通过DEFINE_SPINLOCK()来定义
void blk_cleanup_queue(request_queue_t * q);
清除内核中的request_queue请求队列,在出口函数中使用
static DEFINE_SPINLOCK(spinlock_t lock);
定义一个自旋锁(spinlock)
static inline void set_capacity(struct gendisk *disk, sector_t size);
设置gendisk结构体的扇区数(成员capacity),size等于扇区数
该函数内容如下:
disk->capacity = size;
void add_disk(struct gendisk *gd);
向内核中注册gendisk结构体
void put_disk(struct gendisk *disk);
注销内核中的gendisk结构体,在出口函数中使用
struct request *elv_next_request(request_queue_t *q);
通过电梯调度算法获取申请中未完成的申请,获取成功返回一个request结构体,不成功返回一个NULL
(PS:不使用获取到的这个申请时,应使用end_request()来结束获取申请)
void end_request(struct request *req, int uptodate);
结束获取申请,当uptodate==0,表示使用该申请读写扇区失败,uptodate==1,表示成功
static inline void *kzalloc(size_t size, gfp_t flags);
分配一段静态缓存,这里用来当做我们的磁盘扇区使用,分配成功返回缓存地址,分配失败会返回0
void kfree(const void *block);
注销一段静态缓存,与kzalloc()成对,在出口函数中使用
rq_data_dir(rq);
获取request申请结构体的命令标志(cmd_flags成员),当返回READ(0)表示读扇区命令,否则表示写扇区命令
3、步骤如下:
3.1 在入口函数中:
1) 使用register_blkdev()创建一个块设备
2) blk_init_queue()使用分配一个申请队列,并赋申请队列处理函数
3) 使用alloc_disk()分配一个gendisk结构体
4)设置gendisk结构体的成员
- 设备成员参数(major、first_minor、disk_name、fops)
- 设置queue成员,等于之前分配的申请队列
- 通过set_capacity()设置capacity和成员,等于扇区数
5)使用kzalloc()来获取缓存地址,用作扇区
6)使用add_disk()注册gendisk结构体
3.2 在申请队列的处理函数中
1) while循环使用elv_next_request()获取申请队列中每个未处理的申请
2) 使用rq_data_dir()来获取每个申请的读写命令标志,为0(READ)表示读,为1(WRITE)表示写
3)使用memcpy()来读或者写扇区(缓存)
4)使用end_request()来结束获取的每个申请
3.3 在出口函数中
1)使用put_disk()和del_gendisk()来注销,释放gendisk结构体
2)使用kfree()释放磁盘扇区缓存
3)使用blk_cleanup_queue()清楚内存中的申请队列
4)使用unregister_blkdev()卸载块设备
4、代码如下:
/* 参考:
* 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; //分配一块内存
//为了用命令fdisk,使用getgeo
static int ramblock_getgeo(struct block_device *bdev, struct hd_geometry *geo)
{
/* 容量 = heads*cylinders*cylinders*512,一个扇区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;
static int w_cnt = 0;
struct request *req;
//printk("do_ramblock_request %d\n", ++cnt);
//以电梯调度算法,从队列q中取出下一个请求,实现读写操作
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) //读出缓存,从磁盘里读数据到buffer里
{
//printk("do_ramblock_request read %d\n", ++r_cnt);
//参数to from count
memcpy(req->buffer, ramblock_buf+offset, len);
}
else //写入缓存,从buffer里的数据写到内存里去
{
//printk("do_ramblock_request write %d\n", ++w_cnt);
//参数to from count,
memcpy(ramblock_buf+offset, req->buffer, len);
}
end_request(req, 1); //结束获取的申请,1:成功 0:失败
}
}
static int ramblock_init(void)
{
/* 1. 分配一个gendisk结构体 */
ramblock_disk = alloc_disk(16); /* 次设备号个数:分区个数+1,创建15个分区 */
/* 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"); /* 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); //容量,以扇区为单位的(扇区为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");
5、测试运行:
insmod ramblock.ko //挂载memblock块设备
mkdosfs /dev/ramblock //将ramblock块设备格式化为dos磁盘类型
mount /dev/ramblock /tmp/ //挂载块设备到/tmp目录下
接下来在/tmp目录下vi 1.txt文件,最终都会保存在/dev/ramblock块设备里面
cd /; umount /tmp/ //退出tmp,卸载,同时之前读写的文件也会消失
cat /dev/ramblock > /mnt/ramblock.bin //在/mnt目录下创建.bin文件,然后将块设备里面的文件追加
然后进入linux的nfs挂载目录中
sudo mount -o loop ramblock.bin /mnt //挂载ramblock.bin, -loop:将文件当作磁盘来挂载
在开发板上的效果图:
在虚拟机linux上的效果图:
6、使用fdisk来对磁盘进行分区
名称: fdisk
使用: fdisk [块设备磁盘]
说明: 将一个块设备(磁盘)分成若干个块设备(磁盘),并将分区的信息写进分区表。
fdisk命令菜单常用参数如下所示:
- d:(del)删除一个分区。
- n:(new)新建一个新分区。
- p:(print)打印分区表。
- q:(quit)放弃不保存。
- t:改变分区类型
- w:(write)把分区写进分区表,保存并退出。
操作实例:
# fdisk /dev/memblock //对memblock块设备分区
1.输入n, 出现两个菜单e表示扩展分区,p表示主分区
2.输入p,进入主分区,再输入1,表示第一个主分区:
为什么柱面数只有1~32?因为在程序中我们设置了该块设备的磁盘信息,
如上图, 因为geo->heads =2,所以最多只能创建2个分区
如下图,我们输入3,创建第3个主分区会失败:
3.然后输入1,表示开始柱面 ,再输入5,表示结束柱面
4.再次输入n,p,2,创建第2个分区,可以发现起始柱面就是从6开始的,因为1~5柱面被第一个分区占用了
5.第2个分区创建好了,输入p,打印分区表
6.输入w,保存并退出。
发现出错,出现分区无法写入分区表,如下图所示:
找到在驱动程序入口函数中,alloc_disk()分配一个gendisk,设置的只有一个分区.如下图所示:
修改参数,改为大于2的值即可,然后重新执行就没有问题了
7.输入ls /dev/memblock* -l,就能看到分到的分区了
(PS:次设备号为0的,就是主磁盘)