0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

IO請求在block layer的來龍去脈

Linux閱碼場 ? 來源:未知 ? 作者:胡薇 ? 2018-06-06 15:39 ? 次閱讀

所謂請求合并就是將進程內(nèi)或者進程間產(chǎn)生的在物理地址上連續(xù)的多個IO請求合并成單個IO請求一并處理,從而提升IO請求的處理效率。在前面有關(guān)通用塊層介紹的系列文章當(dāng)中我們或多或少地提及了IO請求合并的概念,本篇我們從頭集中梳理IO請求在block layer的來龍去脈,以此來增強對IO請求合并的理解。首先來看一張圖,下面的圖展示了IO請求數(shù)據(jù)由用戶進程產(chǎn)生,到最終持久化存儲到物理存儲介質(zhì),其間在內(nèi)核空間所經(jīng)歷的數(shù)據(jù)流以及IO請求合并可能的觸發(fā)點。

從內(nèi)核的角度而言,進程產(chǎn)生的IO路徑主要有圖中①②③所示的三條:

①緩存IO, 對應(yīng)圖中的路徑①,系統(tǒng)中絕大部分IO走的這種形式,充分利用filesystem 層的page cache所帶來的優(yōu)勢, 應(yīng)用程序產(chǎn)生的IO經(jīng)系統(tǒng)調(diào)用落入page cache之后便可以直接返回,page cache中的緩存數(shù)據(jù)由內(nèi)核回寫線程在適當(dāng)時機負責(zé)同步到底層的存儲介質(zhì)之上,當(dāng)然應(yīng)用程序也可以主動發(fā)起回寫過程(如fsync系統(tǒng)調(diào)用)來確保數(shù)據(jù)盡快同步到存儲介質(zhì)上,從而避免系統(tǒng)崩潰或者掉電帶來的數(shù)據(jù)不一致性。緩存IO可以帶來很多好處,首先應(yīng)用程序?qū)O丟給page cache之后就直接返回了,避免了每次IO都將整個IO協(xié)議棧走一遍,從而減少了IO的延遲。其次,page cache中的緩存最后以頁或塊為單位進行回寫,并非應(yīng)用程序向page cache中提交了幾次IO, 回寫的時候就需要往通用塊層提交幾次IO, 這樣在提交時間上不連續(xù)但在空間上連續(xù)的小塊IO請求就可以合并到同一個緩存頁中一并處理。再次,如果應(yīng)用程序之前產(chǎn)生的IO已經(jīng)在page cache中,后續(xù)又產(chǎn)生了相同的IO,那么只需要將后到的IO覆蓋page cache中的舊IO,這樣一來如果應(yīng)用程序頻繁的操作文件的同一個位置,我們只需要向底層存儲設(shè)備提交最后一次IO就可以了。最后,應(yīng)用程序?qū)懭氲絧age cache中的緩存數(shù)據(jù)可以為后續(xù)的讀操作服務(wù),讀取數(shù)據(jù)的時候先搜索page cache,如果命中了則直接返回,如果沒命中則從底層讀取并保存到page cache中,下次再讀的時候便可以從page cache中命中。

②非緩存IO(帶蓄流),對應(yīng)圖中的路徑②,這種IO繞過文件系統(tǒng)層的cache。用戶在打開要讀寫的文件的時候需要加上“O_DIRECT”標(biāo)志,意為直接IO,不讓文件系統(tǒng)的page cache介入。從用戶角度而言,應(yīng)用程序能直接控制的IO形式除了上面提到的“緩存IO”,剩下的IO都走的這種形式,就算文件打開時加上了 ”O(jiān)_SYNC” 標(biāo)志,最終產(chǎn)生的IO也會進入蓄流鏈表(圖中的Plug List)。如果應(yīng)用程序在用戶空間自己做了緩存,那么就可以使用這種IO方式,常見的如數(shù)據(jù)庫應(yīng)用。

③非緩存IO(不帶蓄流),對應(yīng)圖中的路徑③,內(nèi)核通用塊層的蓄流機制只給內(nèi)核空間提供了接口來控制IO請求是否蓄流,用戶空間進程沒有辦法控制提交的IO請求進入通用塊層的時候是否蓄流。嚴格的說用戶空間直接產(chǎn)生的IO都會走蓄流路徑,哪怕是IO的時候附上了“O_DIRECT” 和 ”O(jiān)_SYNC”標(biāo)志(可以參考《Linux通用塊層介紹(part1: bio層)》中的蓄流章節(jié)),用戶間接產(chǎn)生的IO,如文件系統(tǒng)日志數(shù)據(jù)、元數(shù)據(jù),有的不會走蓄流路徑而是直接進入調(diào)度隊列盡快得到調(diào)度。注意一點,通用塊層的蓄流只提供機制和接口而不提供策略,至于需不需要蓄流、何時蓄流完全由內(nèi)核中的IO派發(fā)者決定。

應(yīng)用程序不管使用圖中哪條IO路徑,內(nèi)核都會想方設(shè)法對IO進行合并。內(nèi)核為促進這種合并,在IO協(xié)議棧上設(shè)置了三個最佳狙擊點:

lCache (頁高速緩存)

lPlug List (蓄流鏈表)

lElevator Queue (調(diào)度隊列)

cache 合并

IO處在文件系統(tǒng)層的page cache中時只有IO數(shù)據(jù),還沒有IO請求(bio 或 request),只有page cache 在讀寫的時候才會產(chǎn)生IO請求。本文主要介紹IO請求在通用塊層的合并,因此對于IO 在cache 層的合并只做現(xiàn)象分析,不深入到內(nèi)部邏輯和代碼細節(jié)。如果是緩存IO,用戶進程提交的寫數(shù)據(jù)會積聚在page cache 中。cache 保存IO數(shù)據(jù)的基本單位為page,大小一般為4K, 因此cache 又叫“頁高速緩存”, 用戶進程提交的小塊數(shù)據(jù)可以緩存到cache中的同一個page中,最后回寫線程將一個page中的數(shù)據(jù)一次性提交給通用塊層處理。以dd程序?qū)懸粋€裸設(shè)備為例,每次寫1K數(shù)據(jù),連續(xù)寫16次:

dd if=/dev/zero of=/dev/sdb bs=1k count=16

通過blktrace觀測的結(jié)果為:

blktrace -d /dev/sdb -o - | blkparse -i -

bio請求在通用塊層的處理情況主要是通過第六列反映出來的,如果對blkparse的輸出不太了解,可以 man 一下blktrace。對照每一行的輸出來看看應(yīng)用程序產(chǎn)生的寫IO經(jīng)由page cache之后是如何派發(fā)到通用塊層的:

現(xiàn)階段只關(guān)注IO是如何從page cache中派發(fā)到通用塊層的,所以后面的瀉流、派發(fā)過程沒有貼出來?;貙懢€程–kworker以8個扇區(qū)(扇區(qū)大小為512B, 8個扇區(qū)為4K對應(yīng)一個page大?。閱挝粚d程序讀寫的1K數(shù)據(jù)塊派發(fā)給通用塊層處理。dd程序?qū)懥?6次,回寫線程只寫了4次(對應(yīng)四次Q),page cache的緩存功能有效的合并了應(yīng)用程序直接產(chǎn)生的IO數(shù)據(jù)。文件系統(tǒng)層的page cache對讀IO也有一定的作用,帶緩存的讀IO會觸發(fā)文件系統(tǒng)層的預(yù)讀機制,所謂預(yù)讀有專門的預(yù)讀算法,通過判斷用戶進程IO趨勢,提前將存儲介質(zhì)上的數(shù)據(jù)塊讀入page cache中,下次讀操作來時可以直接從page cache中命中,而不需要每次都發(fā)起對塊設(shè)備的讀請求。還是以dd程序讀一個裸設(shè)備為例,每次讀1K數(shù)據(jù),連續(xù)讀16次:

dd if=/dev/sdb of=/dev/zero bs=1K count=16

通過blktrace觀測的結(jié)果為:

blktrace -d /dev/sdb -o - | blkparse -i -

同樣只關(guān)注IO是如何從上層派發(fā)到通用塊層的,不關(guān)注IO在通用塊層的具體情況,先不考慮P,I,U,D,C等操作,那么上面的輸出可以簡單解析為:

讀操作是同步的,所以觸發(fā)讀請求的是dd進程本身。dd進程發(fā)起了16次讀操作,總共讀取16K數(shù)據(jù),但是預(yù)讀機制只向底層發(fā)送了兩次讀請求,分別為0+32(16K), 32+64(32K),總共預(yù)讀了16 + 32 = 48K數(shù)據(jù),并保存到cache中,多預(yù)讀的數(shù)據(jù)可以為后續(xù)的讀操作服務(wù)。

plug 合并

在閱讀本節(jié)之前可以先回顧下linuxer公眾號中介紹bio和request的系列文章,熟悉IO請求在通用塊層的處理,以及蓄流(plug)機制的原理和接口。特別推薦宋寶華老師寫的《文件讀寫(BIO)波瀾壯闊的一生》,通俗易懂地介紹了一個文件io的生命周期。

每個進程都有一個私有的蓄流鏈表,進程在往通用塊層派發(fā)IO之前如果開啟了蓄流功能,那么IO請求在被發(fā)送給IO調(diào)度器之前都保存在蓄流鏈表中,直到泄流(unplug)的時候才批量交給調(diào)度器。蓄流的主要目的就是為了增加請求合并的機會,bio在進入蓄流鏈表之前會嘗試與蓄流鏈表中保存的request進行合并,使用的接口為blk_attempt_plug_merge(). 本文是基于內(nèi)核4.17分析的,源碼來源于4.17-rc1。

代碼遍歷蓄流鏈表中的request,使用blk_try_merge找到一個能與bio合并的request并判斷合并類型,蓄流鏈表中的合并類型有三種:ELEVATOR_BACK_MERGE,ELEVATOR_FRONT_MERGE,ELEVATOR_DISCARD_MERGE。普通文件IO操作只會進行前兩種合并,第三種是丟棄操作的合并,不是普通的IO的合并,故不討論。

bio后向合并 (ELEVATOR_BACK_MERGE)

為了驗證IO請求在通用塊層的各種合并形式,準(zhǔn)備了以下測試程序,該測試程序使用內(nèi)核原生支持的異步IO引擎,可異步地向內(nèi)核一次提交多個IO請求。為了減少page cache和文件系統(tǒng)的干擾,使用O_DIRECT的方式直接向裸設(shè)備派發(fā)IO。

iotc.c

...

/* dispatch 3 4k-size ios using the io_type specified by user */

#define NUM_EVENTS 3

#define ALIGN_SIZE 4096

#define WR_SIZE 4096

enum io_type {

SEQUENCE_IO,/* dispatch 3 ios: 0-4k(0+8), 4-8k(8+8), 8-12k(16+8) */

REVERSE_IO,/* dispatch 3 ios: 8-12k(16+8), 4-8k(8+8),0-4k(0+8) */

INTERLEAVE_IO, /* dispatch 3 ios: 8-12k(16+8), 0-4k(0+8),4-8k(8+8) */ ,

IO_TYPE_END

};

int io_units[IO_TYPE_END][NUM_EVENTS] = {

{0, 1, 2},/* corresponding to SEQUENCE_IO */

{2, 1, 0},/* corresponding to REVERSE_IO */

{2, 0, 1}/* corresponding to INTERLEAVE_IO */

};

char *io_opt = "srid:";/* acceptable options */

int main(int argc, char *argv[])

{

int fd;

io_context_t ctx;

struct timespec tms;

struct io_event events[NUM_EVENTS];

struct iocb iocbs[NUM_EVENTS],

*iocbp[NUM_EVENTS];

int i, io_flag = -1;;

void *buf;

bool hit = false;

char *dev = NULL, opt;

/* io_flag and dev got set according the options passedby user , don’t paste the code of parsing here to shrink space */

fd = open(dev, O_RDWR | __O_DIRECT);

/* we can dispatch 32 IOs at 1 systemcall */

ctx = 0;

io_setup(32, &ctx);

posix_memalign(&buf,ALIGN_SIZE,WR_SIZE);

/* prepare IO request according to io_type */

for (i = 0; i < NUM_EVENTS; iocbp[i] = iocbs + i, ++i)

io_prep_pwrite(&iocbs[i], fd, buf, WR_SIZE,io_units[io_flag][i] * WR_SIZE);

/* submit IOs using io_submit systemcall */

io_submit(ctx, NUM_EVENTS, iocbp);

/* get the IO result with a timeout of 1S*/

tms.tv_sec = 1;

tms.tv_nsec = 0;

io_getevents(ctx, 1, NUM_EVENTS, events, &tms);

return 0;

}

測試程序接收兩個參數(shù),第一個為作用的設(shè)備,第二個為IO類型,定義了三種IO類型:SEQUENCE_IO(順序),REVERSE_IO(逆序),INTERLEAVE_IO(交替)分別用來驗證蓄流階段的bio后向合并、前向合并和泄流階段的request合并。為了減少篇幅,此處貼出的源碼刪除了選項解析和容錯處理,只保留主干,原版位于:https://github.com/liuzhengyuan/iotc。

為驗證bio在蓄流階段的后向合并,用上面的測試程序iotc順序派發(fā)三個寫io:

# ./iotc-d/dev/sdb-s

-d 指定作用的設(shè)備sdb, -s 指定IO方式為SEQUENCE_IO(順序),表示順序發(fā)起三個寫請求: bio0(0 + 8), bio1(8 + 8), bio2(16 + 8)。通過blktrace來觀察iotc派發(fā)的bio請求在通用塊層蓄流鏈表中的合并情況:

blktrace -d /dev/sdb -o - | blkparse -i -

上面的輸出可以簡單解析為:

第一個bio(bio0)進入通用塊層時,此時蓄流鏈表為空,于是申請一個request并用bio0初始化,再將request添加進蓄流鏈表,同時告訴blktrace蓄流已正式工作。第二個bio(bio1)到來的時候會走blk_attempt_plug_merge的邏輯,嘗試調(diào)用bio_attempt_back_merge與蓄流鏈表中的request合并,發(fā)現(xiàn)正好能合并到第一個bio所在的request尾部,于是直接返回。第三個bio(bio2)的處理與第二個同理。通過蓄流合并之后,三個IO請求最終合并成了一個request(0 + 24)。用一副圖來展示整個合并過程:

bio前向合并 (ELEVATOR_FRONT_MERGE)

為驗證bio在蓄流階段的前向合并,使用iotc逆序派發(fā)三個寫io:

# ./iotc-d/dev/sdb-r

-r 指定IO方式為REVERSE_IO(逆序),表示逆序發(fā)起三個寫請求: bio0(16 + 8),bio1(8 + 8), bio2(0 + 8)。blktrace的觀察結(jié)果為:

blktrace -d /dev/sdb -o - | blkparse -i -

上面的輸出可以簡單解析為:

與前面的后向合并相比,唯一的區(qū)別是合并方式由之前的”M”變成了現(xiàn)在的”F”,即在blk_attempt_plug_merge中走的是bio_attempt_front_merge分支。通過下面的圖來展示前向合并過程:

“plug 合并”不會做request與request的進階合并,蓄流鏈表中的request之間的合并會在泄流的時候做,即在下面介紹的“elevator 合并”中做。

elevator 合并

上面講到的蓄流鏈表合并是為進程內(nèi)的IO請求服務(wù)的,每個進程只往自己的蓄流鏈表中提交IO請求,進程間的蓄流鏈表相互獨立,互不干涉。但是,多個進程可以同時對一個設(shè)備發(fā)起IO請求,那么通用塊層還需要提供一個節(jié)點,讓進程間的IO請求有機會進行合并。一個塊設(shè)備有且僅有一個請求隊列(調(diào)度隊列),所有對塊設(shè)備的IO請求都需要經(jīng)過這個公共節(jié)點,因此調(diào)度隊列(Elevator Queue)是IO請求合并的另一個節(jié)點。先回顧一下通用塊層處理IO請求的核心函數(shù):blk_queue_bio(), 上層派發(fā)的bio請求都會流經(jīng)該函數(shù),或?qū)io蓄流到Plug List,或?qū)io合并到Elevator Queue, 或?qū)io生成request直接插入到Elevator Queue。blk_queue_bio()的主要處理流程為:

其中”A”標(biāo)識的“合并到蓄流鏈表的request中”就是上一章介紹的“plug 合并”。bio如果不能合并到蓄流鏈表中接下來會嘗試合并到“B”標(biāo)識的”合并到調(diào)度隊列的request中”。”合并到調(diào)度隊列的request中”只是“elevator 合并”的第一個點。你可能已經(jīng)發(fā)現(xiàn)了blk_queue_bio()將bio合并到蓄流鏈表或者將request添加進蓄流鏈表之后就沒管了,從路徑①可以發(fā)現(xiàn)蓄流鏈表中的request最終都是要交給電梯調(diào)度隊列的,這正是”elevator 合并”的第二個點,關(guān)于泄流的時機請參考我之前寫的《Linux通用塊層介紹(part1: bio層)》。下面分別介紹這兩個合并點:

bio合并到elevator

先看B表示的代碼段:

blk_queue_bio:

switch (elv_merge(q, &req, bio)) {

case ELEVATOR_BACK_MERGE:

if (!bio_attempt_back_merge(q, req, bio))

break;

elv_bio_merged(q, req, bio);

free = attempt_back_merge(q, req);

if (free)

__blk_put_request(q, free);

else

elv_merged_request(q, req, ELEVATOR_BACK_MERGE);

goto out_unlock;

case ELEVATOR_FRONT_MERGE:

if (!bio_attempt_front_merge(q, req, bio))

break;

elv_bio_merged(q, req, bio);

free = attempt_front_merge(q, req);

if (free)

__blk_put_request(q, free);

else

elv_merged_request(q, req, ELEVATOR_FRONT_MERGE);

goto out_unlock;

default:

break;

}

合并邏輯基本與”plug 合并”相似,先調(diào)用elv_merge接口判斷合并類型,然后根據(jù)是后向合并或是前向合并分別調(diào)用bio_attempt_back_merge和bio_attempt_front_merge進行合并操作,由于操作對象從蓄流鏈表變成了電梯調(diào)度隊列,bio合并完了之后還需額外干幾件事:

1.調(diào)用elv_bio_merged, 該函數(shù)會調(diào)用電梯調(diào)度器注冊的elevator_bio_merged_fn接口來通知調(diào)度器做相應(yīng)的處理,對于deadline調(diào)度器而言該接口為NULL。

2.尋找進階合并,參考我之前寫的《Linux通用塊層介紹(part2: request層)》中對進階合并的描述,如果bio產(chǎn)生了后向合并,則調(diào)用attempt_back_merge試圖進行后向進階合并,如果bio產(chǎn)生了前向合并,則調(diào)用attempt_front_merge企圖進行前向進階合并。deadline的進階合并接口為deadline_merged_requests, 被合并的request會從調(diào)度隊列中刪除。通過下面的圖示來展示后向進階合并過程,前向進階合并同理。

3.如果產(chǎn)生了進階合并,則被合并的request可以釋放了,參考上圖,可調(diào)用blk_put_request進行回收。如果只產(chǎn)生了bio合并,合并后的request的長度和扇區(qū)地址都會發(fā)生變化,需要調(diào)用elv_merged_request->elevator_merged_fn來更新合并后的請求在調(diào)度隊列的位置。deadline對應(yīng)的接口為deadline_merged_request,其相應(yīng)的操作為將合并的request先從調(diào)度隊列移出再重新插進去。

“bio合并到elevator”的合并形式只會發(fā)生在進程間,即只有一個進程在IO的時候不會產(chǎn)生這種合并形式,原因在于進程在向調(diào)度隊列派發(fā)IO請求或者試圖與將bio與調(diào)度隊列中的請求合并的時候是持有設(shè)備的隊列鎖得,其他進程是不能往調(diào)度隊列派發(fā)請求,這也是通用塊層單隊列通道窄需要發(fā)展多隊列的主要原因之一,只有進程在將調(diào)度隊列中的request逐個派發(fā)給驅(qū)動層的時候才會將設(shè)備隊列鎖重新打開,即只有當(dāng)一個進程在將調(diào)度隊列中request派發(fā)給驅(qū)動的時候其他進程才有機會將bio合并到還未派發(fā)完的request中。所以想通過簡單的IO測試程序來捕捉這種形式的合并比較困難,這對兩個IO進程的IO產(chǎn)生時序有非常高的要求,故不演示。有興趣的可以參考上面的github倉庫,里面有patch對內(nèi)核特定的請求派發(fā)位置加上延時來改變IO請求本來的時序,從而讓測試程序人為的達到這種碰撞效果。

request在泄流的時候合并到elevator

通用塊層的泄流接口為:blk_flush_plug_list(), 該接口主要的處理邏輯如下圖所示

其中請求合并發(fā)生的點在__elv_add_request()。blk_flush_plug_list會遍歷蓄流鏈表中的每個request,然后將每個request通過 _elv_add_request接口添加到調(diào)度隊列中,添加的過程中會嘗試與調(diào)度隊列中已有的request進行合并。

__elv_add_request:

case ELEVATOR_INSERT_SORT_MERGE:

/*

* If we succeed in merging this request with one in the

* queue already, we are done - rq has now been freed,

* so no need to do anything further.

*/

if (elv_attempt_insert_merge(q, rq))

break;

/* fall through */

case ELEVATOR_INSERT_SORT:

BUG_ON(blk_rq_is_passthrough(rq));

rq->rq_flags |= RQF_SORTED;

q->nr_sorted++;

if (rq_mergeable(rq)) {

elv_rqhash_add(q, rq);

if (!q->last_merge)

q->last_merge = rq;

}

q->elevator->type->ops.sq.elevator_add_req_fn(q, rq);

break;

泄流時走的是ELEVATOR_INSERT_SORT_MERGE分支,正如注釋所說的先讓蓄流的request調(diào)用elv_attempt_insert_merge嘗試與調(diào)度隊列中的request合并,如果不能合并則落入到ELEVATOR_INSERT_SORT分支,該分支直接調(diào)用電梯調(diào)度器注冊的elevator_add_req_fn接口將新來的request插入到調(diào)度隊列合適的位置。其中elv_rqhash_add是將新加入到調(diào)度隊列的request做hash索引,這樣做的好處是加快從調(diào)度隊列尋找可合并的request的索引速度。在泄流的時候調(diào)度隊列中既有其他進程產(chǎn)生的request,也有當(dāng)前進程從蓄流鏈表中派發(fā)的request(blk_flush_plug_list是先將所有request派發(fā)到調(diào)度隊列再一次性queue_unplugged,而不是派發(fā)一個request就queue_unplugged)。所以“request在泄流的時候合并到elevator”既是進程內(nèi)的,也可以是進程間的。elv_attempt_insert_merge的實現(xiàn)只做request間的后向合并,即只會將一個request合并到調(diào)度隊列中的request的尾部。這對于單進程IO而言足夠了,因為blk_flush_plug_list在泄流的時候已經(jīng)將蓄流鏈表中的request進行了list_sort(按扇區(qū)排序)。筆者曾經(jīng)提交過促進進程間request的前向合并的patch(見github),但沒被接收,maintainer–Jens的解析是這種IO場景很難發(fā)生,如果產(chǎn)生這種IO場景基本是應(yīng)用程序設(shè)計不合理。通過增加時間和空間來優(yōu)化一個并不常見的場景并不可取。最后通過一個例子來驗證進程內(nèi)“request在泄流的時候合并到elevator”,進程間的合并同樣對請求派發(fā)時序有很強的要求,在此不演示,github中有相應(yīng)的測試patch和測試方法。iotc使用下面的方式派發(fā)三個寫io:

# ./iotc-d/dev/sdb-i

-i指定IO方式為INTERLEAVE_IO(交替),表示按扇區(qū)交替的方式發(fā)起三個寫請求: bio0(16 + 8),bio1(8 + 8), bio2(0 + 8)。blktrace的觀察結(jié)果為:

blktrace -d /dev/sdb -o - | blkparse -i -

上面的輸出可以簡單解析為:

bio0(16 + 8)先到達plug list,bio1(0+8)到達時發(fā)現(xiàn)不能與plug list中的request合并,于是申請一個request添加到plug list。bio2(8+8)到達時首先與bio1進行后向合并。之后進程觸發(fā)泄流,泄流接口函數(shù)會將plug list中的request排序,因此request(0+16)先派發(fā)到調(diào)度隊列,此時調(diào)度隊列為空不能進行合并。然后派發(fā)request(16+8),派發(fā)時調(diào)用elv_attempt_insert_merge接口嘗試與調(diào)度隊列中的其他request進行合并,發(fā)現(xiàn)可以與request(0+16)進行后向合并,于是兩個request合并成一個,最后向設(shè)備驅(qū)動派發(fā)的只有一個request(0+24)。整個過程可以用下面的圖來展示:

小結(jié)

通過cache 、plug和elevator自上而下的三層狙擊,應(yīng)用程序產(chǎn)生的IO能最大限度的進行合并,從而提升IO帶寬,降低IO延遲,延長設(shè)備壽命。page cache打頭陣,既做數(shù)據(jù)緩存又做IO合并,主要是針對小塊IO進行合并,因為使用內(nèi)存頁做緩存,所以合并后的最大IO單元為頁大小,當(dāng)然對于大塊IO,page cache也會將它拆分成以頁為單位下發(fā),這不影響最終的效果,因為后面還有plug 和 elevator補刀。plug list竭盡全力合并進程內(nèi)產(chǎn)生的IO, 從設(shè)備的角度而言進程內(nèi)產(chǎn)生的IO相關(guān)性更強,合并的可能性更大,plug list設(shè)計位于elevator queue之上而且又是每個進程私有的,因此plug list既有利于IO合并,又減輕了elevator queue的負擔(dān)。elevator queue更多的是承擔(dān)進程間IO的合并,用來彌補plug list對進程間合并的不足,如果是帶緩存的IO,這種IO合并基本上不會出現(xiàn)。從實際應(yīng)用角度出發(fā),IO合并更多的是發(fā)生在page cache和plug list中。

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • IO
    IO
    +關(guān)注

    關(guān)注

    0

    文章

    434

    瀏覽量

    39049
  • Cache
    +關(guān)注

    關(guān)注

    0

    文章

    129

    瀏覽量

    28272

原文標(biāo)題:劉正元: Linux 通用塊層之IO合并

文章出處:【微信號:LinuxDev,微信公眾號:Linux閱碼場】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關(guān)推薦

    for always可以block中合成的嗎?

    (genvar)塊”和一個“always @ block”,我兩者中都有“for loops”。我的問題是這些“for loops”可以合成FPGA實現(xiàn)嗎?我知道Genvar塊是硬件,沒關(guān)系。我關(guān)注
    發(fā)表于 10-30 11:11

    Vivado 2015.2塊設(shè)計上打開子層次結(jié)構(gòu)彈出一個新的Block Design窗口

    假設(shè)我Vivado 2015.2的Block Design中有三層設(shè)計。此塊設(shè)計看起來像Hierarchy_0(Hierarchy_1(Hierarchy_2))。當(dāng)我雙擊
    發(fā)表于 12-25 10:58

    請問AD09如何為top layer 和bottom layer單獨設(shè)置keep out layer的大???

    AD09如何為top layer 和bottom layer 單獨設(shè)置keep out layer的大小
    發(fā)表于 05-27 01:05

    layer3編碼源碼

    layer3編碼源碼
    發(fā)表于 04-08 03:20 ?32次下載

    wcdma-physical layer

    wcdma-physical layer:
    發(fā)表于 06-04 17:27 ?22次下載
    wcdma-physical <b class='flag-5'>layer</b>

    CAN Physical Layer for Industr

    CAN Physical Layer for Industrial Application:Two-Wire Differential Transmission1. ScopeThe scope
    發(fā)表于 09-21 15:47 ?12次下載

    A CAN Physical Layer Discussio

    A CAN Physical Layer Discussion:Many network protocols are described using the sevenlayer Open
    發(fā)表于 10-01 16:59 ?10次下載

    什么是Transport Layer Security

    什么是Transport Layer Security   術(shù)語名稱:Transport Layer Security 術(shù)語解釋:傳輸層安全,確保無線局域網(wǎng)和因特網(wǎng)上通
    發(fā)表于 02-24 09:59 ?906次閱讀

    MAX14820 IO-Link設(shè)備收發(fā)器

    specifiedIO-Link data rates are supported. In IO-Link applications,the transceiver acts as the physical layer interfaceto a microcontro
    發(fā)表于 03-19 17:38 ?38次下載
    MAX14820 <b class='flag-5'>IO</b>-Link設(shè)備收發(fā)器

    http請求 get post

    Http請求類 packagewzh.Http; importjava.io.BufferedReader; importjava.io
    發(fā)表于 09-27 10:36 ?16次下載

    PCIe的Spec中明確規(guī)定只有Root有權(quán)限發(fā)起配置請求

    處理器一般不能夠直接發(fā)起配置讀寫請求,因為其只能產(chǎn)生Memory Request和IO Request。這就意味著Root必須要將處理器的相關(guān)請求轉(zhuǎn)換為配置讀寫請求。針對傳統(tǒng)的PCI設(shè)
    的頭像 發(fā)表于 05-04 09:12 ?6895次閱讀
    PCIe的Spec中明確規(guī)定只有Root有權(quán)限發(fā)起配置<b class='flag-5'>請求</b>

    layer是什么?解析ad9中的plane與layer

    layer是什么?PCB設(shè)計中layer是什么意思?PCB設(shè)計中多層板的層設(shè)置當(dāng)初困擾了很久,就是沒搞懂plane和layer的區(qū)別。 Multi-
    的頭像 發(fā)表于 10-16 11:37 ?8209次閱讀

    ACIS內(nèi)核和parasolid內(nèi)核的來龍去脈與比較

    ACIS內(nèi)核和parasolid內(nèi)核的來龍去脈與比較(深圳市普德新星電源技術(shù)有限公司)-ACIS內(nèi)核和parasolid內(nèi)核的來龍去脈與比較 ? ? ? ? ? ? ??
    發(fā)表于 08-31 16:52 ?11次下載
    ACIS內(nèi)核和parasolid內(nèi)核的<b class='flag-5'>來龍去脈</b>與比較

    查看linux系統(tǒng)磁盤io情況的辦法是什么

    談到 Linux 磁盤 I/O 的工作原理,我們了解到 Linux 存儲系統(tǒng) I/O 棧由文件系統(tǒng)層(file system layer)、通用塊層( general block layer)和設(shè)備層(device
    發(fā)表于 08-01 10:14 ?2403次閱讀

    什么是io多路復(fù)用?IO多路復(fù)用的優(yōu)缺點

    IO多路復(fù)用是一種同步IO模型,它允許單個進程/線程同時處理多個IO請求。具體來說,一個進程/線程可以監(jiān)視多個文件句柄,一旦某個文件句柄就緒,就能夠通知應(yīng)用程序進行相應(yīng)的讀寫操作。
    的頭像 發(fā)表于 01-18 15:48 ?1557次閱讀