1. 程式人生 > >Linux塊裝置驅動詳解(一)

Linux塊裝置驅動詳解(一)

    請求佇列跟蹤等候的塊I/O請求,它儲存用於描述這個裝置能夠支援的請求的型別資訊、它們的最大大小、多少不同的段可進入一個請求、硬體扇區大小、對齊要求等引數,其結果是:如果請求佇列被配置正確了,它不會交給該裝置一個不能處理的請求。
    請求佇列還實現一個插入介面,這個介面允許使用多個I/O排程器,I/O排程器(也稱電梯)的工作是以最優效能的方式向驅動提交I/O請求。大部分I/O 排程器累積批量的 I/O 請求,並將它們排列為遞增(或遞減)的塊索引順序後提交給驅動。進行這些工作的原因在於,對於磁頭而言,當給定順序排列的請求時,可以使得磁碟順序地從一頭到另一頭工作,非常像一個滿載的電梯,在一個方向移動直到所有它的“請求”已被滿足。
另外,I/O排程器還負責合併鄰近的請求,當一個新 I/O 請求被提交給排程器後,它會在佇列裡搜尋包含鄰近扇區的請求;如果找到一個,並且如果結果的請求不是太大,排程器將合併這2個請求。
對磁碟等塊裝置進行I/O操作順序的排程類似於電梯的原理,先服務完上樓的乘客,再服務下樓的乘客效率會更高,而“上躥下跳”,順序響應使用者的請求則會導致電梯無序地忙亂。
    Linux 2.6包含4個I/O排程器,它們分別是No-op I/O scheduler、Anticipatory I/O scheduler、Deadline I/O scheduler與CFQ I/O scheduler。
    Noop I/O scheduler是一個簡化的排程程式,它只作最基本的合併與排序。
    Anticipatory I/O scheduler是當前核心中預設的I/O排程器,它擁有非常好的效能,在2.5中它就相當引人注意。在與2.4核心進行的對比測試中,在2.4中多項以分鐘為單位完成的任務,它則是以秒為單位來完成的,正因為如此它成為目前2.6中預設的I/O排程器。Anticipatory I/O scheduler的缺點是比較龐大與複雜,在一些特殊的情況下,特別是在資料吞吐量非常大的資料庫系統中它會變得比較緩慢。
    Deadline I/O scheduler是針對Anticipatory I/O scheduler的缺點進行改善而來的,表現出的效能幾乎與Anticipatory I/O scheduler一樣好,但是比Anticipatory小巧。
    CFQ I/O scheduler為系統內的所有任務分配相同的頻寬,提供一個公平的工作環境,它比較適合桌面環境。事實上在測試中它也有不錯的表現,mplayer、xmms等多媒體播放器與它配合的相當好,回放平滑,幾乎沒有因訪問磁碟而出現的跳幀現象。
    核心block目錄中的noop-iosched.c、as-iosched.c、deadline-iosched.c和cfq-iosched.c檔案分別實現了上述排程演算法。
    可以通過給kernel新增啟動引數,選擇使用的IO排程演算法,如:
    kernel elevator=deadline
    •  初始化請求佇列
    request_queue_t *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock);
    該函式的第1個引數是請求處理函式的指標,第2個引數是控制訪問佇列許可權的自旋鎖,這個函式會發生記憶體分配的行為,故它可能會失敗,因此一定要檢查它的返回值。這個函式一般在塊裝置驅動的模組載入函式中呼叫。
    •  清除請求佇列
    void blk_cleanup_queue(request_queue_t * q);
    這個函式完成將請求佇列返回給系統的任務,一般在塊裝置驅動模組解除安裝函式中呼叫。
    而blk_put_queue()巨集則定義為:
    #define blk_put_queue(q) blk_cleanup_queue((q))
    •  分配“請求佇列”
    request_queue_t *blk_alloc_queue(int gfp_mask);
    對於FLASH、RAM盤等完全隨機訪問的非機械裝置,並不需要進行復雜的I/O排程,這個時候,應該使用上述函式分配1個“請求佇列”,並使用如下函式來繫結“請求佇列”和“製造請求”函式。
    void blk_queue_make_request(request_queue_t * q, make_request_fn * mfn);
    在13.6.2節我們會看到,這種方式分配的“請求佇列”實際上不包含任何請求,所以給其加上引號。
    •  提取請求
    struct request *elv_next_request(request_queue_t *queue);
    上述函式用於返回下一個要處理的請求(由 I/O 排程器決定),如果沒有請求則返回NULL。elv_next_request()不會清除請求,它仍然將這個請求保留在佇列上,但是標識它為活動的,這個標識將阻止I/O 排程器合併其它的請求到已開始執行的請求。因為elv_next_request()不從佇列裡清除請求,因此連續呼叫它2次,2次會返回同一個請求結構體。
    •  去除請求
    void blkdev_dequeue_request(struct request *req);
    上述函式從佇列中去除1個請求。如果驅動中同時從同一個佇列中操作了多個請求,它必須以這樣的方式將它們 從佇列中去除。
    如果需要將1個已經出列的請求歸還到佇列中,可以呼叫:
    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);
    如果塊裝置到達不能處理等候的命令的狀態,應呼叫blk_stop_queue()來告知塊裝置層。之後,請求函式將不被呼叫,除非再次呼叫blk_start_queue()將裝置恢復到可處理請求的狀態。
    •  引數設定
    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 int max);
    這些函式用於設定描述塊裝置可處理的請求的引數。blk_queue_max_sectors()描述任一請求可包含的最大扇區數,預設值為255;blk_queue_max_phys_segments()和 blk_queue_max_hw_segments()都控制1個請求中可包含的最大物理段(系統記憶體中不相鄰的區),blk_queue_max_hw_segments()考慮了系統I/O記憶體管理單元的重對映,這2個引數預設都是 128。blk_queue_max_segment_size告知核心請求段的最大位元組數,預設值為65,536。
    •  通告核心
    void blk_queue_bounce_limit(request_queue_t *queue, u64 dma_addr);
    該函式用於告知核心塊裝置執行DMA時可使用的最高實體地址dma_addr,如果一個請求包含超出這個限制的記憶體引用,一個“反彈”緩衝區將被用來給這個操作。這種方式的代價昂貴,因此應儘量避免使用。
可以給dma_addr引數提供任何可能的值或使用預先定義的巨集,如BLK_BOUNCE_HIGH(對高階記憶體頁使用反彈緩衝區)、BLK_BOUNCE_ISA(驅動只可在16M的ISA區執行DMA)或者BLK_BOUCE_ANY(驅動可在任何地址執行DMA),預設值是BLK_BOUNCE_HIGH。
    blk_queue_segment_boundary(request_queue_t *queue, unsigned long mask);
    如果我們正在驅動編寫的裝置無法處理跨越一個特殊大小記憶體邊界的請求,應該使用這個函式來告知核心這個邊界。例如,如果裝置處理跨4MB 邊界的請求有困難,應該傳遞一個0x3fffff 掩碼。預設的掩碼是0xffffffff(對應4GB邊界)。
    void blk_queue_dma_alignment(request_queue_t *queue, int mask);
    該函式用於告知核心塊裝置施加於DMA 傳送的記憶體對齊限制,所有請求都匹配這個對齊,預設的遮蔽是 0x1ff,它導致所有的請求被對齊到 512位元組邊界。
    void blk_queue_hardsect_size(request_queue_t *queue, unsigned short max);
    該函式用於告知核心塊裝置硬體扇區的大小,所有由核心產生的請求都是這個大小的倍數並且被正確對界。但是,核心塊裝置層和驅動之間的通訊還是以512位元組扇區為單位進行。