歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> Linux編程

Linux驅動開發之塊設備初入門

1、塊設備
  塊設備將數據按照固定塊大小的塊中,每個塊的大小通常在512字節到32768字節之間,磁盤、SD卡都是常見的塊設備。

2、字符設備和塊設備的區別:
字符設備 塊設備
----------------------------------------------
按字節訪問   按塊進行訪問
只能按照數據流訪問   隨機訪問
直接訪問設備   掛在文件系統的方式訪問

3、Linux塊設備處理模型

|-------------------------------------------------------------|
|                          VFS                              |
|-------------------------------------------------------------|
|    |                                        |            |
|Disk Caches                                    |            |
|    |                                        |            |
|-------------------------------------------------------------|
|Disk Filesystem  |  Disk Filesystem  |  Block Device File    |
|-----------------                    -----------------------|
|                    Mapping Layer                          |
|-------------------------------------------------------------|
|                  Generic Block Layer                      |
|-------------------------------------------------------------|
|                  I/O Scheduler Layer                        |
|-------------------------------------------------------------|
|    Block Device Driver      |      Block Device Driver      |
|------------------------    |        --------------------  |
|      Hard Disk            |            Hard Disk          |
|------------------------------------------------------------—|

VFS:虛擬文件系統,VFS是對各種具體文件系統的一種封裝,為用戶程序提供訪問文件的統一接口。

Disk Cache:當用戶發起文件訪問請求的時候,首先會到Disk Cache中尋找文件是否被緩存,如果緩存中有則在Cache中讀取,如果數據沒有被緩存,那就必須到文件系統中去讀取。

Mapping Layer:
  1、首先確定文件系統的block size,然後計算所請求的數據包含多少個block,
  2、調用具體文件系統的函數來訪問文件的inode,確定所請求數據在磁盤上面的邏輯地址。

Generic block layer:
  Linux內核為快設備抽象成了統一的模型,把塊設備看做是若干個扇區組成的數據空間。來完成塊設備的相關核心功能。

I/O scheduler layer:
  I/O調度層負責將I/O操作順序,采用電梯算法。提高 I/O 調度器的效率也是影響整個系統對塊設備上數據管理效率的一個方面。

Block Device Driver:
  快設備驅動程序,完成和硬件的具體交互,塊設備相關數據結構

4、設備描述 gendisk結構體
  內核使用 gendisk 結構來表示一個獨立的磁盤設備,內核還使用 gendisk 結構來表示分區,在此結構中,很多程序必須由驅動程序來進行初始化。該結構體定義<linux/genhd.h>中。

4.1、gendisk結構體解析
    struct gendisk {
        int major;                    /*設備主設備號*/
        int first_minor;            /*起始次設備號*/
        int minors;                    /*次設備號的數量,也稱為分區數量,如果改值為1,表示無法分區*/
        char disk_name[32];            /*設備名稱*/
        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個扇區,描述設備容量*/
        ....
    };

    struct gendisk *alloc_disk(int minors)
    分配一個gendisk結構,minors為此設備好的個數 = 分區數+1

    void del_gendisk(struct gendisk *disk)
    當不需要這個磁盤的時候,釋放gendisk結構

    endisk中包含一個kobject成員, 它是一個可被引用計數的結構體。通過get_disk()和put_disk()函數可用來操作引用計數。驅動通常不需要做這個。
    struct kobject *get_disk(struct gendisk *disk);
    void put_disk(struct gendisk *disk);

    void add_disk(struct gendisk *gd);
    對gendisk初始化之後還不能使用和這個設備,應該使用add_disk注冊磁盤設備

    static inline void set_capacity(struct gendisk *disk, sector_t size)
    設置gendisk容量,塊設備中最小單位是扇區,扇區的大小一般是2的整數倍,最常見的大小是512kb (xx>>9)表示/512 (xx<<9)表示*512。扇區大小是物理設備所決定的。
 

5、block_device_operationse結構體
    類似與字符設備驅動程序中的file_operations結構,該集合用於控制設備的操作,但是在大多數情況下都是以mount的方式進行訪問,用戶程序一般不會直接訪問塊設備中的文件。需要包含<linux/blkdev.h>頭文件。
    struct block_device_operations {
        /*打開這個設備時候調用*/
        int (*open) (struct inode *, struct file *);
       
        /*關閉/釋放 時候調用*/
        int (*release) (struct inode *, struct file *);
       
        /*ioctl()系統調用的實現,塊設備包含大量的標准請求,這些標准請求由 Linux 塊設備層處理,因此大部分塊設備驅動的*/
        int (*ioctl) (struct inode *, struct file *, unsigned, unsigned long);
       
        long (*unlocked_ioctl) (struct file *, unsigned, unsigned long);
        long (*compat_ioctl) (struct file *, unsigned, unsigned long);
        int (*direct_access) (struct block_device *, sector_t, unsigned long *);
       
        /*內核周期調用檢查驅動器介質有沒有發生改變,改變返回非0,沒有改變返回0。用於支持可移動設備,非可以移動設備不用實現*/
        int (*media_changed) (struct gendisk *);
       
        /*被調用響應介質被改變,驅動進行必要的工作*/
        int (*revalidate_disk) (struct gendisk *);
       
        /*根據驅動器的幾何信息填充hd_geometry,包含磁頭,柱面,扇區等信息.*/
        int (*getgeo)(struct block_device *, struct hd_geometry *);
       
        /*模塊擁有者,一般初始化為THIS_MODULE*/
        struct module *owner;
    };
    block_device_operations 結構中沒有實際讀或寫數據的函數,在塊 I/O 子系統中,這些操作由請求函數處理。

6、request 和 bio 結構體

    struct request {
        struct list_head queuelist;/*請求鏈表*/
        struct list_head donelist;
        request_queue_t *q;          /*請求所屬隊列*/
        unsigned int cmd_flags;
        enum rq_cmd_type_bits cmd_type;
        sector_t sector;        /* 當前扇區 */
        sector_t hard_sector;    /*要傳輸的下一個扇區*/
        unsigned long nr_sectors;/* 要傳輸的扇區數目*/
        unsigned long hard_nr_sectors;  /*要被完成扇區的數目 */
        unsigned int current_nr_sectors;/*當前傳送的扇區*/
        unsigned int hard_cur_sectors;  /*當前要被完成的扇區數目*/
        struct bio *bio;    /*請求的block i/o(bio)結構體鏈表首 request請求 第一個bio*/
        struct bio *biotail;/*請求的bio結構體鏈表尾 request請求 最後個bio*/
        char *buffer; /*request請求中斷 第一個bio*/
        int ref_count;/*引用計數*/
        .....................
    };

    struct request_queue
    {
        ..........................
        /*自旋鎖 保護隊列結構*/
        spinlock_t        __queue_lock;
        spinlock_t        *queue_lock;

        /*kobject隊列 */
        struct kobject kobj;

        /* queue settings */
        unsigned long nr_requests;        /*最大的請求數量*/
        unsigned int  nr_congestion_on;
        unsigned int  nr_congestion_off;
        unsigned int  nr_batching;
        unsigned short max_sectors;        /*最大扇區數*/
        unsigned short max_hw_sectors;
        unsigned short max_phys_sectors;/*最大的段數*/
        unsigned short max_hw_segments;
        unsigned short hardsect_size;    /*硬件扇區尺寸*/
        unsigned int max_segment_size;    /*最大的段尺寸*/
        unsigned long seg_boundary_mask;/*段邊界掩碼*/
        unsigned int dma_alignment;        /*DMA傳送內存對齊限制*/
        struct blk_queue_tag* queue_tags;
        atomic_t refcnt;                /*引用計數*/
        unsigned int in_flight;
        unsigned int sg_timeout;
        unsigned int sg_reserved_size;
        int node;
        struct list_head drain_list;
        struct request* flush_rq;
        unsigned char ordered;
    };
   
    關於request_queue的操作:
    /*初始化請求隊列*/
   
    kernel elevator = deadline;/*給kernel添加啟動參數*/
   
    request_queue_t *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock)
    /*
    *兩個參數分別是請求處理函數指針 控制隊列訪問權限的自旋鎖
    *此函數會分配內存,需要判斷返回值,在加載函數中調用
    */
   
   
   
    /*清除請求隊列*/
    void blk_cleanup_queue(request_queue_t * q)
    /*
    * 此函數完成將請求隊列返回給系統的任務,一般在卸載函數中調用.
    * 此函數即bld_put_queue()的宏定義#define blk_put_queue(q) blk_cleanup_queue((q))
    */
   
   
   
    /*分配請求隊列*/
    request_queue_t *blk_alloc_queue(gfp_t gfp_mask)
   
    void blk_queue_make_request(request_queue_t * q, make_request_fn * mfn)
    /*
    * 前一個函數用於分配一個請求隊列,後一個函數是將請求隊列和"制造函數"進行綁定
    * 但函數blk_alloc_queue實際上並不包含任何請求.
    */
   
    /*去除請求*/
    void blkdev_dequeue_request(struct request* req);
    void elv_requeue_request(request_queue_t* queue, struct request* req);
   
    /*啟停請求*/
    void blk_stop_queue(request_queue_t* queue);
    void blk_start_queue(request_queue_t* queue);
   
    //參數設置
    void blk_queue_max_sectors(request_queue_t* q, unsigned short max);
    /*請求可包含的最大扇區數.默認255*/
   
    void blk_queue_max_phys_segments(request_queue_t* q, unsigned short max);
    void blk_queue_max_hw_segments(request_queue_t* q, unsigned short max);
    /*這兩個函數設置一個請求可包含的最大物理段數(系統內存中不相鄰的區),缺省是128*/
   
   
    void blk_queue_max_segment_size(request_queue_t* q, unsigned int max);
    /*告知內核請求短的最大字節數,默認2^16 = 65536*/
   
    //通告內核
    void blk_queue_bounce_limit(request_queue_t* queue, u64 dma_addr);
    /*
    * 此函數告知內核設備執行DMA時,可使用的最高物理地址dma_addr,常用的宏如下:
    * BLK_BOUNCE_HIGH:對高端內存頁使用反彈緩沖(缺省)
    * BLK_BOUNCE_ISA:驅動只可以在MB的ISA區執行DMA
    * BLK_BOUNCE_ANY:驅動可在任何地方執行DMA
    */   
   
    blk_queue_segment_boundary(request_queue_t* queue, unsigned long mask);
    /*這個函數在設備無法處理跨越一個特殊大小內存邊界的請求時,告知內核這個邊界.*/
   
    void blk_queue_dma_alignment(request_queue_t* q, int mask);
    /*告知內核設備加於DMA傳送的內存對齊限制*/
   
    viod blk_queue_hardsect_size(request_queue_t* q, unsigned short max);
    /*此函數告知內核塊設備硬件扇區大小*/
   
   
    塊IO(bio)結構體:
        通常一個bio對應一個IO請求,IO調度算法可以將連續的bio合並成一個請求,所以一個請求包含多個bio
   
    struct bio {
        sector_t        bi_sector;    /* device address in 512 byte  sectors */
        struct bio        *bi_next;    /* request queue link */
        struct block_device    *bi_bdev;
       
        /*如果是一個寫請求,最低有效位被置位,可使用bio_data_dir(bio)宏來獲取讀寫方向*/
        unsigned long        bi_flags;    /* status, command, etc */
        unsigned long        bi_rw;        /* 低位代表:READ/WRITE,高位代表:優先級 */

        unsigned short        bi_vcnt;    /* how many bio_vec's (bio_vec的數量)*/
        unsigned short        bi_idx;        /* current index into bvl_vec(當前bio_vec的索引) */

        /*不相鄰的物理段的數目*/
        unsigned short        bi_phys_segments;
       
        /*物理合並和DMA remap合並後不相鄰的物理扇區*/   
        unsigned short        bi_hw_segments;

        /*被傳送的數據大小(byte),用bio_sector(bio)獲取扇區為單位的大小*/
        unsigned int        bi_size;    /* residual I/O count */

        /*被傳送的數據大小(byte),用bio_sector(bio)獲取扇區為單位的大小*/
        /*為了明了最大的hw尺寸,考慮bio中第一個和最後一個虛擬的可合並的段的尺寸*/
        unsigned int        bi_hw_front_size;
        unsigned int        bi_hw_back_size;

        unsigned int        bi_max_vecs;    /*能持有的最大bvl_vecs數*/

        struct bio_vec        *bi_io_vec;    /*能持有的最大bvl_vecs數*/
        ...................
    }
   
   
    struct bio_vec {
    struct page        *bv_page; /*要操作的頁指針*/
    unsigned int    bv_len;      /*要傳輸的字節數*/
    unsigned int    bv_offset;/*偏移位置*/
    };
   
    /*一般不直接訪問bio的bio_vec成員,而使用bio_for_each_segment()宏進行操作.
    *該宏循環遍歷整個bio中的每個段.
    */
    #define __bio_for_each_segment(bvl, bio, i, start_idx)\
            for(
                bvl = bio_iovec_idx((bio),(start_idx)),i = (start_idx);\
                i <(bio)->bi_vcnt;\
                bvl++, i++\
            )
    #define bio_for_each_segment(bvl, bio, i)\
              __bio_for_each_segment(bvl, bio, i, (bio)->bi_idx)
   
    在內核中,提供了一組函數(宏)用於操作bio:
    int bio_data_dir(struct bio* bio);
   
    該函數用於獲得數據傳送方向.
    struct page* bio_page(struct bio* bio);
   
    該函數用於獲得目前的頁指針.
    int bio_offset(struct bio* bio);
   
    該函數返回操作對應的當前頁的頁內偏移,通常塊IO操作本身就是頁對齊的.
    int bio_cur_sectors(struct bio* bio);
   
    該函數返回當前bio_vec要傳輸的扇區數.
    char* bio_data(struct bio* bio);
   
    該函數返回數據緩沖區的內核虛擬地址.
    char* bvec_kmap_irq(struct bio_vec* bvec, unsigned long* offset);
    該函數也返回一個內核虛擬地址此地址可用於存取被給定的bio_vec入口指向的數據緩沖區.同時會屏蔽中斷並返回一個原子kmap,因此,在此函數調用之前,驅動不應該是睡眠狀態.
    void bvec_kunmap_irq(char* buffer, unsigned long flags);
   
    該函數撤銷函數bvec_kmap_irq()創建的內存映射.
    char* bio_kmap_irq(struct bio* bio, unsigned long* flags);
   
    該函數是對bvec_kmap_irq函數的封裝,它返回給定的比偶的當前bio_vec入口的映射.
    char* __bio_kmap_atomic(struct bio* bio, int i, enum km_type type);
   
    該函數是通過kmap_atomic()獲得返回給定bio的第i個緩沖區的虛擬地址.
    void __bio_kunmap_atomic(char* addr, enum km_type type);
   
    該函數返還由函數__bio_kmap_atomic()獲得的內核虛擬地址給系統.
    void bio_get(struct bio* bio);
    void bio_put(struct bio* bio);
7、塊設備注冊與取消

    塊設備驅動的第一個任務就是將他們自己注冊到內核中,其函數原型如下:
   
    ☆int register_blkdev(unsigned int major, const char* name);   
    major參數是塊設備要使用的主設備號,name為設備名,它會在/proc/devices中被現實.如果major為0,內核會自動分配一個新的主設備號,並由該函數返回.如果返回值為負值,則說明設備號分派失敗.
    注銷函數是unregister_blkdev(),原型如下:

    ☆int unreister_blkdev(unsigned int major, const char* name);
       
    這裡unreister_blkdev與register_blkdev的參數必須匹配,否則這個函數會返回-EINVAL.    在Linux2.6中,對register_blkdev的調用是可選的.register_blkdev這個調用在Linux2.6中只完成了兩件事情:
    ①如果需要,分派一個主設備號;
    ②在/proc/devices中創建一個入口.
 

8、塊設備驅動程序編寫模板

塊驅動中相關相關模塊模板

1.塊設備驅動的模塊加載與卸載

    1)塊設備驅動的模塊加載完成的工作如下:

        ☆ 分配,初始化請求隊列,綁定請求隊列和請求函數

        ☆ 分配,初始化gendisk,給gendisk的major,fops,queue等成員賦值,最後添加gendisk.

        ☆ 注冊塊設備驅動.

       

代碼1:使用blk_alloc_queue函數完成塊設備驅動的模塊加載模板

    static int __init xxx_init(void){

        //分配gendisk

        xxx_disks = alloc_disk(1);

        if(!xxx_disks){

            goto out;

        }

        //塊設備驅動注冊

        if(register_blkdev(xxx_MAJOR, "xxx"){

            err = -EIO;

            goto out;

        }

        //"請求隊列"分配

        xxx_queue = blk_alloc_queue(GFP_KERNEL);

        if(!xxx_queue){

            goto out_queue;

        }

        blk_queue_make_request(xxx_queue, &xxx_make_request);//綁定"制造請求"函數

        blk_queue_hardsect_size(xxx_queue,xxx_blocksize);//告知內核硬件扇區尺寸

        //gendisk初始化

        xxx_disks->major = xxx_MAJOR;

        xxx_disks->first_minor = 0;

        xxx_disks->fops = &xxx_fop;

        xxx_disks->queue = xxx_queue;

        sprintf(xxx_disks->disk_name, "xxx%d", i);

        set_capacity(xxx_disks, xxx_size);//設置gendisk容量為xxx_size個扇區大小

        add_disk(xxx_disks);

        return 0;

        out_queue:unregister_blkdev(xxx_MAJOR, "xxx");

        out:put_disk(xxx_disks);

        blk_cleanup_queue(xxx_queue);

        return -ENOMEM;

    }

代碼2:使用blk_init_queue函數完成塊設備驅動的模塊加載模板

    static int __init xxx_init(void){

        //塊設備驅動注冊

        if(register_blkdev(xxx_MAJOR, "xxx"){

            err = -EIO;

            goto out;

        }

        //請求隊列初始化

        xxx_queue = blk_init_queue(xxx_request, xxx_lock);

        if(!xxx_queue){

            goto out_queue;

        }

        blk_queue_hardsect_size(xxx_queue, xxx_blocksize);//告知內核硬件扇區大小

        //gendisk初始化

        xxx_disks->major = xxx_MAJOR;

        xxx_disks->first_minor = 0;

        xxx_disks->fops = &xxx_fop;

        xxx_disks->queue = xxx_queue;

        sprintf(xxx_disks->disk_name, "xxx%d", i);

        set_capacity(xxx_disks, xxx_size*2);//設置gendisk容量為xxx_size個扇區大小

        add_disk(xxx_disks);

        return 0;

        out_queue:unregister_blkdev(xxx_MAJOR, "xxx");

        out:put_disk(xxx_disks);

        blk_cleanup_queue(xxx_queue);

        return -ENOMEM;

    }

2)塊設備驅動的模塊卸載完成的工作如下:

    ☆ 清除請求隊列.

    ☆ 刪除gendisk和gendisk的引用

    ☆ 刪除對塊設備的引用,注銷塊設備驅動.

   

代碼3:塊設備驅動模塊卸載函數模板

    static void __exit xxx_exit(void){

        if(bdev){

            invalidate_bdev(xxx_bdev, 1);

            blkdev_put(xxx_bdev);

        }

        del_gendisk(xxx_disks);//刪除gendisk

        put_disk(xxx_disks);

        blk_cleanup_queue(xxx_queue[i]);//清除請求隊列

        unregister_blkdev(xxx_MAJOR, "xxx");

    }

2.塊設備驅動的打開與釋放

塊設備驅動的open()和release()函數不是必須的,一個簡單的塊設備驅動可以不提供open()和release()函數.

塊設備驅動的open()函數和字符設備驅動的open()和類似,都以相關inode和file結構體指針作為參數,當一個結點引用一個塊設備時,inode->i_bdev->bd_disk包含一個指向關聯gendisk的結構體的指針.因此類似字符設備,可將gendisk的private_data賦給file的private_data,private_data同樣最好是指向描述該設備的設備結構體xxx_dev的指針.如下面的代碼:

    static int xxx_open(struct inode* inode, struct file* file){

        struct xxx_dev* dev = inode->i_bdev->db_disk->private_data;

        file->private_data = dev;

        ...

        return 0;

    }

3.塊設備驅動的ioctl

塊設備可以包含一個ioctl()函數,以提供對該設備的IO控制,實際上搞成的塊設備層代碼處理了絕大多數ioctl(),因此具體的塊設備驅動中,通常不在需要實現很多ioctl()命令.下面的代碼中只實現一個命令HDIO_GETGEO,用於獲得磁盤的幾何信息(geometry,指CHS,即Cylinder, Head, Sector/Track).

    static int xxx_ioctl(struct inode* inode, struct file* file,\

                            unsigned int cmd, unsigned long arg){

        long size;

        struct hd_geometry geo;

        struct xxx_dev* dev = file->private_data;

        switch(cmd){

            case HDIO_GETGEO:

                size = dev->size * (hardsect_size / KERNEL_SECTOR_SIZE);

                geo.cylinders = (size & ~0x3f) >> 6;

                geo.heads = 4;

                geo.sectors = 16;

                if(copy_to_user((void __user*)arg, &geo, sizeof(geo)){

                    return -EFAULT;

                }

                return 0;

        }

        return -ENOTTY;//未知命令

    }

4.塊設備驅動的I/O請求

☆ 使用請求隊列

塊設備驅動請求函數的原型為:

    void request(request_queue_t* q);

這個函數不能由驅動自己調用,只有當內核認為是時候讓驅動處理對設備的讀寫等操作時,它才會調用這個函數.請求函數可以在沒有完成請求隊列中的所有請求的情況下返回,甚至它一個請求不完成都可以返回.但對大部分設備而言,一般會在請求函數中處理完所有請求後才返回.

    static void xxx_request(request_queue_t* q){

        struct request* req;

        //elv_next_request()用於獲取隊列中第一個未完成的請求

        //end_request()會將請求從請求隊列中剝離

        while((req = elv_next_request(q)) != NULL){

            struct xxx_dev* dev = req->rq_disk->private_data;

            if(!blk_fs_request(req)){//如果不是文件系統請求,直接清除,調用end_request().

                printk(KERN_NOTICE "Skip non-fs request\n");

                end_request(req, 0);//通知請求處理失敗.第二個參數0代表請求失敗.

                continue;

            }

            xxx_transfer(dev, req->sector, req->current_nr_sectors, req->buffer,\

                rq_data_dir(req));//處理這個請求.

            end_request(req, 1);//通知成功完成這個請求.1,表示請求成功.

        }

    }

    static void xxx_transfer(struct xxx_dev* dev, unsigned long sector,\

        unsigned long nsect, char* buffer, int write){

            unsigned long offset = sector * KERNEL_SECTOR_SIZE;

            unsigned long nbytes = nsect * KERNEL_SECTOR_SIZE;

            if((offset + nbytes) > dev->size){

                printk(KERN_NOTICE "Beyond-end write (%ld %ld)\n", offset, nbytes);

                return ;

            }

            if(write)

                write_dev(offset, buffer, nbytes);//向設備寫nbytes個字節的數據.

            else

                read_dev(offset, buffer, nbytes);//從設備讀取nbytes個字節的數據.

    }

下面是end_that_request_first()的源碼和分析

    //end_request()源碼清單

    void end_request(struct request* req, int uptodate){

        //當設備完成一個IO請求的部分或全部扇區傳輸後,必須告知塊設備層.end_that_request_first

        //原型為:int end_that_request_first(struct request* req, int success, int count);

        //此函數高數塊設備層,已經完成count各扇區的傳送.返回表示所有扇區傳送完畢.

        if(!end_that_request_first(req, uptodate, req->hard_cur_sectors)){

            //add_disk_randomness()作用是使用塊IO請求的定時來給系統的隨機數池貢獻熵,它不影響

            //塊設備,但僅當磁盤的操作時間是真正隨機的時候,才調用它.

            add_disk_randomness(req->rq_disk);

            blkdev_dequeue_request(req);//清除此請求.

            end_that_request_last(req);//通知等待此請求的對象,此請求已經完成

        }

    }

下面是一個更復雜的請求函數,分別遍歷了request,bio,以及bio中的segment

    //請求函數遍歷請求,bio和段

    static void xxx_full_request(request_queue_t* q){

        struct request* req;

        int sectors_xferred;

        struct xxx_dev* dev = q->queuedata;

        //XXX 遍歷每個請求

        while((req = elv_next_request(q)) != NULL){

            if(!blk_fs_request(req)){

                printk(KERN_NOTICE "Skip non-fs request\n");

                end_request(req, 0);

                continue;

            }

            sectors_xferred = xxx_xfer_reqeust(dev, req);

            if(!end_that_request_first(req, 1, sectors_xferred)){

                blkdev_dequeue_reqeust(req);

                end_that_request_last(req);

            }

        }

    }

    //XXX 請求處理

    static int xxx_xfer_request(struct xxx_dev* dev, struct reqeust* req){

        struct bio* bio;

        int nsect = 0;

        //遍歷請求中的每個bio

        rq_for_each_bio(bio, req){

            xxx_xfer_bio(dev, bio);

            nsect += bio->bi_size / KERNEL_SECTOR_SIZE;

        }

        return nsect;

    }

    //XXX bio處理

    static int xxx_xfer_bio(struct xxx_dev* dev, struct bio* bio){

        int i;

        struct bio_vec* bvec;

        sector_t sector = bio->bi_sector;

        //遍歷每一個segment

        bio_for_each_segment(bvec, bio, i){

            char* buffer = __bio_kmap_atomic(bio, i, KM_USER0);

            xxx_transfer(dev, sector, bio_cur_sectors(bio), buffer,\

                bio_data_dir(bio) == WRITE);

            sector += bio_cur_sectors(bio);

            __bio_kunmap_atomic(bio, KMUSER0);

        }

        return 0;

    }

☆ 不使用請求隊

對於機械的磁盤設備而言,請求隊列有助於提高系統性能.但對於如SD卡,RAM盤等可隨機訪問的塊設備,請求隊列無法獲益.對於這些設備,塊層支持"無隊列"的操作模式,驅動為此必須提供一個"制造請求"函數(注意:這不是請求函數哦),"制造請求"函數的原型為:

typedef int (make_request_fn) (request_queue_t* q, struct bio* bio);

此函數的第一個參數,是一個"請求隊列",但實際並不包含任何請求.所以主要參數是bio,它表示一個或多個要傳送的緩沖區.此函數或直接進行傳輸,或將請求重定向給其他設備.在處理完成之後,應使用bio_endio()通知處理結束.bio_endio()原型如下:

    void bio_endio(struct bio* bio, unsigned int byetes, int error);

bytes是已經傳送的字節數(注意:bytes≤bio->bi_size),這個函數同時更新了bio的當前緩沖區指針.當設備進一步處理bio後,驅動應再次調用bio_endio(),如不能完成請求,將錯誤碼賦給error參數,並在函數中得以處理.此函數無論處理IO成功與否都返回0,如果返回非零值,則bio將再次被提交:

    static int xxx_make_request(request_queue_t* q, struct bio* bio){

        struct xxx_dev* dev = q->queuedata;

        int status = xxx_xfer_bio(dev, bio);//處理bio

        bio_endio(bio, bio->bi_size, status);//報告結束

        return 0;

    }

說明:這裡要指出,如果是無隊列的IO請求處理,其加載模塊應使用<<代碼1:使用blk_alloc_queue函數完成塊設備驅動的模塊加載模板>>,否則應使用<<代碼2:使用blk_init_queue函數完成塊設備驅動的模塊加載模板>>.

Copyright © Linux教程網 All Rights Reserved