99re热视频这里只精品,久久久天堂国产精品女人,国产av一区二区三区,久久久精品成人免费看片,99久久精品免费看国产一区二区三区

16.3. 請求處理

2018-02-24 15:50 更新

16.3.?請求處理

每個塊驅(qū)動的核心是它的請求函數(shù). 這個函數(shù)是真正做工作的地方 --或者至少開始的地方; 剩下的都是開銷. 因此, 我們花不少時間來看在塊驅(qū)動中的請求處理.

一個磁盤驅(qū)動的性能可能是系統(tǒng)整個性能的關(guān)鍵部分. 因此, 內(nèi)核的塊子系統(tǒng)編寫時在性能上考慮了很多; 它做所有可能的事情來使你的驅(qū)動從它控制的設(shè)備上獲得最多. 這是一個好事情, 其中它盲目地使能快速 I/O. 另一方面, 塊子系統(tǒng)沒必要在驅(qū)動 API 中曝露大量復(fù)雜性. 有可能編寫一個非常簡單的請求函數(shù)( 我們將很快見到 ), 但是如果你的驅(qū)動必須在一個高層次上操作復(fù)雜的硬件, 它可能是任何樣子.

16.3.1.?對請求方法的介紹

塊驅(qū)動的請求方法有下面的原型:


void request(request_queue_t *queue); 

這個函數(shù)被調(diào)用, 無論何時內(nèi)核認(rèn)為你的驅(qū)動是時候處理對設(shè)備的讀, 寫, 或者其他操作. 請求函數(shù)在返回之前實際不需要完成所有的在隊列中的請求; 實際上, 它可能不完成它們?nèi)魏我粋€, 對大部分真實設(shè)備. 它必須, 但是, 驅(qū)動這些請求并且確保它們最終被驅(qū)動全部處理.

每個設(shè)備有一個請求隊列. 這是因為實際的從和到磁盤的傳輸可能在遠(yuǎn)離內(nèi)核請求它們時發(fā)生, 并且因為內(nèi)核需要這個靈活性來調(diào)度每個傳送, 在最好的時刻(將影響磁盤上鄰近扇區(qū)的請求集合到一起, 例如). 并且這個請求函數(shù), 你可能記得, 和一個請求隊列相關(guān), 當(dāng)這個隊列被創(chuàng)建時. 讓我們回顧 sbull 如何創(chuàng)建它的隊列:


dev->queue = blk_init_queue(sbull_request, &dev->lock); 

這樣, 當(dāng)這個隊列被創(chuàng)建時, 請求函數(shù)和它關(guān)聯(lián)到一起. 我們還提供了一個自旋鎖作為隊列創(chuàng)建過程的一部分. 無論何時我們的請求函數(shù)被調(diào)用, 內(nèi)核持有這個鎖. 結(jié)果, 請求函數(shù)在原子上下文中運行; 它必須遵循所有的 5 章討論過的原子代碼的通用規(guī)則.

在你的請求函數(shù)持有鎖時, 隊列鎖還阻止內(nèi)核去排隊任何對你的設(shè)備的其他請求. 在一些條件下, 你可能考慮在請求函數(shù)運行時丟棄這個鎖. 如果你這樣做, 但是, 你必須保證不存取請求隊列, 或者任何其他的被這個鎖保護(hù)的數(shù)據(jù)結(jié)構(gòu), 在這個鎖不被持有時. 你必須重新請求這個鎖, 在請求函數(shù)返回之前.

最后, 請求函數(shù)的啟動(常常地)與任何用戶空間進(jìn)程之間是完全異步的. 你不能假設(shè)內(nèi)核運行在發(fā)起當(dāng)前請求的進(jìn)程上下文. 你不知道由這個請求提供的 I/O 緩沖是否在內(nèi)核或者用戶空間. 因此任何類型的明確存取用戶空間的操作都是錯誤的并且將肯定引起麻煩. 如你將見到的, 你的驅(qū)動需要知道的關(guān)于請求的所有事情, 都包含在通過請求隊列傳遞給你的結(jié)構(gòu)中.

16.3.2.?一個簡單的請求方法

sbull 例子驅(qū)動提供了幾個不同的方法給請求處理. 缺省地, sbull 使用一個方法, 稱為 sbull_request, 它打算作為一個最簡單地請求方法的例子. 別忙, 它在這里:


static void sbull_request(request_queue_t *q)
{
        struct request *req;
        while ((req = elv_next_request(q)) != NULL) {
                struct sbull_dev *dev = req->rq_disk->private_data;
                if (! blk_fs_request(req)) {

                        printk (KERN_NOTICE "Skip non-fs request\n");
                        end_request(req, 0);
                        continue;
                }
                sbull_transfer(dev, req->sector, req->current_nr_sectors,
                               req->buffer, rq_data_dir(req));
                end_request(req, 1);
        }
}

這個函數(shù)介紹了 struct request 結(jié)構(gòu). 我們之后將詳細(xì)檢查 struct request; 現(xiàn)在, 只需說它表示一個我們要執(zhí)行的塊 I/O 請求.

內(nèi)核提供函數(shù) elv_next_request 來獲得隊列中第一個未完成的請求; 當(dāng)沒有請求要被處理時這個函數(shù)返回 NULL. 注意 elf_next 不從隊列里去除請求. 如果你連續(xù)調(diào)用它 2 次, 它 2 次都返回同一個請求結(jié)構(gòu). 在這個簡單的操作模式中, 請求只在它們完成時被剝離隊列.

一個塊請求隊列可包含實際上不從磁盤和自磁盤移動塊的請求. 這些請求可包括供應(yīng)商特定的, 低層的診斷操作或者和特殊設(shè)備模式相關(guān)的指令, 例如給可記錄介質(zhì)的報文寫模式. 大部分塊驅(qū)動不知道如何處理這樣的請求, 并且簡單地失敗它們; sbull 也以這種方式工作. 對 block_fs_request 的調(diào)用告訴我們是否我們在查看一個文件系統(tǒng)請求--一個一旦數(shù)據(jù)塊的. 如果這個請求不是一個文件系統(tǒng)請求, 我們傳遞它到 end_request:


void end_request(struct request *req, int succeeded); 

當(dāng)我們處理了非文件系統(tǒng)請求, 之后我們傳遞 succeeded 為 0 來指示我們沒有成功完成這個請求. 否則, 我們調(diào)用 sbull_transfer 來真正移動數(shù)據(jù), 使用一套在請求結(jié)構(gòu)中提供的成員:

sector_t sector;
我們設(shè)備上起始扇區(qū)的索引. 記住這個扇區(qū)號, 象所有這樣的在內(nèi)核和驅(qū)動之間傳遞的數(shù)目, 是以 512-字節(jié)扇區(qū)來表示的. 如果你的硬件使用一個不同的扇區(qū)大小, 你需要相應(yīng)地調(diào)整扇區(qū). 例如, 如果硬件是 2048-字節(jié)的扇區(qū), 你需要用 4 來除起始扇區(qū)號, 在安放它到對硬件的請求之前.

unsigned long nr_sectors;
要被傳送的扇區(qū)(512-字節(jié))數(shù)目.

char *buffer;
一個指向緩沖的指針, 數(shù)據(jù)應(yīng)當(dāng)被傳送到或者從的緩沖. 這個指針是一個內(nèi)核虛擬地址并且可被驅(qū)動直接解引用, 如果需要.

rq_data_dir(struct request *req);
這個宏從請求中抽取傳送的方向; 一個 0 返回值表示從設(shè)備中讀, 非 0 返回值表示寫入設(shè)備.

有了這個信息, sbull 驅(qū)動可實現(xiàn)實際的數(shù)據(jù)傳送, 使用一個簡單的 memcpy 調(diào)用 -- 我們數(shù)據(jù)已經(jīng)在內(nèi)存, 畢竟. 進(jìn)行這個拷貝操作的函數(shù)( sbull_transfer ) 也處理扇區(qū)大小的調(diào)整, 并確保我們沒有拷貝超過我們的虛擬設(shè)備的尾.


static void sbull_transfer(struct sbull_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)
                memcpy(dev->data + offset, buffer, nbytes);
        else
                memcpy(buffer, dev->data + offset, nbytes);
}

用這個代碼, sbull 實現(xiàn)了一個完整的, 簡單的基于 RAM 的磁盤設(shè)備. 但是, 對于很多類型的設(shè)備, 它不是一個實際的驅(qū)動, 由于幾個理由.

這些原因的第一個是 sbull 同步執(zhí)行請求, 一次一個. 高性能的磁盤設(shè)備能夠在同時有很多個請求停留; 磁盤的板上控制器因此可以優(yōu)化的順序(有人希望)執(zhí)行它們. 如果我們只處理隊列中的第一個請求, 我們在給定時間不能有多個請求被滿足. 能夠工作于多個請求要求對請求隊列和請求結(jié)構(gòu)的深入理解; 下面幾節(jié)會幫助來建立這種理解.

但是, 有另外一個問題要考慮. 當(dāng)系統(tǒng)進(jìn)行大的傳輸, 包含多個在一起的磁盤扇區(qū), 就獲得最好的性能. 磁盤操作的最高開銷常常是讀寫頭的定位; 一旦這個完成, 實際上需要的讀或者寫數(shù)據(jù)的時間幾乎可忽略. 設(shè)計和實現(xiàn)文件系統(tǒng)和虛擬內(nèi)存子系統(tǒng)的開發(fā)者理解這點, 因此他們盡力在磁盤上連續(xù)地查找相關(guān)的數(shù)據(jù), 并且在一次請求中傳送盡可能多扇區(qū). 塊子系統(tǒng)也在這個方面起作用; 請求隊列包含大量邏輯,目的是找到鄰近的請求并且接合它們?yōu)楦蟮牟僮?

sbull 驅(qū)動, 但是, 采取所有這些工作并且簡單地忽略它. 一次只有一個緩沖被傳送, 意味著最大的單次傳送幾乎從不超過單個頁的大小. 一個塊驅(qū)動能做的比那個要好的多, 但是它需要一個對請求結(jié)構(gòu)和bio結(jié)構(gòu)的更深的理解, 請求是從它們建立的.

下面幾節(jié)更深入地研究塊層如何完成它的工作, 已經(jīng)這些工作導(dǎo)致的數(shù)據(jù)結(jié)構(gòu).

16.3.3.?請求隊列

最簡單的說, 一個塊請求隊列就是: 一個塊 I/O 請求的隊列. 如果你往下查看, 一個請求隊列是一令人吃驚得復(fù)雜的數(shù)據(jù)結(jié)構(gòu). 幸運的是, 驅(qū)動不必?fù)?dān)心大部分的復(fù)雜性.

請求隊列跟蹤等候的塊I/O請求. 但是它們也在這些請求的創(chuàng)建中扮演重要角色. 請求隊列存儲參數(shù), 來描述這個設(shè)備能夠支持什么類型的請求: 它們的最大大小, 多少不同的段可進(jìn)入一個請求, 硬件扇區(qū)大小, 對齊要求, 等等. 如果你的請求隊列被正確配置了, 它應(yīng)當(dāng)從不交給你一個你的設(shè)備不能處理的請求.

請求隊列還實現(xiàn)一個插入接口, 這個接口允許使用多 I/O 調(diào)度器(或者電梯). 一個 I/O 調(diào)度器的工作是提交 I/O 請求給你的驅(qū)動, 以最大化性能的方式. 為此, 大部分 I/O 調(diào)度器累積批量的 I/O 請求, 排列它們?yōu)檫f增(或遞減)的塊索引順序, 并且以那個順序提交請求給驅(qū)動. 磁頭, 當(dāng)給定一列排序的請求時, 從磁盤的一頭到另一頭工作, 非常象一個滿載的電梯, 在一個方向移動直到所有它的"請求"(等待出去的人)已被滿足. 2.6 內(nèi)核包含一個"底線調(diào)度器", 它努力確保每個請求在預(yù)設(shè)的最大時間內(nèi)被滿足, 以及一個"預(yù)測調(diào)度器", 它實際上短暫停止設(shè)備, 在一個預(yù)想中的讀請求之后, 這樣另一個鄰近的讀將幾乎是馬上到達(dá). 到本書為止, 缺省的調(diào)度器是預(yù)測調(diào)度器, 它看來有最好的交互的系統(tǒng)性能.

I/O 調(diào)度器還負(fù)責(zé)合并鄰近的請求. 當(dāng)一個新 I/O 請求被提交給調(diào)度器, 它在隊列里搜尋包含鄰近扇區(qū)的請求; 如果找到一個, 并且如果結(jié)果的請求不是太大, 這 2 個請求被合并.

請求隊列有一個 struct request_queue 或者 request_queue_t 類型. 這個類型, 和許多操作它的函數(shù), 定義在 <linux/blkdev.h>. 如果你對請求隊列的實現(xiàn)感興趣, 你可找到大部分代碼在 drivers/block/ll_rw_block.c 和 elevator.c.

16.3.3.1.?隊列的創(chuàng)建和刪除

如同我們在我們的例子代碼中見到的, 一個請求隊列是一個動態(tài)的數(shù)據(jù)結(jié)構(gòu), 它必須被塊 I/O 子系統(tǒng)創(chuàng)建. 這個創(chuàng)建和初始化一個隊列的函數(shù)是:


request_queue_t *blk_init_queue(request_fn_proc *request, spinlock_t *lock); 

當(dāng)然, 參數(shù)是, 這個隊列的請求函數(shù)和一個控制對隊列存取的自旋鎖. 這個函數(shù)分配內(nèi)存(實際上, 不少內(nèi)存)并且可能失敗因為這個; 你應(yīng)當(dāng)一直檢查返回值, 在試圖使用這個隊列之前.

作為初始化一個請求隊列的一部分, 你可設(shè)置成員 queuedata(它是一個 void * 指針 )為任何你喜歡的值. 這個成員是請求隊列的對于我們在其他結(jié)構(gòu)中見到的 private_data 的對等體.

為返回一個請求隊列給系統(tǒng)(在模塊卸載時間, 通常), 調(diào)用 blk_cleanup_queue:


void blk_cleanup_queue(request_queue_t *); 

這個調(diào)用后, 你的驅(qū)動從給定的隊列中不再看到請求,并且不應(yīng)當(dāng)再次引用它.

16.3.3.2.?排隊函數(shù)

有非常少的函數(shù)來操作隊列中的請求 -- 至少, 考慮到驅(qū)動. 你必須持有隊列鎖, 在你調(diào)用這些函數(shù)之前.

返回要處理的下一個請求的函數(shù)是 elv_next_request:


struct request *elv_next_request(request_queue_t *queue); 

我們已經(jīng)在簡單的 sbull 例子中見到這個函數(shù). 它返回一個指向下一個要處理的請求的指針(由 I/O 調(diào)度器所決定的)或者 NULL 如果沒有請求要處理. elv_next_request 留這個請求在隊列上, 但是標(biāo)識它為活動的; 這個標(biāo)識阻止了 I/O 調(diào)度器試圖合并其他的請求到這些你開始執(zhí)行的.

為實際上從一個隊列中去除一個請求, 使用 blkdev_dequeue_request:


void blkdev_dequeue_request(struct request *req); 

如果你的驅(qū)動同時從同一個隊列中操作多個請求, 它必須以這樣的方式將它們解出隊列.

如果你由于同樣的理由需要放置一個出列請求回到隊列中, 你可以調(diào)用:


void elv_requeue_request(request_queue_t *queue, struct request *req); 

16.3.3.3.?隊列控制函數(shù)

塊層輸出了一套函數(shù), 可被驅(qū)動用來控制一個請求隊列如何操作. 這些函數(shù)包括:

void blk_stop_queue(request_queue_t queue);void blk_start_queue(request_queue_t queue);
如果你的設(shè)備已到到達(dá)一個狀態(tài), 它不能處理等候的命令, 你可調(diào)用 blk_stop_queue 來告知塊層. 在這個調(diào)用之后, 你的請求函數(shù)將不被調(diào)用直到你調(diào)用 blk_start_queue. 不用說, 你不應(yīng)當(dāng)忘記重啟隊列, 當(dāng)你的設(shè)備可處理更多請求時. 隊列鎖必須被持有當(dāng)調(diào)用任何一個這些函數(shù)時.

void blk_queue_bounce_limit(request_queue_t *queue, u64 dma_addr);
告知內(nèi)核你的設(shè)備可進(jìn)行 DMA 的最高物理地址的函數(shù). 如果一個請求包含一個超出這個限制的內(nèi)存引用, 一個反彈緩沖將被用來給這個操作; 當(dāng)然, 這是一個進(jìn)行塊 I/O 的昂貴方式, 并且應(yīng)當(dāng)盡量避免. 你可在這個參數(shù)中提供任何可能的值, 或者使用預(yù)先定義的符號 BLK_BOUNCE_HIGH(使用反彈緩沖給高內(nèi)存頁), BLK_BOUNCE_ISA (驅(qū)動只可 DMA 到 16MB 的 ISA 區(qū)), 或者BLK_BOUCE_ANY(驅(qū)動可進(jìn)行 DMA 到任何地址). 缺省值是 BLK_BOUNCE_HIGH.

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);
設(shè)置參數(shù)的函數(shù), 這些參數(shù)描述可被設(shè)備滿足的請求. blk_queue_max 可用來以扇區(qū)方式設(shè)置任一請求的最大的大小; 缺省是 255. blk_queue_max_phys_segments 和 blk_queue_max_hw_segments 都控制多少物理段(系統(tǒng)內(nèi)存中不相鄰的區(qū))可包含在一個請求中. 使用 blk_queue_max_phys_segments 來說你的驅(qū)動準(zhǔn)備處理多少段; 例如, 這可能是一個靜態(tài)分配的散布表的大小. blk_queue_max_hw_segments, 相反, 是設(shè)備可處理的最多的段數(shù). 這 2 個參數(shù)缺省都是 128. 最后, blk_queue_max_segment_size 告知內(nèi)核任一個請求的段可能是多大字節(jié); 缺省是 65,536 字節(jié).

blk_queue_segment_boundary(request_queue_t *queue, unsigned long mask);
一些設(shè)備無法處理跨越一個特殊大小內(nèi)存邊界的請求; 如果你的設(shè)備是其中之一, 使用這個函數(shù)來告知內(nèi)核這個邊界. 例如, 如果你的設(shè)備處理跨 4-MB 邊界的請求有困難, 傳遞一個 0x3fffff 掩碼. 缺省的掩碼是 0xffffffff.

void blk_queue_dma_alignment(request_queue_t *queue, int mask);
告知內(nèi)核關(guān)于你的設(shè)備施加于 DMA 傳送的內(nèi)存對齊限制的函數(shù). 所有的請求被創(chuàng)建有給定的對齊, 并且請求的長度也匹配這個對齊. 缺省的掩碼是 0x1ff, 它導(dǎo)致所有的請求被對齊到 512-字節(jié)邊界.

void blk_queue_hardsect_size(request_queue_t *queue, unsigned short max);
告知內(nèi)核你的設(shè)備的硬件扇區(qū)大小. 所有由內(nèi)核產(chǎn)生的請求是這個大小的倍數(shù)并且被正確對齊. 所有的在塊層和驅(qū)動之間的通訊繼續(xù)以 512-字節(jié)扇區(qū)來表達(dá), 但是.

16.3.4.?請求的分析

在我們的簡單例子里, 我們遇到了這個請求結(jié)構(gòu). 但是, 我們未曾接觸這個復(fù)雜的數(shù)據(jù)結(jié)構(gòu). 在本節(jié), 我們看, 詳細(xì)地, 塊 I/O 請求在 Linux 內(nèi)核中如何被表示.

每個請求結(jié)構(gòu)代表一個塊 I/O 請求, 盡管它可能是由幾個獨立的請求在更高層次合并而成. 對任何特殊的請求而傳送的扇區(qū)可能分布在整個主內(nèi)存, 盡管它們常常對應(yīng)塊設(shè)備中的多個連續(xù)的扇區(qū). 這個請求被表示為多個段, 每個對應(yīng)一個內(nèi)存中的緩沖. 內(nèi)核可能合并多個涉及磁盤上鄰近扇區(qū)的請求, 但是它從不合并在單個請求結(jié)構(gòu)中的讀和寫操作. 內(nèi)核還確保不合并請求, 如果結(jié)果會破壞任何的在前面章節(jié)中描述的請求隊列限制.

基本上, 一個請求結(jié)構(gòu)被實現(xiàn)為一個 bio 結(jié)構(gòu)的鏈表, 結(jié)合一些維護(hù)信息來使驅(qū)動可以跟蹤它的位置, 當(dāng)它在完成這個請求中. 這個 bio 結(jié)構(gòu)是一個塊 I/O 請求移植的低級描述; 我們現(xiàn)在看看它.

16.3.4.1.?bio 結(jié)構(gòu)

當(dāng)內(nèi)核, 以一個文件系統(tǒng)的形式, 虛擬文件子系統(tǒng), 或者一個系統(tǒng)調(diào)用, 決定一組塊必須傳送到或從一個塊 I/O 設(shè)備; 它裝配一個 bio 結(jié)構(gòu)來描述那個操作. 那個結(jié)構(gòu)接著被遞給這個塊 I/O 代碼, 這個代碼合并它到一個存在的請求結(jié)構(gòu), 或者, 如果需要, 創(chuàng)建一個新的. 這個 bio 結(jié)構(gòu)包含一個塊驅(qū)動需要來進(jìn)行請求的任何東西, 而不必涉及使這個請求啟動的用戶空間進(jìn)程.

bio 結(jié)構(gòu), 在 <linux/bio.h> 中定義, 包含許多成員對驅(qū)動作者是有用的:

sector_t bi_sector;
這個 bio 要被傳送的第一個(512字節(jié))扇區(qū).

unsigned int bi_size;
被傳送的數(shù)據(jù)大小, 以字節(jié)計. 相反, 常常更易使用 bio_sectors(bio), 一個給定以扇區(qū)計的大小的宏.

unsigned long bi_flags;
一組描述 bio 的標(biāo)志; 最低有效位被置位如果這是一個寫請求(盡管宏 bio_data_dir(bio)應(yīng)當(dāng)用來代替直接加鎖這個標(biāo)志).

unsigned short bio_phys_segments;unsigned short bio_hw_segments;
包含在這個 BIO 中的物理段的數(shù)目, 和在 DMA 映射完成后被硬件看到的段數(shù)目, 分別地.

一個 bio 的核心, 但是, 是一個稱為 bi_io_vec 的數(shù)組, 它由下列結(jié)構(gòu)組成:


struct bio_vec {
 struct page  *bv_page; 
 unsigned int  bv_len; 
 unsigned int  bv_offset;  
};  

bio 結(jié)構(gòu)顯示了這些結(jié)構(gòu)如何結(jié)合在一起. 如同你所見到的, 在一個塊 I/O 請求被轉(zhuǎn)換為一個 bio 結(jié)構(gòu)后, 它已被分為單獨的物理內(nèi)存頁. 所有的一個驅(qū)動需要做的事情是步進(jìn)全部這個結(jié)構(gòu)數(shù)組(它們有 bi_vcnt 個), 和在每個頁內(nèi)傳遞數(shù)據(jù)(但是只 len 字節(jié), 從 offset 開始).

圖?16.1.?bio 結(jié)構(gòu)

如果你的驅(qū)動器尊敬屏障請求, 第一步是通知塊層這個事實. 屏障處理是另一個請求隊列; 它被設(shè)置為:


void blk_queue_ordered(request_queue_t *queue, int flag);

為指示你的驅(qū)動實現(xiàn)了屏障請求, 設(shè)置 flag 參數(shù)為一個非零值.

實際的屏障請求實現(xiàn)是簡單地測試在請求結(jié)構(gòu)中關(guān)聯(lián)的標(biāo)志. 已經(jīng)提供了一個宏來進(jìn)行這個測試:


int blk_barrier_rq(struct request *req); 

如果這個宏返回一個非零值, 這個請求是一個屏障請求. 根據(jù)你的硬件如何工作, 你可能必須停止從隊列中獲取請求, 直到屏障請求已經(jīng)完成. 另外的驅(qū)動器能理解屏障請求; 在這個情況中, 你的驅(qū)動所有的必須做的是對這些驅(qū)動器發(fā)出正確的操作.

16.3.4.4.?不可重入請求

塊驅(qū)動常常試圖重試第一次失敗的請求. 這個做法可產(chǎn)生一個更加可靠的系統(tǒng)并且?guī)椭鷣肀苊鈹?shù)據(jù)丟失. 內(nèi)核, 但是, 有時標(biāo)識請求為不可重入的. 這樣的請求應(yīng)當(dāng)完全盡快失敗, 如果它們無法在第一次試的時候執(zhí)行.

如果你的驅(qū)動在考慮重試一個失敗的請求, 他應(yīng)當(dāng)首先調(diào)用:


int blk_noretry_request(struct request *req); 

如果這個宏返回非零值, 你的驅(qū)動應(yīng)當(dāng)放棄這個請求, 使用一個錯誤碼來代替重試它.

16.3.5.?請求完成函數(shù)

如同我們將見到的, 有幾個不同的方式來使用一個請求結(jié)構(gòu). 它們所有的都使用幾個通用的函數(shù), 但是, 它們處理一個 I/O 請求或者部分請求的完成. 這 2 個函數(shù)都是原子的并且可從一個原子上下文被安全地調(diào)用.

當(dāng)你的設(shè)備已經(jīng)完成傳送一些或者全部扇區(qū), 在一個 I/O 請求中, 它必須通知塊子系統(tǒng), 使用:


int end_that_request_first(struct request *req, int success, int count); 

這個函數(shù)告知塊代碼, 你的驅(qū)動已經(jīng)完成 count 個扇區(qū)地傳送, 從你最后留下的地方開始. 如果 I/O 是成功的, 傳遞 success 為 1; 否則傳遞 0. 注意你必須指出完成, 按照從第一個扇區(qū)到最后一個的順序; 如果你的驅(qū)動和設(shè)備有些共謀來亂序完成請求, 你必須存儲這個亂序的完成狀態(tài)直到介入的扇區(qū)已經(jīng)被傳遞.

從 end_that_request_first 的返回值是一個指示, 指示是否所有的這個請求中的扇區(qū)已經(jīng)被傳送或者沒有. 一個 0 返回值表示所有的扇區(qū)已經(jīng)被傳送并且這個請求完成. 在這點, 你必須使用 blkdev_dequeue_request 來從隊列中解除請求(如果你還沒有這樣做)并且傳遞它到:


void end_that_request_last(struct request *req); 

end_that_request_last 通知任何在等待這個請求的人, 這個請求已經(jīng)完成并且回收這個請求結(jié)構(gòu); 它必須在持有隊列鎖時被調(diào)用.

在我們的簡單的 sbull 例子里, 我們不使用任何上面的函數(shù). 相反, 那個例子, 被稱為 end_request. 為顯示這個調(diào)用的效果, 這里有整個的 end_request 函數(shù), 如果在 2.6.10 內(nèi)核中見到的:


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

 if (!end_that_request_first(req, uptodate, req->hard_cur_sectors)) {
 add_disk_randomness(req->rq_disk);
 blkdev_dequeue_request(req);
 end_that_request_last(req);
 }
}

函數(shù) add_disk_randomness 使用塊 I/O 請求的定時來貢獻(xiàn)熵給系統(tǒng)的隨機(jī)數(shù)池; 它應(yīng)當(dāng)被調(diào)用僅當(dāng)磁盤的定時是真正的隨機(jī)的. 對大部分的機(jī)械設(shè)備這是真的, 但是對一個基于內(nèi)存的虛擬設(shè)備它不是真的, 例如 sbull. 因此, 下一節(jié)中更復(fù)雜的 sbull 版本不調(diào)用 add_disk_randomness.

16.3.5.1.?使用 bio

現(xiàn)在你了解了足夠多的來編寫一個塊驅(qū)動, 可直接使用組成一個請求的 bio 結(jié)構(gòu). 但是, 一個例子可能會有幫助. 如果這個 sbull 驅(qū)動被加載為 request_mode 參數(shù)被設(shè)為 1, 它注冊一個知道 bio 的請求函數(shù)來代替我們上面見到的簡單函數(shù). 那個函數(shù)看來如此:


static void sbull_full_request(request_queue_t *q)
{
        struct request *req;
        int sectors_xferred;
        struct sbull_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 = sbull_xfer_request(dev, req);
                if (! end_that_request_first(req, 1, sectors_xferred)) {
                        blkdev_dequeue_request(req);
                        end_that_request_last(req);
                }
        }
}

這個函數(shù)簡單地獲取每個請求, 傳遞它到 sbull_xfer_request, 接著使用 end_that_request_first 和, 如果需要, end_that_request_last 來完成它. 因此, 這個函數(shù)在處理高級隊列并且請求管理部分問題. 真正執(zhí)行一個請求的工作, 但是, 落入 sbull_xfer_request:


static int sbull_xfer_request(struct sbull_dev *dev, struct request *req)
{
        struct bio *bio;
        int nsect = 0;

        rq_for_each_bio(bio, req)
        {
                sbull_xfer_bio(dev, bio);
                nsect += bio->bi_size/KERNEL_SECTOR_SIZE;

        }
        return nsect;
}

這里我們介紹另一個宏: rq_for_each_bio. 如同你可能期望的, 這個宏簡單地步入請求中的每個 bio 結(jié)構(gòu), 給我們一個可傳遞給 sbull_xfer_bio 用于傳輸?shù)闹羔? 那個函數(shù)看來如此:


static int sbull_xfer_bio(struct sbull_dev *dev, struct bio *bio)
{
        int i;
        struct bio_vec *bvec;
        sector_t sector = bio->bi_sector;

        /* Do each segment independently. */
        bio_for_each_segment(bvec, bio, i)
        {
                char *buffer = __bio_kmap_atomic(bio, i, KM_USER0);
                sbull_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; /* Always "succeed" */
}

這個函數(shù)簡單地步入每個 bio 結(jié)構(gòu)中的段, 獲得一個內(nèi)核虛擬地址來存取緩沖, 接著調(diào)用之前我們見到的同樣的 sbull_transfer 函數(shù)來拷貝數(shù)據(jù).

每個設(shè)備有它自己的需要, 但是, 作為一個通用的規(guī)則, 剛剛展示的代碼應(yīng)當(dāng)作為一個模型, 給許多的需要深入 bio 結(jié)構(gòu)的情形.

16.3.5.2.?塊請求和 DMA

如果你工作在一個高性能塊驅(qū)動上, 你有機(jī)會使用 DMA 來進(jìn)行真正的數(shù)據(jù)傳輸. 一個塊驅(qū)動當(dāng)然可步入 bio 結(jié)構(gòu), 如同上面描述的, 為每一個創(chuàng)建一個 DMA 映射, 并且傳遞結(jié)構(gòu)給設(shè)備. 但是, 有一個更容易的方法, 如果你的驅(qū)動可進(jìn)行發(fā)散/匯聚 I/O. 函數(shù):


int blk_rq_map_sg(request_queue_t *queue, struct request *req, struct scatterlist *list);

使用來自給定請求的全部段填充給定的列表. 內(nèi)存中鄰近的段在插入散布表之前被接合, 因此你不需要自己探測它們. 返回值是列表中的項數(shù). 這個函數(shù)還回傳, 在它第 3 個參數(shù), 一個適合傳遞給 dma_map_sg 的散布表.(關(guān)于 dma_map_sg 的更多信息見 15 章的"發(fā)散-匯聚映射"一節(jié)).

你的驅(qū)動必須在調(diào)用 blk_rq_map_sg 之前給散布表分配存儲. 這個列表必須能夠至少持有這個請求有的物理段那么多的項; struct request 成員 nr_phys_segments 持有那個數(shù)量, 它不能超過由 blk_queue_max_phys_segments 指定的物理段的最大數(shù)目.

如果你不想 blk_rq_map_sg 來接合鄰近的段, 你可改變這個缺省的行為, 使用一個調(diào)用諸如:


clear_bit(QUEUE_FLAG_CLUSTER, &queue->queue_flags); 

一些 SCSI 磁盤驅(qū)動用這樣的方式標(biāo)識它們的請求隊列, 因為它們沒有從接合請求中獲益.

16.3.5.3.?不用一個請求隊列

前面, 我們已經(jīng)討論了內(nèi)核所作的在隊列中優(yōu)化請求順序的工作; 這個工作包括排列請求和, 或許, 甚至延遲隊列來允許一個預(yù)期的請求到達(dá). 這些技術(shù)在處理一個真正的旋轉(zhuǎn)的磁盤驅(qū)動器時有助于系統(tǒng)的性能. 但是, 使用一個象 sbull 的設(shè)備它們是完全浪費了. 許多面向塊的設(shè)備, 例如閃存陣列, 用于數(shù)字相機(jī)的存儲卡的讀取器, 并且 RAM 盤真正地有隨機(jī)存取的性能, 包含從高級的請求隊列邏輯中獲益. 其他設(shè)備, 例如軟件 RAID 陣列或者被邏輯卷管理者創(chuàng)建的虛擬磁盤, 沒有這個塊層的請求隊列被優(yōu)化的性能特征. 對于這類設(shè)備, 它最好直接從塊層接收請求, 并且根本不去煩請求隊列.

對于這些情況, 塊層支持"無隊列"的操作模式. 為使用這個模式, 你的驅(qū)動必須提供一個"制作請求"函數(shù), 而不是一個請求函數(shù). make_request 函數(shù)有這個原型:


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

注意一個請求隊列仍然存在, 即便它從不會真正有任何請求. make_request 函數(shù)用一個 bio 結(jié)構(gòu)作為它的主要參數(shù), 這個 bio 結(jié)構(gòu)表示一個或多個要傳送的緩沖. make_request 函數(shù)做 2 個事情之一: 它可或者直接進(jìn)行傳輸, 或者重定向這個請求到另一個設(shè)備.

直接進(jìn)行傳送只是使用我們前面描述的存取者方法來完成這個 bio. 因為沒有使用請求結(jié)構(gòu), 但是, 你的函數(shù)應(yīng)當(dāng)通知這個 bio 結(jié)構(gòu)的創(chuàng)建者直接指出完成, 使用對 bio_endio 的調(diào)用:


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

這里, bytes 是你至今已經(jīng)傳送的字節(jié)數(shù). 它可小于由這個 bio 整體所代表的字節(jié)數(shù); 在這個方式中, 你可指示部分完成, 并且更新在 bio 中的內(nèi)部的"當(dāng)前緩沖"指針. 你應(yīng)當(dāng)再次調(diào)用 bio_endio 在你的設(shè)備進(jìn)行進(jìn)一步處理時, 或者當(dāng)你不能完成這個請求指出一個錯誤. 錯誤是通過提供一個非零值給 error 參數(shù)來指示的; 這個值通常是一個錯誤碼, 例如 -EIO. make_request 應(yīng)當(dāng)返回 0, 不管這個 I/O 是否成功.

如果 sbull 用 request_mode=2 加載, 它操作一個 make_request 函數(shù). 因為 sbull 已經(jīng)有一個函數(shù)看傳送單個 bio, 這個 make_request 函數(shù)簡單:


static int sbull_make_request(request_queue_t *q, struct bio *bio)
{
        struct sbull_dev *dev = q->queuedata;
        int status;
        status = sbull_xfer_bio(dev, bio);
        bio_endio(bio, bio->bi_size, status);
        return 0;
}

請注意你應(yīng)當(dāng)從不調(diào)用 bio_endio 從一個通常的請求函數(shù); 那個工作由 end_that_request_first 代替來處理.

一些塊驅(qū)動, 例如那些實現(xiàn)卷管理者和軟件 RAID 陣列的, 真正需要重定向請求到另一個設(shè)備來處理真正的 I/O. 編寫這樣的一個驅(qū)動超出了本書的范圍. 我們, 但是, 注意如果 make_request 函數(shù)返回一個非零值, bio 被再次提交. 一個"堆疊"驅(qū)動, 可, 因此, 修改 bi_bdev 成員來指向一個不同的設(shè)備, 改變起始扇區(qū)值, 接著返回; 塊系統(tǒng)接著傳遞 bio 到新設(shè)備. 還有一個 bio_split 調(diào)用來劃分一個 bio 到多個塊以提交給多個設(shè)備. 盡管如果隊列參數(shù)被之前設(shè)置, 劃分一個 bio 幾乎從不需要.

任何一個方式, 你都必須告知塊子系統(tǒng), 你的驅(qū)動在使用一個自定義的 make_request 函數(shù). 為此, 你必須分配一個請求隊列, 使用:


request_queue_t *blk_alloc_queue(int flags); 

這個函數(shù)不同于 blk_init_queue, 它不真正建立隊列來持有請求. flags 參數(shù)是一組分配標(biāo)志被用來為隊列分配內(nèi)存; 常常地正確值是 GFP_KERNEL. 一旦你有一個隊列, 傳遞它和你的 make_request 函數(shù)到 blk_queue_make_request:


void blk_queue_make_request(request_queue_t *queue, make_request_fn *func); 

sbull 代碼來設(shè)置 make_request 函數(shù), 象:


dev->queue = blk_alloc_queue(GFP_KERNEL);
if (dev->queue == NULL)
        goto out_vfree;
blk_queue_make_request(dev->queue, sbull_make_request);

對于好奇的人, 花些時間深入 drivers/block/ll_rw_block.c 會發(fā)現(xiàn), 所有的隊列都有一個 make_request 函數(shù). 缺省的版本, generic_make_request, 處理 bio 和一個請求結(jié)構(gòu)的結(jié)合. 通過提供一個它自己的 make_request 函數(shù), 一個驅(qū)動真正只覆蓋一個特定的請求隊列方法, 并且排序大部分工作.

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號