《Linux Device Drivers》第十六章 塊裝置驅動程式——note
阿新 • • 發佈:2018-11-13
- 簡介
- 一個塊裝置驅動程式主要通過傳輸固定大小的隨機資料來訪問裝置
- 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);
- struct block_device_operations
- 註冊塊裝置驅動程式
- 塊裝置操作
- open和release函式
- 對於那些操作實際硬體裝置的驅動程式,open和release函式可以設定驅動程式和硬體的狀態。這些操作包括使磁碟開始或者停止旋轉,鎖住可移動介質的倉門以及分配DMA快取等
- 有一些操作能夠讓塊裝置在使用者空間內被直接開啟,這些操作包括給磁碟分割槽,或者在分割槽上建立檔案系統,或者執行檔案系統檢查程式
- 對可移動介質的支援
- 呼叫media_changed函式以檢查介質是否被改變
- 在介質改變後將呼叫revalideate函式
- ioctl函式
- 高層的塊裝置子系統在驅動程式獲得ioctl命令前,已經截取了大量的命令
- 實際上在一個現代驅動程式中,許多ioctl命令根本就不用實現
- open和release函式
- 請求處理
- 每個塊裝置驅動程式的核心是它的請求函式
- 驅動程式所需要知道的任何關於請求的資訊,都包含在通過請求佇列傳遞給我們的結構中
- request函式介紹
- void request(request_queue_t *queue);
- 當核心需要驅動程式處理讀取、寫入以及其他對裝置的操作時,就會呼叫該函式
- 每個裝置都有一個請求佇列
- dev->queue = blk_init_queue(test_request, &dev->lock);
- 對request函式的呼叫是與使用者空間程序中的動作完全非同步的
- void request(request_queue_t *queue);
- 一個簡單的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;
- struct request
- 屏障請求
- 在驅動程式接收到請求前,塊裝置層重新組合了請求以提高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結構進行一定的操作 */
- }
- example
- 塊裝置請求和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);
- typedef int (prep_rq_fn) (request_queue_t *queue, struct request *req);
- 標記命令佇列
- 同時擁有多個活動請求的硬體通常支援某種形式的標記命令佇列(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);
- 命令預處理