S3C2440 块设备驱动程序的编写驱动之用内存模拟硬盘(二十一)

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的,就是主磁盘)

 

猜你喜欢

转载自blog.csdn.net/xiaodingqq/article/details/81201947