每個(gè)塊驅(qū)動(dòng)的核心是它的請(qǐng)求函數(shù). 這個(gè)函數(shù)是真正做工作的地方 --或者至少開(kāi)始的地方; 剩下的都是開(kāi)銷. 因此, 我們花不少時(shí)間來(lái)看在塊驅(qū)動(dòng)中的請(qǐng)求處理.
一個(gè)磁盤驅(qū)動(dòng)的性能可能是系統(tǒng)整個(gè)性能的關(guān)鍵部分. 因此, 內(nèi)核的塊子系統(tǒng)編寫(xiě)時(shí)在性能上考慮了很多; 它做所有可能的事情來(lái)使你的驅(qū)動(dòng)從它控制的設(shè)備上獲得最多. 這是一個(gè)好事情, 其中它盲目地使能快速 I/O. 另一方面, 塊子系統(tǒng)沒(méi)必要在驅(qū)動(dòng) API 中曝露大量復(fù)雜性. 有可能編寫(xiě)一個(gè)非常簡(jiǎn)單的請(qǐng)求函數(shù)( 我們將很快見(jiàn)到 ), 但是如果你的驅(qū)動(dòng)必須在一個(gè)高層次上操作復(fù)雜的硬件, 它可能是任何樣子.
塊驅(qū)動(dòng)的請(qǐng)求方法有下面的原型:
void request(request_queue_t *queue);
這個(gè)函數(shù)被調(diào)用, 無(wú)論何時(shí)內(nèi)核認(rèn)為你的驅(qū)動(dòng)是時(shí)候處理對(duì)設(shè)備的讀, 寫(xiě), 或者其他操作. 請(qǐng)求函數(shù)在返回之前實(shí)際不需要完成所有的在隊(duì)列中的請(qǐng)求; 實(shí)際上, 它可能不完成它們?nèi)魏我粋€(gè), 對(duì)大部分真實(shí)設(shè)備. 它必須, 但是, 驅(qū)動(dòng)這些請(qǐng)求并且確保它們最終被驅(qū)動(dòng)全部處理.
每個(gè)設(shè)備有一個(gè)請(qǐng)求隊(duì)列. 這是因?yàn)閷?shí)際的從和到磁盤的傳輸可能在遠(yuǎn)離內(nèi)核請(qǐng)求它們時(shí)發(fā)生, 并且因?yàn)閮?nèi)核需要這個(gè)靈活性來(lái)調(diào)度每個(gè)傳送, 在最好的時(shí)刻(將影響磁盤上鄰近扇區(qū)的請(qǐng)求集合到一起, 例如). 并且這個(gè)請(qǐng)求函數(shù), 你可能記得, 和一個(gè)請(qǐng)求隊(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í), 請(qǐng)求函數(shù)和它關(guān)聯(lián)到一起. 我們還提供了一個(gè)自旋鎖作為隊(duì)列創(chuàng)建過(guò)程的一部分. 無(wú)論何時(shí)我們的請(qǐng)求函數(shù)被調(diào)用, 內(nèi)核持有這個(gè)鎖. 結(jié)果, 請(qǐng)求函數(shù)在原子上下文中運(yùn)行; 它必須遵循所有的 5 章討論過(guò)的原子代碼的通用規(guī)則.
在你的請(qǐng)求函數(shù)持有鎖時(shí), 隊(duì)列鎖還阻止內(nèi)核去排隊(duì)任何對(duì)你的設(shè)備的其他請(qǐng)求. 在一些條件下, 你可能考慮在請(qǐng)求函數(shù)運(yùn)行時(shí)丟棄這個(gè)鎖. 如果你這樣做, 但是, 你必須保證不存取請(qǐng)求隊(duì)列, 或者任何其他的被這個(gè)鎖保護(hù)的數(shù)據(jù)結(jié)構(gòu), 在這個(gè)鎖不被持有時(shí). 你必須重新請(qǐng)求這個(gè)鎖, 在請(qǐng)求函數(shù)返回之前.
最后, 請(qǐng)求函數(shù)的啟動(dòng)(常常地)與任何用戶空間進(jìn)程之間是完全異步的. 你不能假設(shè)內(nèi)核運(yùn)行在發(fā)起當(dāng)前請(qǐng)求的進(jìn)程上下文. 你不知道由這個(gè)請(qǐng)求提供的 I/O 緩沖是否在內(nèi)核或者用戶空間. 因此任何類型的明確存取用戶空間的操作都是錯(cuò)誤的并且將肯定引起麻煩. 如你將見(jiàn)到的, 你的驅(qū)動(dòng)需要知道的關(guān)于請(qǐng)求的所有事情, 都包含在通過(guò)請(qǐng)求隊(duì)列傳遞給你的結(jié)構(gòu)中.
sbull 例子驅(qū)動(dòng)提供了幾個(gè)不同的方法給請(qǐng)求處理. 缺省地, sbull 使用一個(gè)方法, 稱為 sbull_request, 它打算作為一個(gè)最簡(jiǎn)單地請(qǐng)求方法的例子. 別忙, 它在這里:
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)在, 只需說(shuō)它表示一個(gè)我們要執(zhí)行的塊 I/O 請(qǐng)求.
內(nèi)核提供函數(shù) elv_next_request 來(lái)獲得隊(duì)列中第一個(gè)未完成的請(qǐng)求; 當(dāng)沒(méi)有請(qǐng)求要被處理時(shí)這個(gè)函數(shù)返回 NULL. 注意 elf_next 不從隊(duì)列里去除請(qǐng)求. 如果你連續(xù)調(diào)用它 2 次, 它 2 次都返回同一個(gè)請(qǐng)求結(jié)構(gòu). 在這個(gè)簡(jiǎn)單的操作模式中, 請(qǐng)求只在它們完成時(shí)被剝離隊(duì)列.
一個(gè)塊請(qǐng)求隊(duì)列可包含實(shí)際上不從磁盤和自磁盤移動(dòng)塊的請(qǐng)求. 這些請(qǐng)求可包括供應(yīng)商特定的, 低層的診斷操作或者和特殊設(shè)備模式相關(guān)的指令, 例如給可記錄介質(zhì)的報(bào)文寫(xiě)模式. 大部分塊驅(qū)動(dòng)不知道如何處理這樣的請(qǐng)求, 并且簡(jiǎn)單地失敗它們; sbull 也以這種方式工作. 對(duì) block_fs_request 的調(diào)用告訴我們是否我們?cè)诓榭匆粋€(gè)文件系統(tǒng)請(qǐng)求--一個(gè)一旦數(shù)據(jù)塊的. 如果這個(gè)請(qǐng)求不是一個(gè)文件系統(tǒng)請(qǐng)求, 我們傳遞它到 end_request:
void end_request(struct request *req, int succeeded);
當(dāng)我們處理了非文件系統(tǒng)請(qǐng)求, 之后我們傳遞 succeeded 為 0 來(lái)指示我們沒(méi)有成功完成這個(gè)請(qǐng)求. 否則, 我們調(diào)用 sbull_transfer 來(lái)真正移動(dòng)數(shù)據(jù), 使用一套在請(qǐng)求結(jié)構(gòu)中提供的成員:
sector_t sector;
我們?cè)O(shè)備上起始扇區(qū)的索引. 記住這個(gè)扇區(qū)號(hào), 象所有這樣的在內(nèi)核和驅(qū)動(dòng)之間傳遞的數(shù)目, 是以 512-字節(jié)扇區(qū)來(lái)表示的. 如果你的硬件使用一個(gè)不同的扇區(qū)大小, 你需要相應(yīng)地調(diào)整扇區(qū). 例如, 如果硬件是 2048-字節(jié)的扇區(qū), 你需要用 4 來(lái)除起始扇區(qū)號(hào), 在安放它到對(duì)硬件的請(qǐng)求之前.
unsigned long nr_sectors;
要被傳送的扇區(qū)(512-字節(jié))數(shù)目.
char *buffer;
一個(gè)指向緩沖的指針, 數(shù)據(jù)應(yīng)當(dāng)被傳送到或者從的緩沖. 這個(gè)指針是一個(gè)內(nèi)核虛擬地址并且可被驅(qū)動(dòng)直接解引用, 如果需要.
rq_data_dir(struct request *req);
這個(gè)宏從請(qǐng)求中抽取傳送的方向; 一個(gè) 0 返回值表示從設(shè)備中讀, 非 0 返回值表示寫(xiě)入設(shè)備.
有了這個(gè)信息, sbull 驅(qū)動(dòng)可實(shí)現(xiàn)實(shí)際的數(shù)據(jù)傳送, 使用一個(gè)簡(jiǎn)單的 memcpy 調(diào)用 -- 我們數(shù)據(jù)已經(jīng)在內(nèi)存, 畢竟. 進(jìn)行這個(gè)拷貝操作的函數(shù)( sbull_transfer ) 也處理扇區(qū)大小的調(diào)整, 并確保我們沒(méi)有拷貝超過(guò)我們的虛擬設(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è)完整的, 簡(jiǎn)單的基于 RAM 的磁盤設(shè)備. 但是, 對(duì)于很多類型的設(shè)備, 它不是一個(gè)實(shí)際的驅(qū)動(dòng), 由于幾個(gè)理由.
這些原因的第一個(gè)是 sbull 同步執(zhí)行請(qǐng)求, 一次一個(gè). 高性能的磁盤設(shè)備能夠在同時(shí)有很多個(gè)請(qǐng)求停留; 磁盤的板上控制器因此可以優(yōu)化的順序(有人希望)執(zhí)行它們. 如果我們只處理隊(duì)列中的第一個(gè)請(qǐng)求, 我們?cè)诮o定時(shí)間不能有多個(gè)請(qǐng)求被滿足. 能夠工作于多個(gè)請(qǐng)求要求對(duì)請(qǐng)求隊(duì)列和請(qǐng)求結(jié)構(gòu)的深入理解; 下面幾節(jié)會(huì)幫助來(lái)建立這種理解.
但是, 有另外一個(gè)問(wèn)題要考慮. 當(dāng)系統(tǒng)進(jìn)行大的傳輸, 包含多個(gè)在一起的磁盤扇區(qū), 就獲得最好的性能. 磁盤操作的最高開(kāi)銷常常是讀寫(xiě)頭的定位; 一旦這個(gè)完成, 實(shí)際上需要的讀或者寫(xiě)數(shù)據(jù)的時(shí)間幾乎可忽略. 設(shè)計(jì)和實(shí)現(xiàn)文件系統(tǒng)和虛擬內(nèi)存子系統(tǒng)的開(kāi)發(fā)者理解這點(diǎn), 因此他們盡力在磁盤上連續(xù)地查找相關(guān)的數(shù)據(jù), 并且在一次請(qǐng)求中傳送盡可能多扇區(qū). 塊子系統(tǒng)也在這個(gè)方面起作用; 請(qǐng)求隊(duì)列包含大量邏輯,目的是找到鄰近的請(qǐng)求并且接合它們?yōu)楦蟮牟僮?
sbull 驅(qū)動(dòng), 但是, 采取所有這些工作并且簡(jiǎn)單地忽略它. 一次只有一個(gè)緩沖被傳送, 意味著最大的單次傳送幾乎從不超過(guò)單個(gè)頁(yè)的大小. 一個(gè)塊驅(qū)動(dòng)能做的比那個(gè)要好的多, 但是它需要一個(gè)對(duì)請(qǐng)求結(jié)構(gòu)和bio結(jié)構(gòu)的更深的理解, 請(qǐng)求是從它們建立的.
下面幾節(jié)更深入地研究塊層如何完成它的工作, 已經(jīng)這些工作導(dǎo)致的數(shù)據(jù)結(jié)構(gòu).
最簡(jiǎn)單的說(shuō), 一個(gè)塊請(qǐng)求隊(duì)列就是: 一個(gè)塊 I/O 請(qǐng)求的隊(duì)列. 如果你往下查看, 一個(gè)請(qǐng)求隊(duì)列是一令人吃驚得復(fù)雜的數(shù)據(jù)結(jié)構(gòu). 幸運(yùn)的是, 驅(qū)動(dòng)不必?fù)?dān)心大部分的復(fù)雜性.
請(qǐng)求隊(duì)列跟蹤等候的塊I/O請(qǐng)求. 但是它們也在這些請(qǐng)求的創(chuàng)建中扮演重要角色. 請(qǐng)求隊(duì)列存儲(chǔ)參數(shù), 來(lái)描述這個(gè)設(shè)備能夠支持什么類型的請(qǐng)求: 它們的最大大小, 多少不同的段可進(jìn)入一個(gè)請(qǐng)求, 硬件扇區(qū)大小, 對(duì)齊要求, 等等. 如果你的請(qǐng)求隊(duì)列被正確配置了, 它應(yīng)當(dāng)從不交給你一個(gè)你的設(shè)備不能處理的請(qǐng)求.
請(qǐng)求隊(duì)列還實(shí)現(xiàn)一個(gè)插入接口, 這個(gè)接口允許使用多 I/O 調(diào)度器(或者電梯). 一個(gè) I/O 調(diào)度器的工作是提交 I/O 請(qǐng)求給你的驅(qū)動(dòng), 以最大化性能的方式. 為此, 大部分 I/O 調(diào)度器累積批量的 I/O 請(qǐng)求, 排列它們?yōu)檫f增(或遞減)的塊索引順序, 并且以那個(gè)順序提交請(qǐng)求給驅(qū)動(dòng). 磁頭, 當(dāng)給定一列排序的請(qǐng)求時(shí), 從磁盤的一頭到另一頭工作, 非常象一個(gè)滿載的電梯, 在一個(gè)方向移動(dòng)直到所有它的"請(qǐng)求"(等待出去的人)已被滿足. 2.6 內(nèi)核包含一個(gè)"底線調(diào)度器", 它努力確保每個(gè)請(qǐng)求在預(yù)設(shè)的最大時(shí)間內(nèi)被滿足, 以及一個(gè)"預(yù)測(cè)調(diào)度器", 它實(shí)際上短暫停止設(shè)備, 在一個(gè)預(yù)想中的讀請(qǐng)求之后, 這樣另一個(gè)鄰近的讀將幾乎是馬上到達(dá). 到本書(shū)為止, 缺省的調(diào)度器是預(yù)測(cè)調(diào)度器, 它看來(lái)有最好的交互的系統(tǒng)性能.
I/O 調(diào)度器還負(fù)責(zé)合并鄰近的請(qǐng)求. 當(dāng)一個(gè)新 I/O 請(qǐng)求被提交給調(diào)度器, 它在隊(duì)列里搜尋包含鄰近扇區(qū)的請(qǐng)求; 如果找到一個(gè), 并且如果結(jié)果的請(qǐng)求不是太大, 這 2 個(gè)請(qǐng)求被合并.
請(qǐng)求隊(duì)列有一個(gè) struct request_queue 或者 request_queue_t 類型. 這個(gè)類型, 和許多操作它的函數(shù), 定義在 <linux/blkdev.h>. 如果你對(duì)請(qǐng)求隊(duì)列的實(shí)現(xiàn)感興趣, 你可找到大部分代碼在 drivers/block/ll_rw_block.c 和 elevator.c.
如同我們?cè)谖覀兊睦哟a中見(jiàn)到的, 一個(gè)請(qǐng)求隊(duì)列是一個(gè)動(dòng)態(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ì)列的請(qǐng)求函數(shù)和一個(gè)控制對(duì)隊(duì)列存取的自旋鎖. 這個(gè)函數(shù)分配內(nèi)存(實(shí)際上, 不少內(nèi)存)并且可能失敗因?yàn)檫@個(gè); 你應(yīng)當(dāng)一直檢查返回值, 在試圖使用這個(gè)隊(duì)列之前.
作為初始化一個(gè)請(qǐng)求隊(duì)列的一部分, 你可設(shè)置成員 queuedata(它是一個(gè) void * 指針 )為任何你喜歡的值. 這個(gè)成員是請(qǐng)求隊(duì)列的對(duì)于我們?cè)谄渌Y(jié)構(gòu)中見(jiàn)到的 private_data 的對(duì)等體.
為返回一個(gè)請(qǐng)求隊(duì)列給系統(tǒng)(在模塊卸載時(shí)間, 通常), 調(diào)用 blk_cleanup_queue:
void blk_cleanup_queue(request_queue_t *);
這個(gè)調(diào)用后, 你的驅(qū)動(dòng)從給定的隊(duì)列中不再看到請(qǐng)求,并且不應(yīng)當(dāng)再次引用它.
有非常少的函數(shù)來(lái)操作隊(duì)列中的請(qǐng)求 -- 至少, 考慮到驅(qū)動(dòng). 你必須持有隊(duì)列鎖, 在你調(diào)用這些函數(shù)之前.
返回要處理的下一個(gè)請(qǐng)求的函數(shù)是 elv_next_request:
struct request *elv_next_request(request_queue_t *queue);
我們已經(jīng)在簡(jiǎn)單的 sbull 例子中見(jiàn)到這個(gè)函數(shù). 它返回一個(gè)指向下一個(gè)要處理的請(qǐng)求的指針(由 I/O 調(diào)度器所決定的)或者 NULL 如果沒(méi)有請(qǐng)求要處理. elv_next_request 留這個(gè)請(qǐng)求在隊(duì)列上, 但是標(biāo)識(shí)它為活動(dòng)的; 這個(gè)標(biāo)識(shí)阻止了 I/O 調(diào)度器試圖合并其他的請(qǐng)求到這些你開(kāi)始執(zhí)行的.
為實(shí)際上從一個(gè)隊(duì)列中去除一個(gè)請(qǐng)求, 使用 blkdev_dequeue_request:
void blkdev_dequeue_request(struct request *req);
如果你的驅(qū)動(dòng)同時(shí)從同一個(gè)隊(duì)列中操作多個(gè)請(qǐng)求, 它必須以這樣的方式將它們解出隊(duì)列.
如果你由于同樣的理由需要放置一個(gè)出列請(qǐng)求回到隊(duì)列中, 你可以調(diào)用:
void elv_requeue_request(request_queue_t *queue, struct request *req);
塊層輸出了一套函數(shù), 可被驅(qū)動(dòng)用來(lái)控制一個(gè)請(qǐng)求隊(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 來(lái)告知塊層. 在這個(gè)調(diào)用之后, 你的請(qǐng)求函數(shù)將不被調(diào)用直到你調(diào)用 blk_start_queue. 不用說(shuō), 你不應(yīng)當(dāng)忘記重啟隊(duì)列, 當(dāng)你的設(shè)備可處理更多請(qǐng)求時(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è)請(qǐng)求包含一個(gè)超出這個(gè)限制的內(nèi)存引用, 一個(gè)反彈緩沖將被用來(lái)給這個(gè)操作; 當(dāng)然, 這是一個(gè)進(jìn)行塊 I/O 的昂貴方式, 并且應(yīng)當(dāng)盡量避免. 你可在這個(gè)參數(shù)中提供任何可能的值, 或者使用預(yù)先定義的符號(hào) BLK_BOUNCE_HIGH(使用反彈緩沖給高內(nèi)存頁(yè)), BLK_BOUNCE_ISA (驅(qū)動(dòng)只可 DMA 到 16MB 的 ISA 區(qū)), 或者BLK_BOUCE_ANY(驅(qū)動(dòng)可進(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è)備滿足的請(qǐng)求. blk_queue_max 可用來(lái)以扇區(qū)方式設(shè)置任一請(qǐng)求的最大的大小; 缺省是 255. blk_queue_max_phys_segments 和 blk_queue_max_hw_segments 都控制多少物理段(系統(tǒng)內(nèi)存中不相鄰的區(qū))可包含在一個(gè)請(qǐng)求中. 使用 blk_queue_max_phys_segments 來(lái)說(shuō)你的驅(qū)動(dòng)準(zhǔn)備處理多少段; 例如, 這可能是一個(gè)靜態(tài)分配的散布表的大小. blk_queue_max_hw_segments, 相反, 是設(shè)備可處理的最多的段數(shù). 這 2 個(gè)參數(shù)缺省都是 128. 最后, blk_queue_max_segment_size 告知內(nèi)核任一個(gè)請(qǐng)求的段可能是多大字節(jié); 缺省是 65,536 字節(jié).
blk_queue_segment_boundary(request_queue_t *queue, unsigned long mask);
一些設(shè)備無(wú)法處理跨越一個(gè)特殊大小內(nèi)存邊界的請(qǐng)求; 如果你的設(shè)備是其中之一, 使用這個(gè)函數(shù)來(lái)告知內(nèi)核這個(gè)邊界. 例如, 如果你的設(shè)備處理跨 4-MB 邊界的請(qǐng)求有困難, 傳遞一個(gè) 0x3fffff 掩碼. 缺省的掩碼是 0xffffffff.
void blk_queue_dma_alignment(request_queue_t *queue, int mask);
告知內(nèi)核關(guān)于你的設(shè)備施加于 DMA 傳送的內(nèi)存對(duì)齊限制的函數(shù). 所有的請(qǐng)求被創(chuàng)建有給定的對(duì)齊, 并且請(qǐng)求的長(zhǎng)度也匹配這個(gè)對(duì)齊. 缺省的掩碼是 0x1ff, 它導(dǎo)致所有的請(qǐng)求被對(duì)齊到 512-字節(jié)邊界.
void blk_queue_hardsect_size(request_queue_t *queue, unsigned short max);
告知內(nèi)核你的設(shè)備的硬件扇區(qū)大小. 所有由內(nèi)核產(chǎn)生的請(qǐng)求是這個(gè)大小的倍數(shù)并且被正確對(duì)齊. 所有的在塊層和驅(qū)動(dòng)之間的通訊繼續(xù)以 512-字節(jié)扇區(qū)來(lái)表達(dá), 但是.
在我們的簡(jiǎn)單例子里, 我們遇到了這個(gè)請(qǐng)求結(jié)構(gòu). 但是, 我們未曾接觸這個(gè)復(fù)雜的數(shù)據(jù)結(jié)構(gòu). 在本節(jié), 我們看, 詳細(xì)地, 塊 I/O 請(qǐng)求在 Linux 內(nèi)核中如何被表示.
每個(gè)請(qǐng)求結(jié)構(gòu)代表一個(gè)塊 I/O 請(qǐng)求, 盡管它可能是由幾個(gè)獨(dú)立的請(qǐng)求在更高層次合并而成. 對(duì)任何特殊的請(qǐng)求而傳送的扇區(qū)可能分布在整個(gè)主內(nèi)存, 盡管它們常常對(duì)應(yīng)塊設(shè)備中的多個(gè)連續(xù)的扇區(qū). 這個(gè)請(qǐng)求被表示為多個(gè)段, 每個(gè)對(duì)應(yīng)一個(gè)內(nèi)存中的緩沖. 內(nèi)核可能合并多個(gè)涉及磁盤上鄰近扇區(qū)的請(qǐng)求, 但是它從不合并在單個(gè)請(qǐng)求結(jié)構(gòu)中的讀和寫(xiě)操作. 內(nèi)核還確保不合并請(qǐng)求, 如果結(jié)果會(huì)破壞任何的在前面章節(jié)中描述的請(qǐng)求隊(duì)列限制.
基本上, 一個(gè)請(qǐng)求結(jié)構(gòu)被實(shí)現(xiàn)為一個(gè) bio 結(jié)構(gòu)的鏈表, 結(jié)合一些維護(hù)信息來(lái)使驅(qū)動(dòng)可以跟蹤它的位置, 當(dāng)它在完成這個(gè)請(qǐng)求中. 這個(gè) bio 結(jié)構(gòu)是一個(gè)塊 I/O 請(qǐng)求移植的低級(jí)描述; 我們現(xiàn)在看看它.
當(dāng)內(nèi)核, 以一個(gè)文件系統(tǒng)的形式, 虛擬文件子系統(tǒng), 或者一個(gè)系統(tǒng)調(diào)用, 決定一組塊必須傳送到或從一個(gè)塊 I/O 設(shè)備; 它裝配一個(gè) bio 結(jié)構(gòu)來(lái)描述那個(gè)操作. 那個(gè)結(jié)構(gòu)接著被遞給這個(gè)塊 I/O 代碼, 這個(gè)代碼合并它到一個(gè)存在的請(qǐng)求結(jié)構(gòu), 或者, 如果需要, 創(chuàng)建一個(gè)新的. 這個(gè) bio 結(jié)構(gòu)包含一個(gè)塊驅(qū)動(dòng)需要來(lái)進(jìn)行請(qǐng)求的任何東西, 而不必涉及使這個(gè)請(qǐng)求啟動(dòng)的用戶空間進(jìn)程.
bio 結(jié)構(gòu), 在 <linux/bio.h> 中定義, 包含許多成員對(duì)驅(qū)動(dòng)作者是有用的:
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è)寫(xiě)請(qǐng)求(盡管宏 bio_data_dir(bio)應(yīng)當(dāng)用來(lái)代替直接加鎖這個(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é)合在一起. 如同你所見(jiàn)到的, 在一個(gè)塊 I/O 請(qǐng)求被轉(zhuǎn)換為一個(gè) bio 結(jié)構(gòu)后, 它已被分為單獨(dú)的物理內(nèi)存頁(yè). 所有的一個(gè)驅(qū)動(dòng)需要做的事情是步進(jìn)全部這個(gè)結(jié)構(gòu)數(shù)組(它們有 bi_vcnt 個(gè)), 和在每個(gè)頁(yè)內(nèi)傳遞數(shù)據(jù)(但是只 len 字節(jié), 從 offset 開(kāi)始).
圖?16.1.?bio 結(jié)構(gòu)
如果你的驅(qū)動(dòng)器尊敬屏障請(qǐng)求, 第一步是通知塊層這個(gè)事實(shí). 屏障處理是另一個(gè)請(qǐng)求隊(duì)列; 它被設(shè)置為:
void blk_queue_ordered(request_queue_t *queue, int flag);
為指示你的驅(qū)動(dòng)實(shí)現(xiàn)了屏障請(qǐng)求, 設(shè)置 flag 參數(shù)為一個(gè)非零值.
實(shí)際的屏障請(qǐng)求實(shí)現(xiàn)是簡(jiǎn)單地測(cè)試在請(qǐng)求結(jié)構(gòu)中關(guān)聯(lián)的標(biāo)志. 已經(jīng)提供了一個(gè)宏來(lái)進(jìn)行這個(gè)測(cè)試:
int blk_barrier_rq(struct request *req);
如果這個(gè)宏返回一個(gè)非零值, 這個(gè)請(qǐng)求是一個(gè)屏障請(qǐng)求. 根據(jù)你的硬件如何工作, 你可能必須停止從隊(duì)列中獲取請(qǐng)求, 直到屏障請(qǐng)求已經(jīng)完成. 另外的驅(qū)動(dòng)器能理解屏障請(qǐng)求; 在這個(gè)情況中, 你的驅(qū)動(dòng)所有的必須做的是對(duì)這些驅(qū)動(dòng)器發(fā)出正確的操作.
塊驅(qū)動(dòng)常常試圖重試第一次失敗的請(qǐng)求. 這個(gè)做法可產(chǎn)生一個(gè)更加可靠的系統(tǒng)并且?guī)椭鷣?lái)避免數(shù)據(jù)丟失. 內(nèi)核, 但是, 有時(shí)標(biāo)識(shí)請(qǐng)求為不可重入的. 這樣的請(qǐng)求應(yīng)當(dāng)完全盡快失敗, 如果它們無(wú)法在第一次試的時(shí)候執(zhí)行.
如果你的驅(qū)動(dòng)在考慮重試一個(gè)失敗的請(qǐng)求, 他應(yīng)當(dāng)首先調(diào)用:
int blk_noretry_request(struct request *req);
如果這個(gè)宏返回非零值, 你的驅(qū)動(dòng)應(yīng)當(dāng)放棄這個(gè)請(qǐng)求, 使用一個(gè)錯(cuò)誤碼來(lái)代替重試它.
如同我們將見(jiàn)到的, 有幾個(gè)不同的方式來(lái)使用一個(gè)請(qǐng)求結(jié)構(gòu). 它們所有的都使用幾個(gè)通用的函數(shù), 但是, 它們處理一個(gè) I/O 請(qǐng)求或者部分請(qǐng)求的完成. 這 2 個(gè)函數(shù)都是原子的并且可從一個(gè)原子上下文被安全地調(diào)用.
當(dāng)你的設(shè)備已經(jīng)完成傳送一些或者全部扇區(qū), 在一個(gè) I/O 請(qǐng)求中, 它必須通知塊子系統(tǒng), 使用:
int end_that_request_first(struct request *req, int success, int count);
這個(gè)函數(shù)告知塊代碼, 你的驅(qū)動(dòng)已經(jīng)完成 count 個(gè)扇區(qū)地傳送, 從你最后留下的地方開(kāi)始. 如果 I/O 是成功的, 傳遞 success 為 1; 否則傳遞 0. 注意你必須指出完成, 按照從第一個(gè)扇區(qū)到最后一個(gè)的順序; 如果你的驅(qū)動(dòng)和設(shè)備有些共謀來(lái)亂序完成請(qǐng)求, 你必須存儲(chǔ)這個(gè)亂序的完成狀態(tài)直到介入的扇區(qū)已經(jīng)被傳遞.
從 end_that_request_first 的返回值是一個(gè)指示, 指示是否所有的這個(gè)請(qǐng)求中的扇區(qū)已經(jīng)被傳送或者沒(méi)有. 一個(gè) 0 返回值表示所有的扇區(qū)已經(jīng)被傳送并且這個(gè)請(qǐng)求完成. 在這點(diǎn), 你必須使用 blkdev_dequeue_request 來(lái)從隊(duì)列中解除請(qǐng)求(如果你還沒(méi)有這樣做)并且傳遞它到:
void end_that_request_last(struct request *req);
end_that_request_last 通知任何在等待這個(gè)請(qǐng)求的人, 這個(gè)請(qǐng)求已經(jīng)完成并且回收這個(gè)請(qǐng)求結(jié)構(gòu); 它必須在持有隊(duì)列鎖時(shí)被調(diào)用.
在我們的簡(jiǎn)單的 sbull 例子里, 我們不使用任何上面的函數(shù). 相反, 那個(gè)例子, 被稱為 end_request. 為顯示這個(gè)調(diào)用的效果, 這里有整個(gè)的 end_request 函數(shù), 如果在 2.6.10 內(nèi)核中見(jiàn)到的:
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 請(qǐng)求的定時(shí)來(lái)貢獻(xiàn)熵給系統(tǒng)的隨機(jī)數(shù)池; 它應(yīng)當(dāng)被調(diào)用僅當(dāng)磁盤的定時(shí)是真正的隨機(jī)的. 對(duì)大部分的機(jī)械設(shè)備這是真的, 但是對(duì)一個(gè)基于內(nèi)存的虛擬設(shè)備它不是真的, 例如 sbull. 因此, 下一節(jié)中更復(fù)雜的 sbull 版本不調(diào)用 add_disk_randomness.
現(xiàn)在你了解了足夠多的來(lái)編寫(xiě)一個(gè)塊驅(qū)動(dòng), 可直接使用組成一個(gè)請(qǐng)求的 bio 結(jié)構(gòu). 但是, 一個(gè)例子可能會(huì)有幫助. 如果這個(gè) sbull 驅(qū)動(dòng)被加載為 request_mode 參數(shù)被設(shè)為 1, 它注冊(cè)一個(gè)知道 bio 的請(qǐng)求函數(shù)來(lái)代替我們上面見(jiàn)到的簡(jiǎn)單函數(shù). 那個(gè)函數(shù)看來(lái)如此:
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ù)簡(jiǎn)單地獲取每個(gè)請(qǐng)求, 傳遞它到 sbull_xfer_request, 接著使用 end_that_request_first 和, 如果需要, end_that_request_last 來(lái)完成它. 因此, 這個(gè)函數(shù)在處理高級(jí)隊(duì)列并且請(qǐng)求管理部分問(wèn)題. 真正執(zhí)行一個(gè)請(qǐng)求的工作, 但是, 落入 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è)宏簡(jiǎn)單地步入請(qǐng)求中的每個(gè) bio 結(jié)構(gòu), 給我們一個(gè)可傳遞給 sbull_xfer_bio 用于傳輸?shù)闹羔? 那個(gè)函數(shù)看來(lái)如此:
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ù)簡(jiǎn)單地步入每個(gè) bio 結(jié)構(gòu)中的段, 獲得一個(gè)內(nèi)核虛擬地址來(lái)存取緩沖, 接著調(diào)用之前我們見(jiàn)到的同樣的 sbull_transfer 函數(shù)來(lái)拷貝數(shù)據(jù).
每個(gè)設(shè)備有它自己的需要, 但是, 作為一個(gè)通用的規(guī)則, 剛剛展示的代碼應(yīng)當(dāng)作為一個(gè)模型, 給許多的需要深入 bio 結(jié)構(gòu)的情形.
如果你工作在一個(gè)高性能塊驅(qū)動(dòng)上, 你有機(jī)會(huì)使用 DMA 來(lái)進(jìn)行真正的數(shù)據(jù)傳輸. 一個(gè)塊驅(qū)動(dòng)當(dāng)然可步入 bio 結(jié)構(gòu), 如同上面描述的, 為每一個(gè)創(chuàng)建一個(gè) DMA 映射, 并且傳遞結(jié)構(gòu)給設(shè)備. 但是, 有一個(gè)更容易的方法, 如果你的驅(qū)動(dòng)可進(jìn)行發(fā)散/匯聚 I/O. 函數(shù):
int blk_rq_map_sg(request_queue_t *queue, struct request *req, struct scatterlist *list);
使用來(lái)自給定請(qǐng)求的全部段填充給定的列表. 內(nèi)存中鄰近的段在插入散布表之前被接合, 因此你不需要自己探測(cè)它們. 返回值是列表中的項(xiàng)數(shù). 這個(gè)函數(shù)還回傳, 在它第 3 個(gè)參數(shù), 一個(gè)適合傳遞給 dma_map_sg 的散布表.(關(guān)于 dma_map_sg 的更多信息見(jiàn) 15 章的"發(fā)散-匯聚映射"一節(jié)).
你的驅(qū)動(dòng)必須在調(diào)用 blk_rq_map_sg 之前給散布表分配存儲(chǔ). 這個(gè)列表必須能夠至少持有這個(gè)請(qǐng)求有的物理段那么多的項(xiàng); struct request 成員 nr_phys_segments 持有那個(gè)數(shù)量, 它不能超過(guò)由 blk_queue_max_phys_segments 指定的物理段的最大數(shù)目.
如果你不想 blk_rq_map_sg 來(lái)接合鄰近的段, 你可改變這個(gè)缺省的行為, 使用一個(gè)調(diào)用諸如:
clear_bit(QUEUE_FLAG_CLUSTER, &queue->queue_flags);
一些 SCSI 磁盤驅(qū)動(dòng)用這樣的方式標(biāo)識(shí)它們的請(qǐng)求隊(duì)列, 因?yàn)樗鼈儧](méi)有從接合請(qǐng)求中獲益.
前面, 我們已經(jīng)討論了內(nèi)核所作的在隊(duì)列中優(yōu)化請(qǐng)求順序的工作; 這個(gè)工作包括排列請(qǐng)求和, 或許, 甚至延遲隊(duì)列來(lái)允許一個(gè)預(yù)期的請(qǐng)求到達(dá). 這些技術(shù)在處理一個(gè)真正的旋轉(zhuǎn)的磁盤驅(qū)動(dòng)器時(shí)有助于系統(tǒng)的性能. 但是, 使用一個(gè)象 sbull 的設(shè)備它們是完全浪費(fèi)了. 許多面向塊的設(shè)備, 例如閃存陣列, 用于數(shù)字相機(jī)的存儲(chǔ)卡的讀取器, 并且 RAM 盤真正地有隨機(jī)存取的性能, 包含從高級(jí)的請(qǐng)求隊(duì)列邏輯中獲益. 其他設(shè)備, 例如軟件 RAID 陣列或者被邏輯卷管理者創(chuàng)建的虛擬磁盤, 沒(méi)有這個(gè)塊層的請(qǐng)求隊(duì)列被優(yōu)化的性能特征. 對(duì)于這類設(shè)備, 它最好直接從塊層接收請(qǐng)求, 并且根本不去煩請(qǐng)求隊(duì)列.
對(duì)于這些情況, 塊層支持"無(wú)隊(duì)列"的操作模式. 為使用這個(gè)模式, 你的驅(qū)動(dòng)必須提供一個(gè)"制作請(qǐng)求"函數(shù), 而不是一個(gè)請(qǐng)求函數(shù). make_request 函數(shù)有這個(gè)原型:
typedef int (make_request_fn) (request_queue_t *q, struct bio *bio);
注意一個(gè)請(qǐng)求隊(duì)列仍然存在, 即便它從不會(huì)真正有任何請(qǐng)求. make_request 函數(shù)用一個(gè) bio 結(jié)構(gòu)作為它的主要參數(shù), 這個(gè) bio 結(jié)構(gòu)表示一個(gè)或多個(gè)要傳送的緩沖. make_request 函數(shù)做 2 個(gè)事情之一: 它可或者直接進(jìn)行傳輸, 或者重定向這個(gè)請(qǐng)求到另一個(gè)設(shè)備.
直接進(jìn)行傳送只是使用我們前面描述的存取者方法來(lái)完成這個(gè) bio. 因?yàn)闆](méi)有使用請(qǐng)求結(jié)構(gòu), 但是, 你的函數(shù)應(yīng)當(dāng)通知這個(gè) bio 結(jié)構(gòu)的創(chuàng)建者直接指出完成, 使用對(duì) 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è)請(qǐng)求指出一個(gè)錯(cuò)誤. 錯(cuò)誤是通過(guò)提供一個(gè)非零值給 error 參數(shù)來(lái)指示的; 這個(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ù)簡(jiǎn)單:
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;
}
請(qǐng)注意你應(yīng)當(dāng)從不調(diào)用 bio_endio 從一個(gè)通常的請(qǐng)求函數(shù); 那個(gè)工作由 end_that_request_first 代替來(lái)處理.
一些塊驅(qū)動(dòng), 例如那些實(shí)現(xiàn)卷管理者和軟件 RAID 陣列的, 真正需要重定向請(qǐng)求到另一個(gè)設(shè)備來(lái)處理真正的 I/O. 編寫(xiě)這樣的一個(gè)驅(qū)動(dòng)超出了本書(shū)的范圍. 我們, 但是, 注意如果 make_request 函數(shù)返回一個(gè)非零值, bio 被再次提交. 一個(gè)"堆疊"驅(qū)動(dòng), 可, 因此, 修改 bi_bdev 成員來(lái)指向一個(gè)不同的設(shè)備, 改變起始扇區(qū)值, 接著返回; 塊系統(tǒng)接著傳遞 bio 到新設(shè)備. 還有一個(gè) bio_split 調(diào)用來(lái)劃分一個(gè) bio 到多個(gè)塊以提交給多個(gè)設(shè)備. 盡管如果隊(duì)列參數(shù)被之前設(shè)置, 劃分一個(gè) bio 幾乎從不需要.
任何一個(gè)方式, 你都必須告知塊子系統(tǒng), 你的驅(qū)動(dòng)在使用一個(gè)自定義的 make_request 函數(shù). 為此, 你必須分配一個(gè)請(qǐng)求隊(duì)列, 使用:
request_queue_t *blk_alloc_queue(int flags);
這個(gè)函數(shù)不同于 blk_init_queue, 它不真正建立隊(duì)列來(lái)持有請(qǐng)求. flags 參數(shù)是一組分配標(biāo)志被用來(lái)為隊(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 代碼來(lái)設(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);
對(duì)于好奇的人, 花些時(shí)間深入 drivers/block/ll_rw_block.c 會(huì)發(fā)現(xiàn), 所有的隊(duì)列都有一個(gè) make_request 函數(shù). 缺省的版本, generic_make_request, 處理 bio 和一個(gè)請(qǐng)求結(jié)構(gòu)的結(jié)合. 通過(guò)提供一個(gè)它自己的 make_request 函數(shù), 一個(gè)驅(qū)動(dòng)真正只覆蓋一個(gè)特定的請(qǐng)求隊(duì)列方法, 并且排序大部分工作.
更多建議: