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