1. 程式人生 > >通過記憶體模擬硬碟實現一個簡單的塊裝置驅動

通過記憶體模擬硬碟實現一個簡單的塊裝置驅動

本文的主要工作是通過硬碟來模擬記憶體,按照塊裝置驅動程式設計的框架實現一個簡單的塊裝置驅動程式。

一、前期的準備工作

1、基本開發環境

Linux核心版本:Linux-3.4.10

開發板 : JZ2440(ARM9)

2、塊裝置驅動的一般開發步驟

a、分配一個gendisk的結構體變數

b、設定這個結構體變數,

b1、分配設定一個佇列,通過它來為塊裝置提供讀寫能力

b2、設定gendisk結構體的其他成員

c、註冊這個gendisk結構體的變數

二、編寫塊裝置驅動程式

通過在記憶體中分配2MB的空間來模擬硬碟,實現簡單的塊裝置的讀寫、格式化、掛接等操作。為了驅動編寫的方便,定義了這樣一個全域性的結構體,並通過這個結構體定義了一個全域性變數,具體實現如下:

/* 定義一個yl_ramdisk_t的結構體,封裝驅動程式使用的各種資料 */
struct yl_ramdisk_t
{
	int major;				// 主裝置號
	unsigned char *ramdisk_buffer;		// 在記憶體中分配的緩衝區的儲存區
	struct gendisk *gendisk;		// 定義一個gendisk結構體變數指標變數
	struct request_queue *queue;		// 定義一個請求佇列的結構體指標變數
};

/* 定義一個yl_ramdisk結構體的全域性變數 */
static struct yl_ramdisk_t yl_ramdisk;
1、分配一個gendisk結構體變數,具體實現如下:
/* 1、分配一個gendisk結構體變數 */
yl_ramdisk.gendisk = alloc_disk(8);	/* 分割槽數 + 1 =  minors*/	
if (!yl_ramdisk.gendisk)
{
	printk("alloc_disk error!\n");
	return -ENOMEM;
}
2、設定這個分配的gendisk結構體變數的成員,具體如下:

2.1 分配一個佇列,主要是為了給塊裝置提供讀寫能力

/* 2.1 分配一個佇列,提供讀寫能力 */
yl_ramdisk.queue = blk_init_queue(do_yl_ramdisk_request, &ramdisk_lock);
if (!yl_ramdisk.queue)
{
    put_disk(yl_ramdisk.gendisk);
printk("blk_init_queue error!\n");
	return -ENOMEM;
}
這裡面需要提供一個函式來實現具體的塊裝置的讀寫操作:do_yl_ramdisk_request()函式,它的具體實現如下:
/* 定義佇列處理函式 */
static void do_yl_ramdisk_request(struct request_queue *q)
{
	struct request *req;

	/* 從求情佇列裡面獲得一個請求 */
	req = blk_fetch_request(q);
	while (req) 
	{
		unsigned long offset = blk_rq_pos(req) << 9;	// 獲取ramdisk的偏移值
		unsigned long len  = blk_rq_cur_bytes(req);		// 獲取傳輸資料的大小
		int err = 0;

		/* 根據讀寫來決定資料傳輸方向 */
		if (rq_data_dir(req) == READ)
			memcpy(req->buffer, (char *)(yl_ramdisk.ramdisk_buffer + offset), len);
		else
			memcpy((char *)(yl_ramdisk.ramdisk_buffer + offset), req->buffer, len);

		/* 判斷是否是佇列尾部,如果不是再次獲得一個請求 */
		if (!__blk_end_request_cur(req, err))
			req = blk_fetch_request(q);
	}
}
2.2 設定gendisk其他相關的屬性、成員,具體如下:
/* 2.2 設定其他相關屬性 */
yl_ramdisk.major = register_blkdev(0, "yl_ramdisk");	// 註冊塊裝置,獲取主裝置號
	
yl_ramdisk.gendisk->major 		= yl_ramdisk.major;		// 設定主裝置號
yl_ramdisk.gendisk->first_minor = 0;					// 設定次裝置號的起始為0
yl_ramdisk.gendisk->queue		= yl_ramdisk.queue;		// 設定佇列
yl_ramdisk.gendisk->fops 		= &yl_ramdisk_fops;		// 塊裝置操作函式集合
sprintf(yl_ramdisk.gendisk->disk_name, YL_DEVICE_NAME);	// 設定名字
set_capacity(yl_ramdisk.gendisk, YL_RAMDISK_SIZE / 512);		// 設定ramdisk的大小
這裡面實現了一個塊裝置操作的函式結合的成員:yl_ramdisk_fops,它的主要功能就是實現對塊裝置的操作,我們這裡主要用它實現模擬硬碟時磁頭、柱面、扇區等的大小,它的具體實現如下:
/* 模擬機械硬碟,為其設定磁頭、柱面、扇區等的大小 */
static int yl_ramdisk_getgeo(struct block_device *bdev, struct hd_geometry *geo)
{
	/* 磁碟大小 =  heads*cylinders*sectors*512 */
	geo->heads = 4;
	geo->cylinders = 32;
	geo->sectors = YL_RAMDISK_SIZE/geo->heads/geo->cylinders/512;
	return 0;
}

/* 定義塊裝置的操作函式結構體變數 */
static const struct block_device_operations yl_ramdisk_fops = {
	.owner	= THIS_MODULE,
	.getgeo = yl_ramdisk_getgeo,
};
3、硬體相關的操作,主要是在記憶體中分配2MB的記憶體空間,為模擬硬碟做準備,具體實現如下:
/* 3、硬體相關的操作 */
yl_ramdisk.ramdisk_buffer = kzalloc(YL_RAMDISK_SIZE, GFP_KERNEL);
if (!yl_ramdisk.ramdisk_buffer) {
	unregister_blkdev(yl_ramdisk.major, YL_DEVICE_NAME);
	blk_cleanup_queue(yl_ramdisk.queue);
	put_disk(yl_ramdisk.gendisk);
	printk("kzalloc error!\n");
	return -ENOMEM;
}
4、註冊這個gendisk結構體變數
/* 4、註冊這個gendisk結構體變數 */
add_disk(yl_ramdisk.gendisk);


完整的程式程式碼實現如下所示:
#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/mutex.h>
#include <linux/blkpg.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/gfp.h>

#include <asm/uaccess.h>
#include <asm/dma.h>

/* 定義ramdisk的大小為2MB */
#define YL_RAMDISK_SIZE		((2) * (1024) * (1024))

/* 定義ramdisk塊裝置的名稱 */
#define YL_DEVICE_NAME		"yl_ramdisk"

/* 定義一個yl_ramdisk_t的結構體,封裝驅動程式使用的各種資料 */
struct yl_ramdisk_t
{
	int major;				// 主裝置號
	unsigned char *ramdisk_buffer;		// 在記憶體中分配的緩衝區的儲存區
	struct gendisk *gendisk;		// 定義一個gendisk結構體變數指標變數
	struct request_queue *queue;		// 定義一個請求佇列的結構體指標變數
};

/* 定義一個yl_ramdisk結構體的全域性變數 */
static struct yl_ramdisk_t yl_ramdisk;

/* 模擬機械硬碟,為其設定磁頭、柱面、扇區等的大小 */
static int yl_ramdisk_getgeo(struct block_device *bdev, struct hd_geometry *geo)
{
	/* 磁碟大小 =  heads*cylinders*sectors*512 */
	geo->heads = 4;
	geo->cylinders = 32;
	geo->sectors = YL_RAMDISK_SIZE/geo->heads/geo->cylinders/512;
	return 0;
}

/* 定義塊裝置的操作函式結構體變數 */
static const struct block_device_operations yl_ramdisk_fops = {
	.owner	= THIS_MODULE,
	.getgeo = yl_ramdisk_getgeo,
};

/* 定義一個自旋鎖,用於分配一個佇列 */
static DEFINE_SPINLOCK(ramdisk_lock);

/* 定義佇列處理函式 */
static void do_yl_ramdisk_request(struct request_queue *q)
{
	struct request *req;

	/* 從求情佇列裡面獲得一個請求 */
	req = blk_fetch_request(q);
	while (req) 
	{
		unsigned long offset = blk_rq_pos(req) << 9;	// 獲取ramdisk的偏移值
		unsigned long len  = blk_rq_cur_bytes(req);		// 獲取傳輸資料的大小
		int err = 0;

		/* 根據讀寫來決定資料傳輸方向 */
		if (rq_data_dir(req) == READ)
			memcpy(req->buffer, (char *)(yl_ramdisk.ramdisk_buffer + offset), len);
		else
			memcpy((char *)(yl_ramdisk.ramdisk_buffer + offset), req->buffer, len);

		/* 判斷是否是佇列尾部,如果不是再次獲得一個請求 */
		if (!__blk_end_request_cur(req, err))
			req = blk_fetch_request(q);
	}
}

/* 定義入口函式 */
static int __init yl_ramdisk_init(void)
{
	/* 1、分配一個gendisk結構體變數 */
	yl_ramdisk.gendisk = alloc_disk(8);	/* 分割槽數 + 1 =  minors*/	
	if (!yl_ramdisk.gendisk)
	{
		printk("alloc_disk error!\n");
		return -ENOMEM;
	}

	/* 2、設定分配的gendisk結構體變數 */
	/* 2.1 分配一個佇列,提供讀寫能力 */
	yl_ramdisk.queue = blk_init_queue(do_yl_ramdisk_request, &ramdisk_lock);
    if (!yl_ramdisk.queue)
    {
    	put_disk(yl_ramdisk.gendisk);
		printk("blk_init_queue error!\n");
		return -ENOMEM;
	}

	/* 2.2 設定其他相關屬性 */
	yl_ramdisk.major = register_blkdev(0, "yl_ramdisk");	// 註冊塊裝置,獲取主裝置號
	
	yl_ramdisk.gendisk->major 		= yl_ramdisk.major;		// 設定主裝置號
	yl_ramdisk.gendisk->first_minor = 0;					// 設定次裝置號的起始為0
	yl_ramdisk.gendisk->queue		= yl_ramdisk.queue;		// 設定佇列
	yl_ramdisk.gendisk->fops 		= &yl_ramdisk_fops;		// 塊裝置操作函式集合
	sprintf(yl_ramdisk.gendisk->disk_name, YL_DEVICE_NAME);	// 設定名字
	set_capacity(yl_ramdisk.gendisk, YL_RAMDISK_SIZE / 512);		// 設定ramdisk的大小

	/* 3、硬體相關的操作 */
	yl_ramdisk.ramdisk_buffer = kzalloc(YL_RAMDISK_SIZE, GFP_KERNEL);
	if (!yl_ramdisk.ramdisk_buffer) {
		unregister_blkdev(yl_ramdisk.major, YL_DEVICE_NAME);
		blk_cleanup_queue(yl_ramdisk.queue);
		put_disk(yl_ramdisk.gendisk);
		printk("kzalloc error!\n");
		return -ENOMEM;
	}

	/* 4、註冊這個gendisk結構體變數 */
	add_disk(yl_ramdisk.gendisk);

	return 0;
}

/* 定義出口函式 */
static void __exit yl_ramdisk_exit(void)
{
	/* 將入口函式分配的資源依次釋放掉 */
	del_gendisk(yl_ramdisk.gendisk);
	put_disk(yl_ramdisk.gendisk);
	blk_cleanup_queue(yl_ramdisk.queue);
	unregister_blkdev(yl_ramdisk.major, YL_DEVICE_NAME);

	kfree(yl_ramdisk.ramdisk_buffer);
}

module_init(yl_ramdisk_init);
module_exit(yl_ramdisk_exit);

MODULE_LICENSE("GPL");