1. 程式人生 > >S3C2440 塊裝置驅動程式的編寫驅動之用記憶體模擬硬碟(二十一)

S3C2440 塊裝置驅動程式的編寫驅動之用記憶體模擬硬碟(二十一)

通過上節的塊裝置驅動分析,本節便通過記憶體來模擬塊裝置驅動,方便我們更加熟悉塊裝置驅動框架

參考核心自帶的塊裝置驅動程式:

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