1. 程式人生 > >23.Linux-塊設備驅動(詳解)

23.Linux-塊設備驅動(詳解)

裏的 end opacity 塊設備 dea 獲取request device geometry cat

通過上節的塊設備驅動分析,本節便通過內存來模擬塊設備驅動

參考內核自帶的塊設備驅動程序:

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 request申請結構體:

struct request {  
    //用於掛在請求隊列鏈表的節點,使用函數elv_next_request()訪問它,而不能直接訪問  
struct list_head queuelist;
struct list_head donelist; /*用於掛在已完成請求鏈表的節點*/ struct request_queue *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結構體扇區數(成員copacity), 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結構體的成員
  • ->4.1)設置成員參數(major、first_minor、disk_name、fops)
  • ->4.2)設置queue成員,等於之前分配的申請隊列
  • ->4.3)通過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)使用memcp()來讀或者寫扇區(緩存)
  • 4)使用end_request()來結束獲取的每個申請

3.3在出口函數中

  • 1)使用put_disk()和del_gendisk()來註銷,釋放gendisk結構體
  • 2)使用kfree()釋放磁盤扇區緩存
  • 3)使用blk_cleanup_queue()清除內存中的申請隊列
  • 4)使用unregister_blkdev()卸載塊設備

4.代碼如下:

#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 DEFINE_SPINLOCK(memblock_lock);                //定義自旋鎖
static request_queue_t * memblock_request;                //申請隊列
static struct gendisk   *memblock_disk;                  //磁盤結構體
static int memblock_major;

#define BLOCKBUF_SIZE               (1024*1024)              //磁盤大小
#define SECTOR_SIZE                   (512)                    //扇區大小
static unsigned char   *block_buf;                              //磁盤地址


static int memblock_getgeo(struct block_device *bdev, struct hd_geometry *geo)
{    
       geo->heads =2;                                         // 2個磁頭分區
       geo->cylinders = 32;                                   //一個磁頭有32個柱面
       geo->sectors = BLOCKBUF_SIZE/(2*32*SECTOR_SIZE);      //一個柱面有多少個扇區    
    return 0;
}

static struct block_device_operations memblock_fops = {
       .owner    = THIS_MODULE,
       .getgeo   =  memblock_getgeo,                //幾何,保存磁盤的信息(柱頭,柱面,扇區)
};

    /*申請隊列處理函數*/
static void do_memblock_request (request_queue_t * q)
{
        struct request *req;
        unsigned long offset;
        unsigned long len; 
        static unsigned long r_cnt = 0;
        static unsigned long w_cnt = 0;
              
        while ((req = elv_next_request(q)) != NULL)        //獲取每個申請
        {
        offset=req->sector*SECTOR_SIZE;                     //偏移值
        len=req->current_nr_sectors*SECTOR_SIZE;            //長度    
                      
        if(rq_data_dir(req)==READ)
        {            
            memcpy(req->buffer,block_buf+offset,len);       //讀出緩存
        }
        else
        {              
            memcpy(block_buf+offset,req->buffer,len);     //寫入緩存
        }
        end_request(req, 1);                                            //結束獲取的申請
        }    
}

    /*入口函數*/
static int memblock_init(void)
{
     /*1)使用register_blkdev()創建一個塊設備*/
     memblock_major=register_blkdev(0, "memblock");     
     
     /*2) blk_init_queue()使用分配一個申請隊列,並賦申請隊列處理函數*/
     memblock_request=blk_init_queue(do_memblock_request,&memblock_lock);
    
     /*3)使用alloc_disk()分配一個gendisk結構體*/
     memblock_disk=alloc_disk(16);                        //不分區
    
     /*4)設置gendisk結構體的成員*/
     /*->4.1)設置成員參數(major、first_minor、disk_name、fops)*/           
     memblock_disk->major = memblock_major;
     memblock_disk->first_minor = 0;
     sprintf(memblock_disk->disk_name, "memblock");
     memblock_disk->fops = &memblock_fops;
        
     /*->4.2)設置queue成員,等於之前分配的申請隊列*/
     memblock_disk->queue = memblock_request;
      
     /*->4.3)通過set_capacity()設置capacity成員,等於扇區數*/
     set_capacity(memblock_disk,BLOCKBUF_SIZE/SECTOR_SIZE);
   
     /*5)使用kzalloc()來獲取緩存地址,用做扇區*/
     block_buf=kzalloc(BLOCKBUF_SIZE, GFP_KERNEL);
 
     /*6)使用add_disk()註冊gendisk結構體*/
     add_disk(memblock_disk);   
     return  0;
}
static void memblock_exit(void)
{        
      /*1)使用put_disk()和del_gendisk()來註銷,釋放gendisk結構體*/
      put_disk(memblock_disk);
      del_gendisk(memblock_disk);
/*2)使用kfree()釋放磁盤扇區緩存 */ kfree(block_buf);
/*3)使用blk_cleanup_queue()清除內存中的申請隊列 */ blk_cleanup_queue(memblock_request); /*4)使用unregister_blkdev()卸載塊設備 */ unregister_blkdev(memblock_major,"memblock"); } module_init(memblock_init); module_exit(memblock_exit); MODULE_LICENSE("GPL");

5.測試運行

insmod ramblock.ko                                     //掛載memblock塊設備

mkdosfs /dev/memblock                               //將memblock塊設備格式化為dos磁盤類型

mount /dev/ memblock   /tmp/                    //掛載塊設備到/tmp目錄下

接下來在/tmp目錄下vi 1.txt文件,最終都會保存在/dev/ memblock塊設備裏面

cd /; umount /tmp/                    //退出/tmp,卸載,同時之前讀寫的文件也會消失

cat /dev/memblock > /mnt/memblock.bin   //在/mnt目錄下創建.bin文件,然後將塊設備裏面的文件追加到.bin裏面

然後進入linux的nfs掛載目錄中

sudo mount -o loop ramblock.bin   /mnt      //掛載ramblock.bin, -loop:將文件當做磁盤來掛載

如下圖,就可以找到我們之前在開發板上創建的1.txt了

技術分享

說明這個塊設備測試運行無誤

6.使用fdisk來對磁盤分區

(fdisk命令使用詳解: http://www.cnblogs.com/lifexy/p/7661239.html)

共分了兩個分區,如下圖所示:

技術分享

如下圖,接下來就可以向上小節那樣,分別操作多個分區磁盤了:

技術分享

技術分享

23.Linux-塊設備驅動(詳解)