国产gaysexchina男同gay,japanrcep老熟妇乱子伦视频,吃奶呻吟打开双腿做受动态图,成人色网站,国产av一区二区三区最新精品

16.3. 請求處理

2018-02-24 15:50 更新

16.3.?請求處理

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

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

16.3.1.?對請求方法的介紹

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


void request(request_queue_t *queue); 

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

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


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

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

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

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

16.3.2.?一個(gè)簡單的請求方法

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


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);
        }
}

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

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

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


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

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

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

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

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

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

有了這個(gè)信息, sbull 驅(qū)動可實(shí)現(xiàn)實(shí)際的數(shù)據(jù)傳送, 使用一個(gè)簡單的 memcpy 調(diào)用 -- 我們數(shù)據(jù)已經(jīng)在內(nèi)存, 畢竟. 進(jìn)行這個(gè)拷貝操作的函數(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);
}

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

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

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

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

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

16.3.3.?請求隊(duì)列

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

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

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

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

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

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

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


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

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

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

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


void blk_cleanup_queue(request_queue_t *); 

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

16.3.3.2.?排隊(duì)函數(shù)

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

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


struct request *elv_next_request(request_queue_t *queue); 

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

為實(shí)際上從一個(gè)隊(duì)列中去除一個(gè)請求, 使用 blkdev_dequeue_request:


void blkdev_dequeue_request(struct request *req); 

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

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


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

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

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

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

void blk_queue_bounce_limit(request_queue_t *queue, u64 dma_addr);
告知內(nèi)核你的設(shè)備可進(jìn)行 DMA 的最高物理地址的函數(shù). 如果一個(gè)請求包含一個(gè)超出這個(gè)限制的內(nèi)存引用, 一個(gè)反彈緩沖將被用來給這個(gè)操作; 當(dāng)然, 這是一個(gè)進(jìn)行塊 I/O 的昂貴方式, 并且應(yīng)當(dāng)盡量避免. 你可在這個(gè)參數(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ū))可包含在一個(gè)請求中. 使用 blk_queue_max_phys_segments 來說你的驅(qū)動準(zhǔn)備處理多少段; 例如, 這可能是一個(gè)靜態(tài)分配的散布表的大小. blk_queue_max_hw_segments, 相反, 是設(shè)備可處理的最多的段數(shù). 這 2 個(gè)參數(shù)缺省都是 128. 最后, blk_queue_max_segment_size 告知內(nèi)核任一個(gè)請求的段可能是多大字節(jié); 缺省是 65,536 字節(jié).

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

void blk_queue_dma_alignment(request_queue_t *queue, int mask);
告知內(nèi)核關(guān)于你的設(shè)備施加于 DMA 傳送的內(nèi)存對齊限制的函數(shù). 所有的請求被創(chuàng)建有給定的對齊, 并且請求的長度也匹配這個(gè)對齊. 缺省的掩碼是 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)生的請求是這個(gè)大小的倍數(shù)并且被正確對齊. 所有的在塊層和驅(qū)動之間的通訊繼續(xù)以 512-字節(jié)扇區(qū)來表達(dá), 但是.

16.3.4.?請求的分析

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

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

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

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

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

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

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

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

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

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

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

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

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


void blk_queue_ordered(request_queue_t *queue, int flag);

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

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


int blk_barrier_rq(struct request *req); 

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

16.3.4.4.?不可重入請求

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

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


int blk_noretry_request(struct request *req); 

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

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

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

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


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

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

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


void end_that_request_last(struct request *req); 

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

在我們的簡單的 sbull 例子里, 我們不使用任何上面的函數(shù). 相反, 那個(gè)例子, 被稱為 end_request. 為顯示這個(gè)調(diào)用的效果, 這里有整個(gè)的 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 請求的定時(shí)來貢獻(xiàn)熵給系統(tǒng)的隨機(jī)數(shù)池; 它應(yīng)當(dāng)被調(diào)用僅當(dāng)磁盤的定時(shí)是真正的隨機(jī)的. 對大部分的機(jī)械設(shè)備這是真的, 但是對一個(gè)基于內(nèi)存的虛擬設(shè)備它不是真的, 例如 sbull. 因此, 下一節(jié)中更復(fù)雜的 sbull 版本不調(diào)用 add_disk_randomness.

16.3.5.1.?使用 bio

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

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

這里我們介紹另一個(gè)宏: rq_for_each_bio. 如同你可能期望的, 這個(gè)宏簡單地步入請求中的每個(gè) bio 結(jié)構(gòu), 給我們一個(gè)可傳遞給 sbull_xfer_bio 用于傳輸?shù)闹羔? 那個(gè)函數(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" */
}

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

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

16.3.5.2.?塊請求和 DMA

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


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

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

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

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


clear_bit(QUEUE_FLAG_CLUSTER, &queue->queue_flags); 

一些 SCSI 磁盤驅(qū)動用這樣的方式標(biāo)識它們的請求隊(duì)列, 因?yàn)樗鼈儧]有從接合請求中獲益.

16.3.5.3.?不用一個(gè)請求隊(duì)列

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

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


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

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

直接進(jìn)行傳送只是使用我們前面描述的存取者方法來完成這個(gè) bio. 因?yàn)闆]有使用請求結(jié)構(gòu), 但是, 你的函數(shù)應(yīng)當(dāng)通知這個(gè) 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ù). 它可小于由這個(gè) bio 整體所代表的字節(jié)數(shù); 在這個(gè)方式中, 你可指示部分完成, 并且更新在 bio 中的內(nèi)部的"當(dāng)前緩沖"指針. 你應(yīng)當(dāng)再次調(diào)用 bio_endio 在你的設(shè)備進(jìn)行進(jìn)一步處理時(shí), 或者當(dāng)你不能完成這個(gè)請求指出一個(gè)錯(cuò)誤. 錯(cuò)誤是通過提供一個(gè)非零值給 error 參數(shù)來指示的; 這個(gè)值通常是一個(gè)錯(cuò)誤碼, 例如 -EIO. make_request 應(yīng)當(dāng)返回 0, 不管這個(gè) I/O 是否成功.

如果 sbull 用 request_mode=2 加載, 它操作一個(gè) make_request 函數(shù). 因?yàn)?sbull 已經(jīng)有一個(gè)函數(shù)看傳送單個(gè) bio, 這個(gè) 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 從一個(gè)通常的請求函數(shù); 那個(gè)工作由 end_that_request_first 代替來處理.

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

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


request_queue_t *blk_alloc_queue(int flags); 

這個(gè)函數(shù)不同于 blk_init_queue, 它不真正建立隊(duì)列來持有請求. flags 參數(shù)是一組分配標(biāo)志被用來為隊(duì)列分配內(nèi)存; 常常地正確值是 GFP_KERNEL. 一旦你有一個(gè)隊(duì)列, 傳遞它和你的 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);

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

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

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號