乾坤合一~Linux設備驅動之塊設備驅動

1. 題外話html

  在蛻變成蝶的一系列學習當中,咱們已經掌握了大部分Linux驅動的知識,在乾坤合一的分享當中,以綜合實例爲主要講解,在一個月的蛻繭成蝶的學習探索當中,以爲數據結構,指針,鏈表等等佔據了代碼的大部分框架,這些都須要咱們平時多看代碼,而且在相關知識點的時候須要在電腦上進行操做,這也讓本身受益不淺,筆者在這期間受到了幾家IT學院的邀請錄製視頻,當兼職佈道師。但畢竟本身仍是個學生,應該潛心學習,爭取更好的作一個IT的人才,因此都沒有接受,這裏很抱歉,而且會更加努力,好好鑽研,但願和你們一塊兒共同進步~node

2. 塊設備與字符設備I/O口操做異同數據結構

2.1 塊設備只能以塊爲單位接受輸入和返回輸出,而字符設備則以字節爲單位。大多數設備是字符設備,由於它們不須要緩衝並且不以固定塊大小進行操做。 框架

2.2 塊設備對於I/O 請求有對應的緩衝區,所以它們能夠選擇以什麼順序進行響應,字符設備無須緩衝且被直接讀寫。對於存儲設備而言調 讀寫的順序做用巨大,由於在讀寫連續的扇區比分離的扇區更快。  dom

2.3 字符設備只能被順序讀寫,而塊設備能夠隨機訪問。雖然塊設備可隨機訪問,可是對於磁盤這類機械設備而言,順序地組織塊設備的訪問能夠提升性能。函數

3. 塊設備驅動結構性能

3.1 block_device_operations 結構體學習

struct block device operations 

      { 

        int (*open)(struct inode *, struct file*);  //打開 

        int (*release)(struct inode *, struct file*);  //釋放 
    //與字符設備驅動相似,當設備被打開和關閉時將調用它們。

        int (*ioctl)(struct inode *,struct file *,unsigned,unsigned long); //ioctl

    // ioctl()系統調用的實現,塊設備包含大量的標準請求,這些標準請求由Linux 塊設備層處理 
        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*); 

        int (*media changed)(struct gendisk*);  //介質被改變?
     //被內核調用來檢查是否驅動器中的介質已經改變,若是是,則返回一個非0 值,不然返回0

       int (*revalidate disk)(struct gendisk*);  //使介質有效
     //revalidate_disk()函數被調用來響應一個介質改變,它給驅動一個機會來進行必要的工做以使新介質準備好。

       int (*getgeo)(struct block device *, struct hd geometry*);//填充驅動器信息 
    //根據驅動器的幾何信息填充一個hd_geometry 結構體

       struct module *owner; //模塊擁有者 
    // 一個指向擁有這個結構體的模塊的指針,它一般被初始化爲THIS_MODULE

     };    

3.2 gendisk 結構體ui

struct gendisk 

      { 

        int major; /* 主設備號 */ 

             
        int first minor;  /*第1個次設備號*/ 

        int minors; /* 最大的次設備數,若是不能分區,則爲1*/ 

            
        char disk name [32]; /* 設備名稱 */ 
               
        struct hd struct **part; /* 磁盤上的分區信息 */ 
              
        struct block device operations *fops; /*塊設備操做結構體*/ 
                   
        struct request queue *queue;  /*請求隊列*/ 
              
       void *private data;  /*私有數據*/ 
     
       sector t capacity; /*扇區數,512 字節爲1個扇區*/ 
      
       int flags; 
             
       char devfs name[64]; 
       int number; 

       struct device *driverfs dev; 
       struct kobject kobj; 
      

       struct timer rand state *random; 
       int policy; 
      

       atomic t sync io; /* RAID */ 
       unsigned long stamp; 
             
       int in flight; 
                   
       #ifdef CONFIG SMP 
                     
         struct disk stats *dkstats; 
       #else 
                      
         struct disk stats dkstats; 
       #endif 
     };

3.3 gendisk的操做atom

//分配gendisk 
struct gendisk *alloc disk (int minors); 

// 增長gendisk 
 void add disk(struct gendisk *gd); 

// 釋放gendisk 
 void del gendisk (struct gendisk *gd); 

//gendisk 引用計數 

// 設置gendisk 容量 
 void set capacity (struct gendisk *disk, sector t size);

3.4 request 與bio 結構體

1) 請求

  在Linux 塊設備驅動中,使用request 結構體來表徵等待進行的I/O 請求,request 結構體的主要成員包括(只用於內核塊設備層):

sector t hard sector;  //第一個還沒有傳輸的扇區

unsigned long hard nr sectors;  //尚待完成的扇區數

unsigned int hard cur sectors;  //當前I/O 操做中待完成的扇區數

2) 請求隊列

   一個塊請求隊列是一個塊I/O 請求的隊列,請求隊列跟蹤的塊I/O 請求,它存儲用於描述這個設備可以支持的請求的類型信息、它們的最大大小、多少不一樣的段可進入一個請求、硬件扇區大小、對齊要求等參數,其結果是:若是請求隊列被配置正確了,它不會交給該設備一個不能處理的請求。

//request 隊列結構體 

     struct request queue 

      { 

        ... 

        /* 保護隊列結構體的自旋鎖 */ 

        spinlock t    queue lock; 

        spinlock t *queue lock; 

        /* 隊列kobject */ 

        struct kobject kobj; 

       /* 隊列設置 */ 

       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 segments; /* 最大的段數 */ 

       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; 

     }; 

3) 塊I/O

  一般一個bio 對應一個I/O 請求,一個請求能夠包含多個bio。

struct bio 
      { 
        
        sector t bi sector; /* 要傳輸的第一個扇區 */ 
         //標識這個 bio  要傳送的第一個 (512 字節)扇區。 
        struct bio *bi next; /* 下一個bio */ 
             
        struct block device     *bi bdev; 
            
        unsigned long bi flags; /* 狀態、命令等 */ 
                          
        unsigned long bi rw; /* 低位表示READ/WRITE,高位表示優先級*/ 

        unsigned short bi vcnt; /* bio vec 數量 */ 
                
        unsigned short bi idx; /* 當前bvl vec 索引 */ 
  
       /*不相鄰的物理段的數目*/ 
                        
       unsigned short bi phys segments; 
      
       /*物理合並和DMA remap合併後不相鄰的物理段的數目*/ 
              
       unsigned short bi hw segments; 

       unsigned int bi size; /* 以字節爲單位所需傳輸的數據大小 */

  //被傳送的數據大小,以字節爲單位,驅動中可使用bio_sectors(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; /* 實際的vec 列表 */ 

        bio end io t *bi end io; 
   
       atomic t bi cnt; 

       void *bi private; 

       bio destructor t *bi destructor; /* destructor */ 
    }; 

3.5  塊設備驅動註冊與註銷 

  首先註冊她們本身到內核,其函數原型以下

int register blkdev (unsigned int major, const char *name);
// major參數是塊設備要使用的主設備號,name爲設備名  

  與register_blkdev()對應的註銷函數是unregister_blkdev(),其原型爲:

int unregister blkdev (unsigned int major, const char *name);
// 傳遞給register_blkdev() 的參數必須與傳遞給register_blkdev() 的參數匹配,不然這個函數返回-EINVAL 

4 Linux 塊設備驅動的模塊加載與卸載

4.1 須要完成的工做

  • 分配、初始化請求隊列,綁定請求隊列和請求函數。
  • 分配、初始化gendisk,給gendisk 的maj or、fops 、queue 等成員賦值,最後添加gendisk。 
  • 註冊塊設備驅動。

4.2 塊設備驅動的模塊加載函數模板 (使用bl k_a llo c_que ue )

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 op; 
          
        xxx disks->queue = xxx queue; 
          
        sprintf(xxx disks->disk name, "xxx%d", i); 
          
        set capacity (xxx disks, xxx size); //xxx size 以512bytes 爲單位 
             
        add disk (xxx disks); //添加gendisk 
      
        return 0; 
          
        out queue: unregister blkdev (XXX MAJOR, "xxx"); 
                 
        out: put disk(xxx disks); 
          
        blk cleanup queue (xxx queue); 
      
        return  - ENOMEM; 

      } 

4.3 塊設備驅動的模塊加載函數模板(使用bl k_ i nit_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 op; 
      
        xxx disks->queue = xxx queue; 
                    
        sprintf(xxx disks->disk name, "xxx%d", i); 
          
        set capacity (xxx disks, xxx size *2); 
          
        add disk (xxx disks); //添加gendisk 
      
        return 0; 
         
        out queue: unregister blkdev (XXX MAJOR, "xxx"); 
            
        out: put disk(xxx disks); 
          
        blk cleanup queue (xxx queue); 
      
        return  - ENOMEM; 

      } 

4.4 在塊設備的open()函數中賦值private_data 

static int xxx open (struct inode *inode, struct file *filp) 
     { 
              
       struct xxx dev *dev = inode->i bdev->bd disk->private data; 
            
       filp->private data = dev;  //賦值file 的private data 
       ... 
       return 0; 

     } 

5 塊設備的I/O請求處理

5.1 使用求情隊列

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

void request (request queue t *queue);
//請求函數能夠在沒有完成請求隊列中的全部請求的狀況下返回,甚至它一個請求不完成均可以返回

下面給出了一個更復雜的請求函數,它進行了3 層遍歷:遍歷請求隊 列中的每一個請求,遍歷請求中的每一個bio,遍歷bio 中的每一個段。請求函數遍歷請求、bio 和段以下:

  static void xxx full request (request queue t *q) 
      { 
       struct request *req; 
                     
        int sectors xferred; 
             
        struct xxx dev *dev = q->queuedata; 
        /* 遍歷每一個請求 */ 
          
        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 request (dev, req); 
         
         if (!end that request first (req, 1, sectors xferred)) 
         { 
            
           blkdev dequeue request (req); 
           
           end that request last (req); 
         } 
        } 
     } 
     /* 請求處理 */ 
         
     static int xxx xfer request (struct xxx dev *dev,struct request *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; 
     } 
     /* 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; 
      
       /* 遍歷每一段 */ 
         
       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, KM USER0); 

        } 

       return 0; 

     }     

5.2 不適用請求隊列

有些設備不須要使用請求隊列,其函數原型以下:

 typedef int (make request fn) (request queue t *q, struct bio *bio);
//bio 結構體表示一個或多個要傳送的緩衝區

在處理處理bio完成後應該使用bio_endio()函數通知處理結束,以下所示:

void bio endio (struct bio *bio, unsigned int bytes, int error); 
//參數bytes 是已經傳送的字節數,它能夠比這個bio 所表明的字節數少

無論對應的I/O 處理成功與否,「製造請求」函數都應該返回0 。若是「製造請求」 函數返回一個非零值,bio 將被再次提交。下面代碼所示爲一個 「製造請求」函數的例子。

static int xxx make request (request queue t *q, struct bio *bio) 
     { 
         
       struct xxx dev *dev = q->queuedata; 
       int status; 
                    
       status = xxx xfer bio (dev, bio); //處理bio 
        
       bio endio (bio, bio->bi size, status); //通告結束 
       return 0; 
}

  

  版權全部,轉載請註明轉載地址:http://www.cnblogs.com/lihuidashen/p/4506781.html

相關文章
相關標籤/搜索