1. 程式人生 > >《Linux Device Drivers》第十六章 塊裝置驅動程式——note

《Linux Device Drivers》第十六章 塊裝置驅動程式——note

  • 簡介
    • 一個塊裝置驅動程式主要通過傳輸固定大小的隨機資料來訪問裝置
    • Linux核心視塊裝置為與字元裝置相異的基本裝置型別
    • Linux塊裝置驅動程式介面使得塊裝置可以發揮其最大的功效,但是其複雜程式又是程式設計者必須面對的一個問題
    • 一個數據塊指的是固定大小的資料,而大小的值由核心確定
    • 資料塊的大小通常是4096個位元組,但是可以根據體系結構和所使用的檔案系統進行改變
    • 與資料塊對應的是扇區,它是由底層硬體決定大小的一個塊,核心所處理的裝置扇區大小是512位元組
    • 如果要使用不同的硬體扇區大小,使用者必須對核心的扇區數做相應的修改
  • 註冊
    • 註冊塊裝置驅動程式
      • <linux/fs.h>
      • int register_blkdev(unsigned int major, const char *name);
        • 如果需要的話分配一個動態的主裝置號
        • 在/proc/devices中建立一個入口項
      • int unregister_blkdev(unsigned int major, const char *name);
    • 註冊磁碟
      • struct block_device_operations
        • int (*open) (struct inode *inode, struct file *filp);
        • int (*release) (struct inode *inode, struct file *filp);
        • int (*ioctl) (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg);
        • int (*media_changed) (struct gendisk *gd);
        • int (*revalidate_disk) (struct gendisk *gd);
        • struct module *owner;
      • gendisk結構
        • <linux/genhd.h>
        • struct gendisk
          • int major;
          • int first_minor;
          • int minors;
            • 常取16
          • char disk_name[32]
            • 顯示在/proc/partitions和sysfs中
          • struct block_device_operations *fops;
          • struct request_queue *queue;
          • int flags;
          • sector_t capacity;
          • void *private_data;
        • struct gendisk *alloc_disk(int minors);
        • void del_gendisk(struct gendisk *gd);
        • void add_disk(struct gendisk *gd);

 

  • 塊裝置操作
    • open和release函式
      • 對於那些操作實際硬體裝置的驅動程式,open和release函式可以設定驅動程式和硬體的狀態。這些操作包括使磁碟開始或者停止旋轉,鎖住可移動介質的倉門以及分配DMA快取等
      • 有一些操作能夠讓塊裝置在使用者空間內被直接開啟,這些操作包括給磁碟分割槽,或者在分割槽上建立檔案系統,或者執行檔案系統檢查程式
    • 對可移動介質的支援
      • 呼叫media_changed函式以檢查介質是否被改變
      • 在介質改變後將呼叫revalideate函式
    • ioctl函式
      • 高層的塊裝置子系統在驅動程式獲得ioctl命令前,已經截取了大量的命令
      • 實際上在一個現代驅動程式中,許多ioctl命令根本就不用實現
  • 請求處理
    • 每個塊裝置驅動程式的核心是它的請求函式
    • 驅動程式所需要知道的任何關於請求的資訊,都包含在通過請求佇列傳遞給我們的結構中
    • request函式介紹
      • void request(request_queue_t *queue);
        • 當核心需要驅動程式處理讀取、寫入以及其他對裝置的操作時,就會呼叫該函式
      • 每個裝置都有一個請求佇列
        • dev->queue = blk_init_queue(test_request, &dev->lock);
      • 對request函式的呼叫是與使用者空間程序中的動作完全非同步的
    • 一個簡單的request函式
      • struct request * elv_next_request(request_queue_t queue);
      • void end_request(struct request *req, int succeeded);
      • struct request
        • sector_t secotr;
        • unsigned long nr_sectors;
        • char *buffer
        • rq_data_dir(struct request *req);
    • 請求佇列
      • 一個塊裝置請求佇列可以這樣描述:包含塊裝置I/O請求的序列
      • 請求佇列跟蹤未完成的塊裝置的I/O請求
      • 請求佇列還實現了外掛介面
      • I/O排程器還負責合併鄰近的請求
      • 請求佇列擁有request_queue或request_queue_t結構型別
      • <linux/blkdev.h>
      • 佇列的建立與刪除
        • request_queue_t *blk_init_queue(request_fn_proc *request, spinlock_t *lock);
        • void blk_cleanup_queue(request_queue_t *queue);
      • 佇列函式
        • struct request *elv_next_request(request_queue_t *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_bounce_limit(request_queue_t *queue, u64 dma_addr);
        • void blk_queue_max_sectors(request_queue_t *queue, unsigned short max);
        • void blk_queue_max_phys_segments(request_queue_t *queue, unsigned short max);
        • void blk_queue_max_hw_segments(request_queue_t *queue, unsigned short max);
        • void blk_queue_max_segment_size(request_queue_t *queue, unsigned short max);
        • void blk_queue_segment_boundary(request_queue_t *queue, unsigned long mask);
        • void blk_queue_dma_alignment(request_queue_t *queue, int mask);
        • void blk_queue_hardsect_size(request_queue_t *queue, unsigned short max);
      • 請求過程剖析
        • 從本質上講,一個request結構是作為一個bio結構的連結串列實現的
        • bio結構
          • bio結構包含了驅動程式執行請求的全部資訊,而不必與初始化這個請求的使用者空間的程序相關聯
          • <linux/bio.h>
          • struct bio
            • sector_t bi_sector;
            • unsigned int bi_size;
              • 以位元組為單位所需要傳輸的資料大小
            • unsigned long bi_flags;
            • unsigned short bio_phys_segments;
            • unsigned short bio_hw_segments;
            • struct bio_vec *bi_io_vec
          • struct bio_vec
            • struct page *vb_page;
            • unsigned int bv_len;
            • unsigned int bv_offset;
          • example
            • int segno;
            • struct bio_vec *bvec;
            • bio_for_each_segment(bvec, bio, segno)
            • {
              • /* 使用該段進行一定的操作 */
            • }
          • char *__bio_kmap_atomic(struct bio *bio, int i, enum km_type type);
          • void __bio_kunmap_atomic(char *buffer, enum km_type type):
          • struct page *bio_page(struct bio *bio);
          • int bio_offset(struct bio *bio);
          • int bio_cur_sectors(struct bio *bio);
          • char *bio_data(struct bio *bio);
          • char *bio_kmap_irq(struct bio *bio, unsigned long *flags);
          • void bio_kunmap_irq(char *buffer, unsigned long *flags);
        • request結構成員
          • struct request
            • sector_t hard_sector;
            • unsigned long hard_nr_sectors;
            • unsigned int hard_cur_sectors;
            • struct bio *bio;
            • char *buffer;
            • unsigned short nr_phys_segments;
            • struct list_head queuelist;
        • 屏障請求
          • 在驅動程式接收到請求前,塊裝置層重新組合了請求以提高I/O效能
          • 出於同樣的目的,驅動程式也可以重新組合請求
          • 但在無限制重新組合請求時面臨了一個問題:一些應用程式的某些操作,要在另外一些操作開始前完成
          • 2.6版本的塊裝置層使用屏障(barrier)請求來解決這個問題
          • 如果一個請求被設定了REQ_HARDBARRER標誌,那麼在其他後續請求被初始化前,它必須被寫入驅動器
          • void blk_queue_ordered(request_queue_t *queue, int flag);
          • int blk_barrier_rq(sruct request *req);
            • 如果返回一個非零值,該請求是一個屏障請求
        • 不可重試請求
          • int blk_noretry_request(struct request *req);
      • 請求完成函式
        • int end_that_request_first(struct request *req, int success, int count);
        • void end_that_request_last(struct request *req);
        • example
          • void end_request(struct request *req, int uptodate)
          • {
            • if (!end_that_request(req, uptodate, req->hard_cur_sectors)
            • {
              • add_disk_randomness(req->rq_disk);
              • blkdev_dequeue_request(req);
              • end_that_request_last(req);
            • }
          • }
        • 使用bio
          • example
            • struct request *req
            • struct bio *bio;
            • rq_for_each_bio(bio, req)
            • {
              • /* 使用該bio結構進行一定的操作 */
            • }
        • 塊裝置請求和DMA
          • int blk_rq_map_sg(request_queue_t *queue, struct request *req, struct scatterlist *list);
          • clear_bit(QUEUE_FLAG_CLEAR, &queue->queue_flags);
        • 不使用請求佇列
          • typedef int (make_request_fn) (request_queue_t *q, struct bio *bio);
          • void bio_endio(struct bio *bio, unsigned int bytes, int error);
          • request_queue_t *blk_alloc_queue(int flags);
            • 並未真正地建立一個儲存請求的佇列
          • void blk_queue_make_request(request_queue_t *queue, make_request_fn *func);
          • drivers/block/ll_rw_block.c
  • 其他一些細節
    • 命令預處理
      • typedef int (prep_rq_fn) (request_queue_t *queue, struct request *req);
        • 該函式要能返回下面的值之一
          • BLKPREP_OK
          • BLKPREP_KILL
          • BLKPREP_DEFER
      • void blk_queue_prep_rq(request_queue_t *queue, prep_rq_fn *func);
    • 標記命令佇列
      • 同時擁有多個活動請求的硬體通常支援某種形式的標記命令佇列(Tagged Command Queueing, TCQ)
      • TCQ只是為每個請求新增一個整數(標記)的技術,這樣當驅動器完成它們中的一個請求後,它就可以告訴驅動程式完成的是哪個
      • int blk_queue_int_tags(request_queue_t *queue, int depth, struct blk_queue_tag *tags);
      • int blk_queue_resize_tags(request_queue_t *queue, int new_depth);
      • int blk_queue_start_tag(request_queue_t *queue, struct request *req);
      • void blk_queue_end_tag(request_queue_t *queue, struct request *req);
      • struct request *blk_queue_find_tag(request_queue_t *queue, int tag);
      • void blk_queue_invalidate_tags(request_queue_t *queue);