1. 程式人生 > >Linux塊裝置驅動

Linux塊裝置驅動

推薦書:《Linux核心原始碼情景分析》

 

1.字元裝置驅動和使用中等待某一事件的方法
①查詢方式
②休眠喚醒,但是這種沒有超時時間
③poll機制,在休眠喚醒基礎上加一個超時時間
④非同步通知,非同步通知實際上就是發訊號
⑤輸入子系統,這樣比較通用

2.塊裝置相對於字元裝置驅動邏輯的變化
①對於硬碟對讀寫的優化
假如要讀磁頭0的扇區0,然後寫磁頭1的扇區0,然後讀磁頭0的扇區1,若像字元裝置那樣,就會機械山跳轉2次,效率低。
優化:
先不執行,放入佇列,優化後再執行,這裡的優化是指調整順序。

②對於flash
假如要寫同一個塊的扇區0,然後再寫扇區1,若是字元裝置的做法,寫扇區0時需要先把整塊讀取出來,然後修改此塊中
扇區0的資料,然後燒寫整個這個塊。寫扇區1時也是需要先把整塊讀取出來,然後修改此塊中扇區1的資料,然後燒寫整個這個塊。
優化:
先不執行,放入佇列,優化後再執行,這裡的優化是指合併相同塊的寫請求。

所以塊裝置不能向字元裝置一樣直接提供讀寫函式,而是需要先放入佇列之中,優化後再執行。


3.塊裝置驅動程式框架

App:open, read, write "1.txt"
----------------------------------------------- 檔案的讀寫
檔案系統:vfat,tfat, ext2, ext3, yaffs2, jffs2 作用:把檔案的讀寫轉換為扇區的讀寫
------------統一的入口:ll_rw_block()---------- 扇區的讀寫
1.把“讀寫”放入佇列中(可能這一步就有優化)
2.呼叫佇列的處理函式(優化/調整順序/合併)
塊裝置驅動程式: 主要是讀寫塊裝置函式的實現和屬性的提供
-----------------------------------------------
硬體:硬碟,flash,eMMC


4.分析ll_rw_block()

ll_rw_block //fs/buffer.c, 位於fs下,說明是所有檔案系統的一個通用的.c檔案
    for (i = 0; i < nr; i++)
    submit_bh(op, op_flags, bh); //fs/buffer.c
        struct bio *bio;
        通用的構造請求,使用bio來構造請求
        submit_bio(bio);
            generic_make_request(bio);
                struct request_queue *q = bio->bi_disk->queue; //
block/blk-core.c 找對佇列 ret = q->make_request_fn(q, bio); //呼叫佇列的構造請求函式,預設的設定函式是:make_request_fn q->make_request_fn在blk_queue_make_request block/Blk-settings.c中被賦值, blk_queue_make_request在blk_init_allocated_queue block/blk-core.c中被賦值為blk_queue_bio,即make_request_fn=blk_queue_bio blk_queue_bio //預設是這個 elv_merge(q, &req, bio) //block/blk-core.c 以電梯呼叫演算法嘗試合併這個請求 如果合併不成功,呼叫get_request使用bio構造請求,將請求放入佇列中 get_request(q, bio->bi_opf, bio, GFP_NOIO); blk_init_request_from_bio(req, bio); blk_flush_plug_list(plug, false); if (q) queue_unplugged(q, depth, from_schedule); __blk_run_queue q->request_fn(q); //呼叫佇列的處理函式,就是塊裝置驅動實際的讀寫函式

 

5.寫塊裝置驅動
1.分配構造struct request_queue,用於提供讀寫能力
2.裝置描述,提供屬性
......
====>核心指定了一個結構體:gendisk
驅動框架:
1.分配gendisk: alloc_disk
2.設定
2.1 分配/設定struct request_queue結構,blk_init_queue
2.2 設定gendisk其它資訊
3.註冊gendisk

6.塊裝置的操作是以扇區為單位的,核心機制決定的,就算是使用記憶體模擬的塊裝置也不例外。

7.對於一塊全0的記憶體模擬的磁碟,未格式化會報"unknow partation table",因為其分割槽表為空

8.測試
cat /proc/devices 檢視裝置號
格式化磁碟:# mkfs /dev/ramblock eg: mkdosfs /dev/ramdisk
掛載:# mount /dev/ramblock /tmp
之後就可以通過訪問/tmp目錄操作磁碟了

9.建立磁碟映像
# cat /dev/ramblock > /ramblock.bin 然後再格式化磁碟再echo進去應該也是可以的。
在Ubuntu裡面:# sudo mount -o loop ramblock.bin /mnt loop選項可以把一個普通檔案當作塊裝置進行掛載。loop把它當作一個迴環裝置。

10.把對記憶體的操作打印出來可以對比App的讀寫和實際的IO操作發生的時機的區別。

11.分割槽
# ls /dev/ramblock* -l 次裝置號是0表示整個磁碟,不是分割槽。起始是0應該是first_minor=0決定的。
# fdisk /dev/ramblock m n p 1(此時顯示1-32 sylinder就是驅動中在ramblock_getgeo()中配置的) 1 5,再建立一個分割槽n p 2 6 32 w
(w表示將配置寫到分割槽表中,分割槽表就是第一個扇區)
# ls /dev/ramblock* -l 次裝置號是0表示整個磁碟,次裝置號是1表示第一個主分割槽,次裝置號是2表示第二個主分割槽。
此時也可以分別格式化每一個主分割槽:
eg: # mkdosfs /dev/ramblock1
eg: # mkdosfs /dev/ramblock2
分別掛載:
eg: # mount /dev/ramblock1 /mnt
eg: # mount /dev/ramblock2 /tmp
使用# fdisk /dev/ramdisk 報錯: Unknow value(s) for: Cylinder 不知道柱面數,fdisk是個老工具了,使用它需要驅動告訴它柱面數信
息(目前很多塊裝置都不使用這個儲存方式了)

12.記憶體模擬塊裝置驅動程式碼

/* 參考: 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)  /*使用1M記憶體來模擬磁碟*/
static unsigned char *ramblock_buf;

/*這些資訊在使用fdisk工具分割槽的時候會用到*/
static int ramblock_getgeo(struct block_device *bdev, struct hd_geometry *geo)
{
    /* 容量 = heads * cylinders * sectors * 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,
};


/*目前核心中elv_next_request已經不存在了*/
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);

    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) /*也就是: req->cmd_flags & 1*/
        {
            printk("do_ramblock_request read %d\n", ++r_cnt);
            memcpy(req->buffer, ramblock_buf+offset, len); /*這裡是使用memcpy來模擬複雜的IO操作*/
        }
        else
        {
            /*加了這個列印,可以看出來當向裝置進行寫的時候沒有立即呼叫,而是過來一小會才呼叫的,
            與演算法有關,先放到佇列中,然後才執行。

            # cp /etc/fstab /tmp(掛載目錄),發現過來一會也沒有呼叫這個函式
            # sync  立即就列印了

            # cp /etc/fstab /tmp  發現沒有立即寫
            # umount /tmp/ 發現立即列印了
            */
            printk("do_ramblock_request write %d\n", ++w_cnt);
            memcpy(ramblock_buf+offset, req->buffer, len);
        }

        end_request(req, 1);
    }
}

static int ramblock_init(void)
{
    /* 1. 分配一個gendisk結構體 */
    ramblock_disk = alloc_disk(16); /* 次裝置號個數: 分割槽個數+1 */

    /* 2. 設定 */
    /* 2.1 分配/設定佇列: 提供讀寫能力 */
    ramblock_queue = blk_init_queue(do_ramblock_request, &ramblock_lock); /*arg1: 執行實際讀寫io操作的函式*/
    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"); /*區別設定這兩個名字進行測試*/ /*仿照scsi/sd.c sd_format_disk_name測試一下,對比sd卡的,它是什麼/dev/下的裝置節點名就是什麼*/
    ramblock_disk->fops        = &ramblock_fops;
    set_capacity(ramblock_disk, RAMBLOCK_SIZE / 512); /*設定磁碟容量,是以扇區為單位的*/

    /* 3. 硬體相關操作 */
    ramblock_buf = kzalloc(RAMBLOCK_SIZE, GFP_KERNEL); /*這個裝置全為0,分割槽表為空,所以在insmod驅動的時候會報"unknow partation table",需要格式化*/

    /* 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");