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

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

3天內不再提示

Linux通用塊層之deadline簡介及通用塊層調度器框架

Linux閱碼場 ? 來源:未知 ? 作者:李倩 ? 2018-04-29 16:40 ? 次閱讀

deadline 簡介

deadline 是linux內核通用塊層中自帶的四個IO電梯調度器之一,其他的三個分別為noop(no operation)、cfq(completely fair queuing)、as(anticipatory scheduling),其中noop是個空的調度器,僅僅直接轉發(fā)IO請求,不提供排序和合并的功能,適合ssd和nvme等快速存儲設備。deadline 調度器由通用塊層的maintainer Jens Axboe在2002年首次寫出并于同期合并進內核,此后幾乎沒經(jīng)過大的修改,歷久彌堅,現(xiàn)在已是通用塊層單隊列的默認IO調度器。deadline相對與其他電梯調度器綜合考慮IO請求的吞吐量和時延性,最要體現(xiàn)在以下三個方面:

ü電梯性

ü饑餓性

ü超時性

各個性質的解析后面會具體介紹。測試數(shù)據(jù)表明,deadline在大多數(shù)多線程應用場景和數(shù)據(jù)庫應用場景下表現(xiàn)的性能要優(yōu)于cfq和as。

通用塊層調度器框架

內核通用塊層自帶四種IO調度器,每個塊設備在注冊的時候都會為其指定一種調度器,塊設備在運行的時候可以通過/sys接口動態(tài)調整當前塊設備使用的調度器,當然如果你喜歡,也可以自己實現(xiàn)一種滿足自己需求的IO調度器,并按照接口標準注冊到通用塊層。通用塊層為了滿足調度器的擴充,方便管理各式各樣的調度器,提煉出了一個統(tǒng)一的框架,稱之為電梯框架。每個調度器只需要需按照框架的標準實現(xiàn)自己調度策略的鉤子函數(shù),然后向電梯框架注冊自己,后續(xù)的塊設備就可以使用該注冊的調度器。內核中大量使用了設計與實現(xiàn)分離的編程手法,熟悉內核md(multi device)子系統(tǒng)的開發(fā)人員應該很了解這種手法,md子系統(tǒng)規(guī)范出一個統(tǒng)一的接口,然后各個特性化的raid實現(xiàn)這些接口,并向md層注冊自己。電梯調度器框架所包含的接口由內核源碼下的include/linux/elevator.h中的struct elevator_ops結構體定義,每個調度器根據(jù)自己的特性只需要實現(xiàn)其中部分接口即可。其中最基本的接口有四個:

lelevator_init_fn:調度器初始化接口,注冊時被調用,用于初始化調度器的私有數(shù)據(jù)結構;

lelevator_exit_fn:調度器退出接口,解注冊時被調用,用于釋放申請的數(shù)據(jù)結構;

lelevator_add_req_fn:調度器入口函數(shù),用于向調度器中添加IO請求;

lelevator_dispatch_fn:調度器出口函數(shù),用于將調度器內的IO請求派發(fā)給設備驅動;

deadline 實現(xiàn)原理

deadline調度器實現(xiàn)的接口有:

static struct elevator_type iosched_deadline = {

.ops.sq = {

.elevator_merge_fn = deadline_merge,

.elevator_merged_fn= deadline_merged_request,

.elevator_merge_req_fn = deadline_merged_requests,

.elevator_dispatch_fn = deadline_dispatch_requests,

.elevator_add_req_fn = deadline_add_request,

.elevator_former_req_fn = elv_rb_former_request,

.elevator_latter_req_fn = elv_rb_latter_request,

.elevator_init_fn = deadline_init_queue,

.elevator_exit_fn = deadline_exit_queue,

},

.elevator_attrs = deadline_attrs,

.elevator_name = "deadline",

.elevator_owner = THIS_MODULE,

};

這些接口在調度器注冊的時候告訴調度器框架本調度器對外暴露的方法,除了對外暴露的接口以外,deadline內部還有一些數(shù)據(jù)結構,用于快速的保存和操作IO請求,如紅黑樹,鏈表等。這些數(shù)據(jù)結構是deadline內部使用的,可以統(tǒng)稱為調度器的調度隊列,對外不可見,需要調度器的接口才能操作。

struct deadline_data {

struct rb_root sort_list[2];

struct list_head fifo_list[2];

struct request *next_rq[2];

unsigned int batching; /* number of sequential requests made */

unsigned int starved; /* times reads have starved writes */

int fifo_expire[2];

int fifo_batch;

int writes_starved;

int front_merges;

};

從面向對象的角度來理解deadline調度器的話,可以將deadline看作一個封裝好的類,上面介紹的接口就是這個類的方法,上面介紹的數(shù)據(jù)結構就是這個類的數(shù)據(jù)成員。deadline_init_queue是這個類的構造函數(shù),在給塊設備隊列分配調度器的時候被調用,用戶初始化類的數(shù)據(jù)成員。deadline_exit_queue相當于析構函數(shù),用于釋放調度器實例化時申請的資源。其他的接口包括將IO請求加入到調度器內部數(shù)據(jù)結構中,將IO請求從調度器內部派發(fā)出去,將一個bio合并到調度器內部的一個request中等。deadline內調度隊列中最重要的兩個成員為:

lstruct rb_root sort_list[2];

lstruct list_head fifo_list[2];

sort_list是兩顆紅黑樹,用于對io請求按起始扇區(qū)編號進行排序,deadline的電梯掃描就是基于這兩個紅黑樹進行的,這里有兩顆樹,一顆讀請求樹,一顆寫請求樹,分別作用于讀和寫。 fifo_list是普通的先進先出鏈表,也有兩個,分別對應讀和寫。每個IO請求在進入調度器的時候都會根據(jù)當前系統(tǒng)時間和超時時間給它賦上一個時間厝,然后根據(jù)IO方向添加到讀或者寫fifo_list,fifo_list頭部保存即將超時的IO請求,調度器在派發(fā)IO請求的時候會檢測fifo_list中是否有已經(jīng)超時的IO請求,如果有則必須進行派發(fā)處理,這就是deadline的字面意思,每個IO請求都有一個死亡線的截至時間,讀和寫的超時時間由fifo_expire成員定義,默認讀為500ms,寫為5s,因此讀的優(yōu)先級比寫要高,因為讀請求大部分情況下是同步的,需要盡快得到滿足。

deadline調度器的工作過程就是: 通用塊層通過deadline注冊的操作接口將IO請求提交給deadline,deadline在收到請求之后根據(jù)請求的扇區(qū)編號將請求在sort_list中排好序,同時給每個請求添加超時時間厝,并插入到fifo_list尾部。一個IO請求同時掛接在sort_list和fifo_list中。通用塊層需要派發(fā)IO請求(瀉流或者direct IO)的時候也是調用deadline注冊的電梯派發(fā)接口,deadline在派發(fā)一個IO請求時會基于電梯算法綜合考慮IO請求是否超時、寫?zhàn)囸I是否觸發(fā)、是否滿足批量條件來決定到底派發(fā)哪個IO請求,派發(fā)之后會將該IO請求同時從sort_list和fifo_list中刪除。deadline服務的整個過程都是由通用塊層驅動的,換言之deadline是被動的,在內核中不提供工作隊列或工作線程。

deadline IO請求的添加

deadline 的調度隊列(數(shù)據(jù)結構)容納的是request請求,本系列文章的《Linux通用塊層介紹(part1: bio層)》、《Linux通用塊層介紹(part2: request層)》從宏觀上介紹了bio請求如何轉化為request請求并添加到調度器的調度隊列中、如何合并進現(xiàn)有的request請求(包括plug 鏈表中的request和調度隊列中的request)。對于deadline來說,IO請求的合并和排序都是在請求添加到deadline 的調度隊列中的過程完成的。bio請求進入deadline 調度隊列有兩條路徑:

bio通過deadline_add_request接口添加到調度隊列

deadline_add_request接口的參數(shù)形式是request,bio在通用塊層會申請一個新的request或者合并到plug 鏈表中的現(xiàn)有的request中,最后調用deadline_add_request添加到調度隊列。

/*

* add rq to rbtree and fifo

*/

static void

deadline_add_request(struct request_queue *q, struct request *rq)

{

struct deadline_data *dd = q->elevator->elevator_data;

const int data_dir = rq_data_dir(rq);

deadline_add_rq_rb(dd, rq);

/*

* set expire time and add to fifo list

*/

rq->fifo_time = jiffies + dd->fifo_expire[data_dir];

list_add_tail(&rq->queuelist, &dd->fifo_list[data_dir]);

}

接口很簡單,首先判斷request的IO方向,根據(jù)IO方向通過deadline_add_rq_rb將request添加到讀/寫紅黑樹中,request在紅黑樹中以請求的起始扇區(qū)作為節(jié)點的key value,可以直接認為紅黑樹中的request是按扇區(qū)增加的方向排好了序。然后給request添加一個超時時間厝,根據(jù)IO方向添加到先進先出鏈表尾部,fifo_list中同一IO方向上的request具有相同的fifo_expire,所以先添加進來的request先超時,每次都是從尾部添加,所以頭部的request先超時,這也是fifo_list名字的由來吧。

bio通過deadline_merge*接口合并到調度隊列中的request

在《Linux通用塊層介紹(part2: request層)》中我們介紹了通用塊層的blk_queue_bio接口,其中有如何一段代碼:

static blk_qc_t blk_queue_bio(struct request_queue *q, struct bio *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;

}

...

}

這段代碼的意思就是嘗試將bio合并到電梯調度隊列中現(xiàn)有的request中去,首先調用elv_merge->deadline_merge判斷是哪種合并(見《Linux通用塊層介紹(part2: request層)》中介紹的合并類型),如果可以合并,則根據(jù)合并類型進行相應的合并,bio合并到request的功能由通用塊層的bio_attempt_front_merge/bio_attempt_back_merge接口完成。對于deadline IO調度器來說,request在紅黑樹中是以起始扇區(qū)作為key value的,如果是前向合并則會改變合并后的request的起始扇區(qū)號,因此bio合并之后還需調用elv_bio_merged->eadline_merged_request來重新更新request在紅黑樹中的key value和位置。不管是前向合并還是后向合并,都有可能觸發(fā)進階合并,最后都會調用一次attempt_front/back_merge->deadline_merged_requests來處理這種進階合并,過程就是調用deadline_remove_request接口將被合并的request從sort_list和fifo_list中刪除,同時更新合并后的request的超時時間。

deadline IO請求的派發(fā)

deadline 調度算法的核心思想就蘊含在請求的派發(fā)接口中,接口代碼比較簡潔,邏輯清晰,單次調用只派發(fā)一個request請求,調度數(shù)據(jù)結構記錄了派發(fā)的上下文,調度時充分權衡了請求的電梯性、饑餓性、超時性。

/*

* deadline_dispatch_requests selects the best request according to

* read/write expire, fifo_batch, etc

*/

static int deadline_dispatch_requests(struct request_queue *q, int force)

{

struct deadline_data *dd = q->elevator->elevator_data;

const int reads = !list_empty(&dd->fifo_list[READ]);

const int writes = !list_empty(&dd->fifo_list[WRITE]);

struct request *rq;

int data_dir;

/*

* batches are currently reads XOR writes

*/

if (dd->next_rq[WRITE])

rq = dd->next_rq[WRITE];

else

rq = dd->next_rq[READ];

if (rq && dd->batching < dd->fifo_batch)

/* we have a next request are still entitled to batch */

goto dispatch_request;

/*

* at this point we are not running a batch. select the appropriate

* data direction (read / write)

*/

if (reads) {

BUG_ON(RB_EMPTY_ROOT(&dd->sort_list[READ]));

if (writes && (dd->starved++ >= dd->writes_starved))

goto dispatch_writes;

data_dir = READ;

goto dispatch_find_request;

}

/*

* there are either no reads or writes have been starved

*/

if (writes) {

dispatch_writes:

BUG_ON(RB_EMPTY_ROOT(&dd->sort_list[WRITE]));

dd->starved = 0;

data_dir = WRITE;

goto dispatch_find_request;

}

return 0;

dispatch_find_request:

/*

* we are not running a batch, find best request for selected data_dir

*/

if (deadline_check_fifo(dd, data_dir) || !dd->next_rq[data_dir]) {

/*

* A deadline has expired, the last request was in the other

* direction, or we have run out of higher-sectored requests.

* Start again from the request with the earliest expiry time.

*/

rq = rq_entry_fifo(dd->fifo_list[data_dir].next);

} else {

/*

* The last req was the same dir and we have a next request in

* sort order. No expired requests so continue on from here.

*/

rq = dd->next_rq[data_dir];

}

dd->batching = 0;

dispatch_request:

/*

* rq is the selected appropriate request.

*/

dd->batching++;

deadline_move_request(dd, rq);

return 1;

}

deadline的電梯性

電梯調度器最重要的屬性就是保持IO請求按適應磁頭臂移動的方向去派發(fā)請求,從而盡可能的減少IO請求的在磁盤上的尋道時間,提升IO處理能力,類似于電梯移動算法,盡可能減少移動距離來提升運行效率,這就是IO調度器稱之為電梯調度器的原因。硬盤的讀寫原理這篇博文詳細介紹了磁盤構造原理以及為什么需要IO調度的原因。deadline的電梯性是通過批量(batching)機制來實現(xiàn)的,前面介紹了IO請求在紅黑樹中已排好序,deadline每次派發(fā)完一個request請求都會將紅黑樹中排好序的下一個request(如果有)暫存到next_rq成員中,下一次再次調用deadline_dispatch_requests時直接派發(fā)next_rq成員中的request,同時按紅黑樹的順序更新next_rq。如果一直按照這種思路派發(fā),那么相反IO方向的請求有可能很長時間得不到響應,同一IO方向的請求也有可能出現(xiàn)超時。因此這種完全按紅黑樹的順序派發(fā)請求的機制必須有個限制,deadline 中的batching和fifo_batch就是起這個作用的,每順序派發(fā)一次,batching加一,如果batching超過了fifo_batch(默認16)就需要考量其他派發(fā)因子,如請求超時,fifo_batch設置過大會提升IO請求的吞吐率,增大IO請求的延遲,反之相反。這種同IO方向上的按順序批次派發(fā)我們稱只為批量機制,這種批量機制可能提前結束,如IO方向上沒有可繼續(xù)派發(fā)的IO請求了。

deadline的饑餓性

deadline的饑餓性說的是寫?zhàn)囸I,寫?zhàn)囸I產生的原因是deadline給予讀方向的IO請求更高的優(yōu)先級,即優(yōu)先處理讀請求,這是因為從實際應用角度來說讀請求一般都是同步的,給予讀更高的優(yōu)先級有助于提升用戶體驗??偸且恢眱?yōu)先處理讀請求勢必會造成寫請求的饑餓,為此deadline引入starved和writes_starved成員變量來防止寫?zhàn)囸I,每優(yōu)先處理一次讀請求則計數(shù)器starved加一,一旦starved達到閥值writes_starved(默認2)且還有寫請求,則下次派發(fā)時轉向派發(fā)寫請求。考慮到deadline的批量機制,默認情況下寫請求最多讓步于16 * 2 = 32個讀請求。

deadline的超時性

deadline的饑餓性只會影響IO請求派發(fā)的方向,deadline的電梯性和超時性決定派發(fā)哪個IO請求。前面已經(jīng)介紹了每個進入調度器的request都會按照超時先后掛接到fifo_list中,頭部的request先超時,尾部的request后超時。在處理完一個批次的request之后,deadline會根據(jù)饑餓性確定的IO方向上有沒有request超時,如果有超時的request則轉向處理該request(fifo_list頭部request)。如果還沒有request超時,且同方向的next_rq有暫存的request,則繼續(xù)批量化處理next_rq中的request。如此以來,fifo_batch并不是批量機制的上限,只是給超時和饑餓的request提供處理的機會,如果即沒有超時又沒有饑餓則當然繼續(xù)按順序批量化處理request會提升效率。

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

    關注

    87

    文章

    11212

    瀏覽量

    208721
  • 調度器
    +關注

    關注

    0

    文章

    98

    瀏覽量

    5232

原文標題:劉正元: Linux 通用塊層之DeadLine IO調度器

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

收藏 人收藏

    評論

    相關推薦

    嵌入式操作系統(tǒng)的通用硬件抽象設計

    是操作系統(tǒng)內核所管理的任務的重要組成部分,是CPU內核的寄存中內容的映像,因此上下文管理的實現(xiàn)依賴于CPU內核中寄存的組織,是與體系結構密切相關的。通用硬件抽象的任務上下文管理統(tǒng)
    發(fā)表于 12-07 10:30

    PCB熱設計通用的4PCB覆蓋

      4.4.3 實驗設計9:通用的4PCB  通過增加兩個內部信號,實驗設計6的2疊加現(xiàn)在將增加到4。與以前一樣,假設這些
    發(fā)表于 04-21 15:04

    EPA功能及用戶技術研究

    EPA功能及用戶技術研究 Research on EPA Functional Block and User Layer Technology
    發(fā)表于 03-17 09:14 ?16次下載

    嵌入式操作系統(tǒng)的通用硬件抽象設計

    摘要 基于嵌入式操作系統(tǒng)硬件抽象層理論,設計一種用于嵌入式操作系統(tǒng)內核開發(fā)的通用硬件抽象平臺。通用硬件抽象能夠為嵌入式操作系統(tǒng)內核的設計開發(fā)屏蔽硬件平
    發(fā)表于 03-29 15:16 ?1188次閱讀
    嵌入式操作系統(tǒng)的<b class='flag-5'>通用</b>硬件抽象<b class='flag-5'>層</b>設計

    Linux的那些事兒我是Block

    Linux的那些事兒我是Block
    發(fā)表于 10-29 09:43 ?9次下載
    <b class='flag-5'>Linux</b>的那些事兒<b class='flag-5'>之</b>我是Block<b class='flag-5'>層</b>

    基于的組成“bio”的詳細解析

    在深挖bio之前,很有必要先了解點背景知識,看看之上的天地。這里“之上”意思是靠近用戶空間(the top),遠離硬件(the bottom),包括所有使用
    的頭像 發(fā)表于 02-03 16:23 ?4029次閱讀
    基于<b class='flag-5'>塊</b><b class='flag-5'>層</b>的組成“bio<b class='flag-5'>層</b>”的詳細解析

    Linux IO系統(tǒng)簡介調度的工作流程詳細概述

    Linux內核組件要讀寫一些數(shù)據(jù)時,并不是請求一發(fā)出,內核便立即執(zhí)行該請求,而是將其推遲執(zhí)行。當傳輸一個新數(shù)據(jù)時,內核需要檢查它能否通過。Linux IO調度程序是介于
    的頭像 發(fā)表于 05-27 10:41 ?5068次閱讀
    <b class='flag-5'>Linux</b> IO系統(tǒng)<b class='flag-5'>簡介</b>和<b class='flag-5'>調度</b><b class='flag-5'>器</b>的工作流程詳細概述

    如何更改 Linux 的 I/O 調度

    Linux 的 I/O 調度是一個以式 I/O 訪問存儲卷的進程,有時也叫磁盤調度。
    發(fā)表于 05-15 15:54 ?832次閱讀
    如何更改 <b class='flag-5'>Linux</b> 的 I/O <b class='flag-5'>調度</b><b class='flag-5'>器</b>

    LTC1060:通用雙濾波構建數(shù)據(jù)Sheet

    LTC1060:通用雙濾波構建數(shù)據(jù)Sheet
    發(fā)表于 05-19 21:11 ?4次下載
    LTC1060:<b class='flag-5'>通用</b>雙濾波<b class='flag-5'>器</b>構建<b class='flag-5'>塊</b>數(shù)據(jù)Sheet

    Linux架構介紹 IO流程與IO調度詳解

    之前一直跟大家聊文件系統(tǒng),文件系統(tǒng)提供一文件到物理的映射轉換。這邏輯可能非常復雜,依賴于文件系統(tǒng)的實現(xiàn)。今天則跟大家聊聊
    的頭像 發(fā)表于 05-16 12:12 ?2297次閱讀

    Linux內核分配器

    為了解決小塊內存的分配問題,Linux 內核提供了分配器,最早實現(xiàn)的分配器是SLAB 分配器。
    的頭像 發(fā)表于 07-27 09:35 ?1573次閱讀

    SPI通用接口介紹

    SPI 通用接口 SPI 通用接口把具體的 SPI 設備的協(xié)議驅動和 SPI 控制驅動連接在一起。 負責 SPI 系統(tǒng)與
    的頭像 發(fā)表于 07-25 10:52 ?725次閱讀

    SPI控制驅動功能介紹

    和相應的設備進行正確的數(shù)據(jù)交換 向通用接口提供接口,使得上層的協(xié)議驅動可以通過通用接口訪問控制驅動 配合
    的頭像 發(fā)表于 07-25 10:58 ?1143次閱讀
    SPI控制<b class='flag-5'>器</b>驅動<b class='flag-5'>層</b>功能介紹

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

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

    SCP基本構建介紹

    整個LayOut分為了三 在這里插入圖片描述 ? 模塊: ? 架構不可知 ? 模塊執(zhí)行一組定義明確的操作。 ? 框架: ? 依賴于執(zhí)行環(huán)境相關服務的體系結構 ? 為所有模塊提供通用
    的頭像 發(fā)表于 11-02 16:52 ?846次閱讀
    SCP基本構建<b class='flag-5'>塊</b>介紹