編寫簡單的ramdisk(無請求隊列)


  最近在研究塊設備驅動的編寫,看了趙磊大牛的《寫一個塊設備驅動》,受益匪淺,雖然能看懂里面說的,但動手寫寫代碼還是能加深理解的,下面實現的ramdisk寫的很簡單,如果有錯誤,歡迎大牛們指正哈!

分配一塊內存區存放ram disk數據

 

  為了簡單,我直接靜態分配了一個大小為16MB的內存區,當然對於編寫驅動來說這個空間有點大。不過就是為了簡單嘛,可以理解。

 

#define SIMP_BLKDEV_BYTES            (16 * 1024 * 1024)
unsigned char simp_blkdev_data[SIMP_BLKDEV_BYTES];

 

分配一個請求隊列

  可以通過函數blk_alloc_queue分配一個默認的請求隊列,用該方法生成的請求對面沒有設置默認的IO調度器。如果調用blk_init_queue函數分配一個請求隊列,會設置默認的IO調度器。因為是編寫ram disk,不需要訪問外部設備,所以不需要使用IO調度器,故使用blk_alloc_queue來分配一個請求隊列。

 

simp_blkdev_queue = blk_alloc_queue(GFP_KERNEL);

 

設置自己的make_request_fn函數

 

  blk_alloc_queue分配的請求隊列中make_request_fn是沒有被賦值的,這也導致了前面說的不會使用默認的IO調度器,那么我們就必須自己實現這個函數,因為上層代碼向請求隊列發生請求時都是通過這個函數來完成的。因為我們使用內存來模擬塊設備,所以其實連請求隊列都不需要,上面分配它僅僅為了讓上層代碼能夠使用請求隊列中的make_request_fn函數,否則上層代碼會不知道去哪里調用make_request_fn

 

  對於上層代碼發出的請求,可以直接用make_request_fn函數來完成請求並直接將結果返回給上層的代碼。具體如下:

 

static void simp_blkdev_make_request(struct request_queue *q, struct bio *bio)
{
    struct bio_vec *bvec;
    int i;
    void *dsk_mem;
    ...
    dsk_mem = simp_blkdev_data + (bio->bi_sector << 9);
    /* 遍歷 bio 中所有的 bvec */
    bio_for_each_segment(bvec, bio, i)
    {
        void *iovec_mem;

        switch(bio_rw(bio))
        {
            case READ:
            case READA:             /* 讀和預讀都進行同樣的處理 */
                /* 用 kmalloc 將請求頁映射到非線性映射區域進行
                    * 訪問,這種方法主要是為了兼容高端內存,
                    * (bvec->bv_page 可能源於高端內存) */
                iovec_mem = kmap(bvec->bv_page) + bvec->bv_offset;
                memcpy(iovec_mem, dsk_mem, bvec->bv_len);
                kunmap(bvec->bv_page);
                break;
            case WRITE:
                    iovec_mem = kmap(bvec->bv_page) + bvec->bv_offset;
                    memcpy(dsk_mem, iovec_mem, bvec->bv_len);
                    kunmap(bvec->bv_page);
                    break;

            default:
                    printk(KERN_ERR SIMP_BLKDEV_DISKNAME
                            ":Unknown value of bio_rw: %lu\n",
                            bio_rw(bio));
                    bio_endio(bio, -EIO);
                    return ;
            }
        dsk_mem += bvec->bv_len;
    }
    bio_endio(bio, 0);
}

 

  上面代碼之間通過判斷bio中的請求類型來判斷具體的操作。bio_endio是用來返回給調用者make_request_fn執行結果的。有了上面自定義的make_request_fn,我們還要把該函數的地址賦值給請求隊列中的make_request_fn,這樣上層代碼就可以使用我們自定義的這個函數了,這個可以通過函數blk_queue_make_request來完成,該函數的作用就是為請求隊列綁定make_request_fn方法。

  從上面的代碼中可以看出,我們直接在make_request_fn中完成了上層代碼的請求,但通常的方法make_request_fn僅僅是更加上層的請求生成request結構,並將該request插入到請求隊列中,再由請求隊列中的request_fn來完成請求隊列中的請求。為什么要把上層的請求先插入到請求隊列中,而不是像我們上面那樣直接處理請求呢?原因是這些請求大多數都是涉及到慢速的磁盤操作,緩存這些請求到請求隊列中有利於合並相鄰的請求和排序請求(IO調度程序要做的事情),這樣有利於減少磁盤尋道的時間,大家都知道磁盤的尋道時間是非常慢的。而在這里由於我們涉及的只是內存操作,所有就沒有必須用這么復雜的機制了,直接像處理字符設備請求那樣,來一個請求就處理一個。

 

分配一個gendisk

  每個塊設備都對應一個gendisk實例,這里也不例外,我們必須把為內存分配一個gendisk結構來把內存模擬為一個塊設備。可以直接調用函數alloc_disk來分配一個gendisk結構。現在的問題是給設備分配一個什么設備號,可以可以隨便選一個呢?顯然是不行的,由於很多的設備號linux已經欲分配給了特定的設備,如果恰好選擇了當前系統正在使用的設備號作為我們的設備號,那很顯然最后會找不到我們的驅動程序,所以我們必須選一個系統一般都不用的設備號,打開linux/include/linux/major.h文件,我們會發現COMPAQ_SMART2_MAJORCOMPAQ_SMART2_MAJOR78個之多的設備號,並且貌似這個設備號很少使用,所以我們就可以選着它了。有了gendisk結構,接下來就是根據我們的需要初始化這個結構了,具體如下:

 

/* 分配一個 gendisk 結構 */
        simp_blkdev_disk = alloc_disk(1);
     /* 填充 gendisk 主要結構成員 */
        strcpy(simp_blkdev_disk->disk_name, SIMP_BLKDEV_DISKNAME);
        simp_blkdev_disk->major = SIMP_BLKDEV_DEVICEMAJOR;
        simp_blkdev_disk->first_minor = 0;
        simp_blkdev_disk->fops = &simp_blkdev_fops;
        simp_blkdev_disk->queue = simp_blkdev_queue;
        set_capacity(simp_blkdev_disk, SIMP_BLKDEV_BYTES >> 9);

 

  simp_blkdev_disk->disk_name設置磁盤的名字,這里我設置為cc這個名字最后會作為設備文件名字出現在/dev目錄中,方便我們最后讀寫這個設備文件。

  simp_blkdev_disk->major是分配給我們這個虛擬設備的設備號,我選的是COMPAQ_SMART2_MAJOR,當然也可以選擇其他的。simp_blkdev_disk->first_mino表示分配給設備的第一個從設備號,一般都是從0開始。simp_blkdev_disk->fops是根具體設備相關的底層函數集,由於我們用的是內存,所以這個只有簡單的使用下面定義就行:

struct block_device_operations simp_blkdev_fops = 
{
        .owner = THIS_MODULE,
};

simp_blkdev_disk->queue就是前面創建的請求隊列,set_capacity設置我們虛擬的這個設備的大小,以扇區為單位。

  到這里一切都准備就緒了,最后只要使用add_disk函數向內核注冊我們虛擬的塊設備就行了。

 

測試

  為了運行上面寫得ram disk代碼,最好的辦法就是使用模塊了。將上面的代碼封裝到模塊中很簡單,把創建請求隊列和gendisk的代碼都放在模塊初始化代碼中,把釋放請求隊列和gendisk的代碼放在模塊退出代碼中。

 

static void __exit simp_blkdev_exit(void)
{
        del_gendisk(simp_blkdev_disk);        /* 刪除 gendisk 結構 */
        put_disk(simp_blkdev_disk);        /* 釋放一個該對象的引用 */
        blk_cleanup_queue(simp_blkdev_queue);    /* 清理請求隊列 */
}

 

  一切都准備就緒了,現在直接編譯模塊,將模塊插入到內核中就可以了。在將模塊插入到內核后,可以查看系統消息:

  再查看/dev目錄下,我們可以看到目錄下多了cc這個文件。

 

  好了,現在就相當於我們有了一個塊設備,大小為16MB,現在可以做的事就是格式化這個設備為ext4文件系統。

 

  接下來我們就可以將這個文件系統掛載到某個目錄上進行操作了,我們可以在這個文件系統中創建文件,目錄等。

 

 

 

 

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM