共計 11638 個字符,預計需要花費 30 分鐘才能閱讀完成。
自動寫代碼機器人,免費開通
這篇文章將為大家詳細講解有關怎么提高 Linux 下塊設備 IO 的整體性能,丸趣 TV 小編覺得挺實用的,因此分享給大家做個參考,希望大家閱讀完這篇文章后可以有所收獲。
IO 調度發生在 Linux 內核的 IO 調度層。這個層次是針對 Linux 的整體 IO 層次體系來說的。從 read()或者 write()系統調用的角度來說,Linux 整體 IO 體系可以分為七層,它們分別是:
VFS 層:虛擬文件系統層。由于內核要跟多種文件系統打交道,而每一種文件系統所實現的數據結構和相關方法都可能不盡相同,所以,內核抽象了這一層,專門用來適配各種文件系統,并對外提供統一操作接口。
文件系統層:不同的文件系統實現自己的操作過程,提供自己特有的特征,具體不多說了,大家愿意的話自己去看代碼即可。
頁緩存層:負責真對 page 的緩存。
通用塊層:由于絕大多數情況的 io 操作是跟塊設備打交道,所以 Linux 在此提供了一個類似 vfs 層的塊設備操作抽象層。下層對接各種不同屬性的塊設備,對上提供統一的 Block IO 請求標準。
IO 調度層:因為絕大多數的塊設備都是類似磁盤這樣的設備,所以有必要根據這類設備的特點以及應用的不同特點來設置一些不同的調度算法和隊列。以便在不同的應用環境下有針對性的提高磁盤的讀寫效率,這里就是大名鼎鼎的 Linux 電梯所起作用的地方。針對機械硬盤的各種調度方法就是在這實現的。
塊設備驅動層:驅動層對外提供相對比較高級的設備操作接口,往往是 C 語言的,而下層對接設備本身的操作方法和規范。
塊設備層:這層就是具體的物理設備了,定義了各種真對設備操作方法和規范。
有一個已經整理好的[Linux IO 結構圖],非常經典,一圖勝千言:
我們今天要研究的內容主要在 IO 調度這一層。
它要解決的核心問題是,如何提高塊設備 IO 的整體性能? 這一層也主要是針對機械硬盤結構而設計的。
眾所周知,機械硬盤的存儲介質是磁盤,磁頭在盤片上移動進行磁道尋址,行為類似播放一張唱片。
這種結構的特點是,順序訪問時吞吐量較高,但是如果一旦對盤片有隨機訪問,那么大量的時間都會浪費在磁頭的移動上,這時候就會導致每次 IO 的響應時間變長,極大的降低 IO 的響應速度。
磁頭在盤片上尋道的操作,類似電梯調度,實際上在最開始的時期,Linux 把這個算法命名為 Linux 電梯算法,即:
如果在尋道的過程中,能把順序路過的相關磁道的數據請求都“順便”處理掉,那么就可以在比較小影響響應速度的前提下,提高整體 IO 的吞吐量。
這就是我們為什么要設計 IO 調度算法的原因。
目前在內核中默認開啟了三種算法 / 模式:noop,cfq 和 deadline。嚴格算應該是兩種:
因為 *** 種叫做 noop,就是空操作調度算法,也就是沒有任何調度操作,并不對 io 請求進行排序,僅僅做適當的 io 合并的一個 fifo 隊列。
目前內核中默認的調度算法應該是 cfq,叫做完全公平隊列調度。這個調度算法人如其名,它試圖給所有進程提供一個完全公平的 IO 操作環境。
請大家一定記住這個詞語,cfq,完全公平隊列調度,不然下文就沒法看了。
cfq 為每個進程創建一個同步 IO 調度隊列,并默認以時間片和請求數限定的方式分配 IO 資源,以此保證每個進程的 IO 資源占用是公平的,cfq 還實現了針對進程級別的優先級調度,這個我們后面會詳細解釋。
查看和修改 IO 調度算法的方法是:
cfq 是通用服務器比較好的 IO 調度算法選擇,對桌面用戶也是比較好的選擇。
但是對于很多 IO 壓力較大的場景就并不是很適應,尤其是 IO 壓力集中在某些進程上的場景。
因為這種場景我們需要更多的滿足某個或者某幾個進程的 IO 響應速度,而不是讓所有的進程公平的使用 IO,比如數據庫應用。
deadline 調度 (最終期限調度) 就是更適合上述場景的解決方案。deadline 實現了四個隊列:
其中兩個分別處理正常 read 和 write,按扇區號排序,進行正常 io 的合并處理以提高吞吐量。因為 IO 請求可能會集中在某些磁盤位置,這樣會導致新來的請求一直被合并,可能會有其他磁盤位置的 io 請求被餓死。
另外兩個處理超時 read 和 write 的隊列,按請求創建時間排序,如果有超時的請求出現,就放進這兩個隊列,調度算法保證超時 (達到最終期限時間) 的隊列中的請求會優先被處理,防止請求被餓死。
不久前,內核還是默認標配四種算法,還有一種叫做 as 的算法(Anticipatory scheduler),預測調度算法。一個高大上的名字,搞得我一度認為 Linux 內核都會算命了。
結果發現,無非是在基于 deadline 算法做 io 調度的之前等一小會時間,如果這段時間內有可以合并的 io 請求到來,就可以合并處理,提高 deadline 調度的在順序讀寫情況下的數據吞吐量。
其實這根本不是啥預測,我覺得不如叫撞大運調度算法,當然這種策略在某些特定場景差效果不錯。
但是在大多數場景下,這個調度不僅沒有提高吞吐量,還降低了響應速度,所以內核干脆把它從默認配置里刪除了。畢竟 Linux 的宗旨是實用,而我們也就不再這個調度算法上多費口舌了。
1、cfq:完全公平隊列調度
cfq 是內核默認選擇的 IO 調度隊列,它在桌面應用場景以及大多數常見應用場景下都是很好的選擇。
如何實現一個所謂的完全公平隊列(Completely Fair Queueing)?
首先我們要理解所謂的公平是對誰的公平? 從操作系統的角度來說,產生操作行為的主體都是進程,所以這里的公平是針對每個進程而言的,我們要試圖讓進程可以公平的占用 IO 資源。
那么如何讓進程公平的占用 IO 資源? 我們需要先理解什么是 IO 資源。當我們衡量一個 IO 資源的時候,一般喜歡用的是兩個單位,一個是數據讀寫的帶寬,另一個是數據讀寫的 IOPS。
帶寬就是以時間為單位的讀寫數據量,比如,100Mbyte/s。而 IOPS 是以時間為單位的讀寫次數。在不同的讀寫情境下,這兩個單位的表現可能不一樣,但是可以確定的是,兩個單位的任何一個達到了性能上限,都會成為 IO 的瓶頸。
從機械硬盤的結構考慮,如果讀寫是順序讀寫,那么 IO 的表現是可以通過比較少的 IOPS 達到較大的帶寬,因為可以合并很多 IO,也可以通過預讀等方式加速數據讀取效率。
當 IO 的表現是偏向于隨機讀寫的時候,那么 IOPS 就會變得更大,IO 的請求的合并可能性下降,當每次 io 請求數據越少的時候,帶寬表現就會越低。
從這里我們可以理解,針對進程的 IO 資源的主要表現形式有兩個:進程在單位時間內提交的 IO 請求個數和進程占用 IO 的帶寬。
其實無論哪個,都是跟進程分配的 IO 處理時間長度緊密相關的。
有時業務可以在較少 IOPS 的情況下占用較大帶寬,另外一些則可能在較大 IOPS 的情況下占用較少帶寬,所以對進程占用 IO 的時間進行調度才是相對最公平的。
即,我不管你是 IOPS 高還是帶寬占用高,到了時間咱就換下一個進程處理,你愛咋樣咋樣。
所以,cfq 就是試圖給所有進程分配等同的塊設備使用的時間片,進程在時間片內,可以將產生的 IO 請求提交給塊設備進行處理,時間片結束,進程的請求將排進它自己的隊列,等待下次調度的時候進行處理。這就是 cfq 的基本原理。
當然,現實生活中不可能有真正的“公平”,常見的應用場景下,我們很肯能需要人為的對進程的 IO 占用進行人為指定優先級,這就像對進程的 CPU 占用設置優先級的概念一樣。
所以,除了針對時間片進行公平隊列調度外,cfq 還提供了優先級支持。每個進程都可以設置一個 IO 優先級,cfq 會根據這個優先級的設置情況作為調度時的重要參考因素。
優先級首先分成三大類:RT、BE、IDLE,它們分別是實時 (Real Time)、*** 效果(Best Try) 和閑置 (Idle) 三個類別,對每個類別的 IO,cfq 都使用不同的策略進行處理。另外,RT 和 BE 類別中,分別又再劃分了 8 個子優先級實現更細節的 QOS 需求,而 IDLE 只有一個子優先級。
另外,我們都知道內核默認對存儲的讀寫都是經過緩存 (buffer/cache) 的,在這種情況下,cfq 是無法區分當前處理的請求是來自哪一個進程的。
只有在進程使用同步方式 (sync read 或者 sync wirte) 或者直接 IO(Direct IO)方式進行讀寫的時候,cfq 才能區分出 IO 請求來自哪個進程。
所以,除了針對每個進程實現的 IO 隊列以外,還實現了一個公共的隊列用來處理異步請求。
當前內核已經實現了針對 IO 資源的 cgroup 資源隔離,所以在以上體系的基礎上,cfq 也實現了針對 cgroup 的調度支持。關于 cgroup 的 blkio 功能的描述,請看我之前的文章 Cgroup ndash; Linux 的 IO 資源隔離。
總的來說,cfq 用了一系列的數據結構實現了以上所有復雜功能的支持,大家可以通過源代碼看到其相關實現,文件在源代碼目錄下的 block/cfq-iosched.c。
1.1 cfq 設計原理
在此,我們對整體數據結構做一個簡要描述:首先,cfq 通過一個叫做 cfq_data 的數據結構維護了整個調度器流程。在一個支持了 cgroup 功能的 cfq 中,全部進程被分成了若干個 contral group 進行管理。
每個 cgroup 在 cfq 中都有一個 cfq_group 的結構進行描述,所有的 cgroup 都被作為一個調度對象放進一個紅黑樹中,并以 vdisktime 為 key 進行排序。
vdisktime 這個時間紀錄的是當前 cgroup 所占用的 io 時間,每次對 cgroup 進行調度時,總是通過紅黑樹選擇當前 vdisktime 時間最少的 cgroup 進行處理,以保證所有 cgroups 之間的 IO 資源占用“公平”。
當然我們知道,cgroup 是可以對 blkio 進行資源比例分配的,其作用原理就是,分配比例大的 cgroup 占用 vdisktime 時間增長較慢,分配比例小的 vdisktime 時間增長較快,快慢與分配比例成正比。
這樣就做到了不同的 cgroup 分配的 IO 比例不一樣,并且在 cfq 的角度看來依然是“公平“的。
選擇好了需要處理的 cgroup(cfq_group)之后,調度器需要決策選擇下一步的 service_tree。
service_tree 這個數據結構對應的都是一系列的紅黑樹,主要目的是用來實現請求優先級分類的,就是 RT、BE、IDLE 的分類。每一個 cfq_group 都維護了 7 個 service_trees,其定義如下:
其中 service_tree_idle 就是用來給 IDLE 類型的請求進行排隊用的紅黑樹。
而上面二維數組,首先 *** 個維度針對 RT 和 BE 分別各實現了一個數組,每一個數組中都維護了三個紅黑樹,分別對應三種不同子類型的請求,分別是:SYNC、SYNC_NOIDLE 以及 ASYNC。
我們可以認為 SYNC 相當于 SYNC_IDLE 并與 SYNC_NOIDLE 對應。idling 是 cfq 在設計上為了盡量合并連續的 IO 請求以達到提高吞吐量的目的而加入的機制,我們可以理解為是一種“空轉”等待機制。
空轉是指,當一個隊列處理一個請求結束后,會在發生調度之前空等一小會時間,如果下一個請求到來,則可以減少磁頭尋址,繼續處理順序的 IO 請求。
為了實現這個功能,cfq 在 service_tree 這層數據結構這實現了 SYNC 隊列,如果請求是同步順序請求,就入隊這個 service tree,如果請求是同步隨機請求,則入隊 SYNC_NOIDLE 隊列,以判斷下一個請求是否是順序請求。
所有的異步寫操作請求將入隊 ASYNC 的 service tree,并且針對這個隊列沒有空轉等待機制。
此外,cfq 還對 SSD 這樣的硬盤有特殊調整,當 cfq 發現存儲設備是一個 ssd 硬盤這樣的隊列深度更大的設備時,所有針對單獨隊列的空轉都將不生效,所有的 IO 請求都將入隊 SYNC_NOIDLE 這個 service tree。
每一個 service tree 都對應了若干個 cfq_queue 隊列,每個 cfq_queue 隊列對應一個進程,這個我們后續再詳細說明。
cfq_group 還維護了一個在 cgroup 內部所有進程公用的異步 IO 請求隊列,其結構如下:
異步請求也分成了 RT、BE、IDLE 這三類進行處理,每一類對應一個 cfq_queue 進行排隊。
BE 和 RT 也實現了優先級的支持,每一個類型有 IOPRIO_BE_NR 這么多個優先級,這個值定義為 8,數組下標為 0 -7。
我們目前分析的內核代碼版本為 Linux 4.4,可以看出,從 cfq 的角度來說,已經可以實現異步 IO 的 cgroup 支持了,我們需要定義一下這里所謂異步 IO 的含義,它僅僅表示從內存的 buffer/cache 中的數據同步到硬盤的 IO 請求,而不是 aio(man 7 aio)或者 linux 的 native 異步 io 以及 libaio 機制,實際上這些所謂的“異步”IO 機制,在內核中都是同步實現的(本質上馮諾伊曼計算機沒有真正的“異步”機制)。
我們在上面已經說明過,由于進程正常情況下都是將數據先寫入 buffer/cache,所以這種異步 IO 都是統一由 cfq_group 中的 async 請求隊列處理的。
那么為什么在上面的 service_tree 中還要實現和一個 ASYNC 的類型呢?
這當然是為了支持區分進程的異步 IO 并使之可以“完全公平”做準備嘍。
實際上在 *** 的 cgroup v2 的 blkio 體系中,內核已經支持了針對 buffer IO 的 cgroup 限速支持,而以上這些可能容易混淆的一堆類型,都是在新的體系下需要用到的類型標記。
新體系的復雜度更高了,功能也更加強大,但是大家先不要著急,正式的 cgroup v2 體系,在 Linux 4.5 發布的時候會正式跟大家見面。
我們繼續選擇 service_tree 的過程,三種優先級類型的 service_tree 的選擇就是根據類型的優先級來做選擇的,RT 優先級 ***,BE 其次,IDLE***。就是說,RT 里有,就會一直處理 RT,RT 沒了再處理 BE。
每個 service_tree 對應一個元素為 cfq_queue 排隊的紅黑樹,而每個 cfq_queue 就是內核為進程 (線程) 創建的請求隊列。
每一個 cfq_queue 都會維護一個 rb_key 的變量,這個變量實際上就是這個隊列的 IO 服務時間(service time)。
這里還是通過紅黑樹找到 service time 時間最短的那個 cfq_queue 進行服務,以保證“完全公平”。
選擇好了 cfq_queue 之后,就要開始處理這個隊列里的 IO 請求了。這里的調度方式基本跟 deadline 類似。
cfq_queue 會對進入隊列的每一個請求進行兩次入隊,一個放進 fifo 中,另一個放進按訪問扇區順序作為 key 的紅黑樹中。
默認從紅黑樹中取請求進行處理,當請求的延時時間達到 deadline 時,就從紅黑樹中取等待時間最長的進行處理,以保證請求不被餓死。
這就是整個 cfq 的調度流程,當然其中還有很多細枝末節沒有交代,比如合并處理以及順序處理等等。
1.2 cfq 的參數調整
理解整個調度流程有助于我們決策如何調整 cfq 的相關參數。所有 cfq 的可調參數都可以在 /sys/class/block/sda/queue/iosched/ 目錄下找到,當然,在你的系統上,請將 sda 替換為相應的磁盤名稱。我們來看一下都有什么:
這些參數部分是跟機械硬盤磁頭尋道方式有關的,如果其說明你看不懂,請先補充相關知識:
back_seek_max: 磁頭可以向后尋址的 *** 范圍,默認值為 16M。
back_seek_penalty: 向后尋址的懲罰系數。這個值是跟向前尋址進行比較的。
以上兩個是為了防止磁頭尋道發生抖動而導致尋址過慢而設置的。基本思路是這樣,一個 io 請求到來的時候,cfq 會根據其尋址位置預估一下其磁頭尋道成本。
設置一個 *** 值 back_seek_max,對于請求所訪問的扇區號在磁頭后方的請求,只要尋址范圍沒有超過這個值,cfq 會像向前尋址的請求一樣處理它。
再設置一個評估成本的系數 back_seek_penalty,相對于磁頭向前尋址,向后尋址的距離為 1 /2(1/back_seek_penalty)時,cfq 認為這兩個請求尋址的代價是相同。
這兩個參數實際上是 cfq 判斷請求合并處理的條件限制,凡事復合這個條件的請求,都會盡量在本次請求處理的時候一起合并處理。
fifo_expire_async: 設置異步請求的超時時間。
同步請求和異步請求是區分不同隊列處理的,cfq 在調度的時候一般情況都會優先處理同步請求,之后再處理異步請求,除非異步請求符合上述合并處理的條件限制范圍內。
當本進程的隊列被調度時,cfq 會優先檢查是否有異步請求超時,就是超過 fifo_expire_async 參數的限制。如果有,則優先發送一個超時的請求,其余請求仍然按照優先級以及扇區編號大小來處理。
fifo_expire_sync: 這個參數跟上面的類似,區別是用來設置同步請求的超時時間。
slice_idle: 參數設置了一個等待時間。這讓 cfq 在切換 cfq_queue 或 service tree 的時候等待一段時間,目的是提高機械硬盤的吞吐量。
一般情況下,來自同一個 cfq_queue 或者 service tree 的 IO 請求的尋址局部性更好,所以這樣可以減少磁盤的尋址次數。這個值在機械硬盤上默認為非零。
當然在固態硬盤或者硬 RAID 設備上設置這個值為非零會降低存儲的效率,因為固態硬盤沒有磁頭尋址這個概念,所以在這樣的設備上應該設置為 0,關閉此功能。
group_idle: 這個參數也跟上一個參數類似,區別是當 cfq 要切換 cfq_group 的時候會等待一段時間。
在 cgroup 的場景下,如果我們沿用 slice_idle 的方式,那么空轉等待可能會在 cgroup 組內每個進程的 cfq_queue 切換時發生。
這樣會如果這個進程一直有請求要處理的話,那么直到這個 cgroup 的配額被耗盡,同組中的其它進程也可能無法被調度到。這樣會導致同組中的其它進程餓死而產生 IO 性能瓶頸。
在這種情況下,我們可以將 slice_idle = 0 而 group_idle = 8。這樣空轉等待就是以 cgroup 為單位進行的,而不是以 cfq_queue 的進程為單位進行,以防止上述問題產生。
low_latency: 這個是用來開啟或關閉 cfq 的低延時 (low latency) 模式的開關。
當這個開關打開時,cfq 將會根據 target_latency 的參數設置來對每一個進程的分片時間 (slice time) 進行重新計算。
這將有利于對吞吐量的公平(默認是對時間片分配的公平)。
關閉這個參數 (設置為 0) 將忽略 target_latency 的值。這將使系統中的進程完全按照時間片方式進行 IO 資源分配。這個開關默認是打開的。
我們已經知道 cfq 設計上有“空轉”(idling)這個概念,目的是為了可以讓連續的讀寫操作盡可能多的合并處理,減少磁頭的尋址操作以便增大吞吐量。
如果有進程總是很快的進行順序讀寫,那么它將因為 cfq 的空轉等待 *** 率很高而導致其它需要處理 IO 的進程響應速度下降,如果另一個需要調度的進程不會發出大量順序 IO 行為的話,系統中不同進程 IO 吞吐量的表現就會很不均衡。
就比如,系統內存的 cache 中有很多臟頁要寫回時,桌面又要打開一個瀏覽器進行操作,這時臟頁寫回的后臺行為就很可能會大量 *** 空轉時間,而導致瀏覽器的小量 IO 一直等待,讓用戶感覺瀏覽器運行響應速度變慢。
這個 low_latency 主要是對這種情況進行優化的選項,當其打開時,系統會根據 target_latency 的配置對因為 *** 空轉而大量占用 IO 吞吐量的進程進行限制,以達到不同進程 IO 占用的吞吐量的相對均衡。這個開關比較合適在類似桌面應用的場景下打開。
target_latency: 當 low_latency 的值為開啟狀態時,cfq 將根據這個值重新計算每個進程分配的 IO 時間片長度。
quantum: 這個參數用來設置每次從 cfq_queue 中處理多少個 IO 請求。在一個隊列處理事件周期中,超過這個數字的 IO 請求將不會被處理。這個參數只對同步的請求有效。
slice_sync: 當一個 cfq_queue 隊列被調度處理時,它可以被分配的處理總時間是通過這個值來作為一個計算參數指定的。公式為:time_slice = slice_sync + (slice_sync/5 * (4 – prio))。這個參數對同步請求有效。
slice_async: 這個值跟上一個類似,區別是對異步請求有效。
slice_async_rq: 這個參數用來限制在一個 slice 的時間范圍內,一個隊列最多可以處理的異步請求個數。請求被處理的 *** 個數還跟相關進程被設置的 io 優先級有關。
1.3 cfq 的 IOPS 模式
我們已經知道,默認情況下 cfq 是以時間片方式支持的帶優先級的調度來保證 IO 資源占用的公平。
高優先級的進程將得到更多的時間片長度,而低優先級的進程時間片相對較小。
當我們的存儲是一個高速并且支持 NCQ(原生指令隊列)的設備的時候,我們 *** 可以讓其可以從多個 cfq 隊列中處理多路的請求,以便提升 NCQ 的利用率。
此時使用時間片的分配方式分配資源就顯得不合時宜了,因為基于時間片的分配,同一時刻最多能處理的請求隊列只有一個。
這時,我們需要切換 cfq 的模式為 IOPS 模式。切換方式很簡單,就是將 slice_idle= 0 即可。內核會自動檢測你的存儲設備是否支持 NCQ,如果支持的話 cfq 會自動切換為 IOPS 模式。
另外,在默認的基于優先級的時間片方式下,我們可以使用 ionice 命令來調整進程的 IO 優先級。進程默認分配的 IO 優先級是根據進程的 nice 值計算而來的,計算方法可以在 man ionice 中看到,這里不再廢話。
2、deadline:最終期限調度
deadline 調度算法相對 cfq 要簡單很多。
其設計目標是:
在保證請求按照設備扇區的順序進行訪問的同時,兼顧其它請求不被餓死,要在一個最終期限前被調度到。
我們知道磁頭對磁盤的尋道是可以進行順序訪問和隨機訪問的,因為尋道延時時間的關系,順序訪問時 IO 的吞吐量更大,隨機訪問的吞吐量小。
如果我們想為一個機械硬盤進行吞吐量優化的話,那么就可以讓調度器按照盡量復合順序訪問的 IO 請求進行排序,之后請求以這樣的順序發送給硬盤,就可以使 IO 的吞吐量更大。
但是這樣做也有另一個問題,就是如果此時出現了一個請求,它要訪問的磁道離目前磁頭所在磁道很遠,應用的請求又大量集中在目前磁道附近。
導致大量請求一直會被合并和插隊處理,而那個要訪問比較遠磁道的請求將因為一直不能被調度而餓死。
deadline 就是這樣一種調度器,能在保證 IO*** 吞吐量的情況下,盡量使遠端請求在一個期限內被調度而不被餓死的調度器。
2.1 deadline 設計原理
為了實現上述目標,deadline 調度器實現了兩類隊列,一類負責對請求按照訪問扇區進行排序。這個隊列使用紅黑樹組織,叫做 sort_list。另一類對請求的訪問時間進行排序。使用鏈表組織,叫做 fifo_list。
由于讀寫請求的明顯處理差異,在每一類隊列中,又按請求的讀寫類型分別分了兩個隊列,就是說 deadline 調度器實際上有 4 個隊列:
按照扇區訪問順序排序的讀隊列;
按照扇區訪問順序排序的寫隊列;
按照請求時間排序的讀隊列;
按照請求時間排序的寫隊列。
deadline 之所以要對讀寫隊列進行分離,是因為要實現讀操作比寫操作更高的優先級。
從應用的角度來看,讀操作一般都是同步行為,就是說,讀的時候程序一般都要等到數據返回后才能做下一步的處理。
而寫操作的同步需求并不明顯,一般程序都可以將數據寫到緩存,之后由內核負責同步到存儲上即可。
所以,對讀操作進行優化可以明顯的得到收益。當然,deadline 在這樣的情況下必然要對寫操作會餓死的情況進行考慮,保證其不會被餓死。
deadline 的入隊很簡單:當一個新的 IO 請求產生并進行了必要的合并操作之后,它在 deadline 調度器中會分別按照扇區順序和請求產生時間分別入隊 sort_list 和 fifo_list。并再進一步根據請求的讀寫類型入隊到相應的讀或者寫隊列。
deadline 的出隊處理相對麻煩一點:
首先判斷讀隊列是否為空,如果讀隊列不為空并且寫隊列沒發生饑餓 (starved writes_starved) 則處理讀隊列,否則處理寫隊列(第 4 部)。
進入讀隊列處理后,首先檢查 fifo_list 中是否有超過最終期限 (read_expire) 的讀請求,如果有則處理該請求以防止被餓死。
如果上一步為假,則處理順序的讀請求以增大吞吐。
如果第 1 部檢查讀隊列為空或者寫隊列處于饑餓狀態,那么應該處理寫隊列。其過程和讀隊列處理類似。
進入寫隊列處理后,首先檢查 fifo_list 中是否有超過最終期限 (write_expire) 的寫請求,如果有則處理該請求以防止被餓死。
如果上一步為假,則處理順序的寫請求以增大吞吐。
整個處理邏輯就是這樣,簡單總結其原則就是,讀的優先級高于寫,達到 deadline 時間的請求處理高于順序處理。正常情況下保證順序讀寫,保證吞吐量,有饑餓的情況下處理饑餓。
2.2 deadline 的參數調整
deadline 的可調參數相對較少,包括:
read_expire: 讀請求的超時時間設置,單位為 ms。當一個讀請求入隊 deadline 的時候,其過期時間將被設置為當前時間 +read_expire,并放倒 fifo_list 中進行排序。
write_expire: 寫請求的超時時間設置,單位為 ms。功能根讀請求類似。
fifo_batch: 在順序 (sort_list) 請求進行處理的時候,deadline 將以 batch 為單位進行處理。
每一個 batch 處理的請求個數為這個參數所限制的個數。在一個 batch 處理的過程中,不會產生是否超時的檢查,也就不會產生額外的磁盤尋道時間。
這個參數可以用來平衡順序處理和饑餓時間的矛盾,當饑餓時間需要盡可能的符合預期的時候,我們可以調小這個值,以便盡可能多的檢查是否有饑餓產生并及時處理。
增大這個值當然也會增大吞吐量,但是會導致處理饑餓請求的延時變長。
writes_starved: 這個值是在上述 deadline 出隊處理 *** 步時做檢查用的。用來判斷當讀隊列不為空時,寫隊列的饑餓程度是否足夠高,以時 deadline 放棄讀請求的處理而處理寫請求。
當檢查存在有寫請求的時候,deadline 并不會立即對寫請求進行處理,而是給相關數據結構中的 starved 進行累計。
如果這是 *** 次檢查到有寫請求進行處理,那么這個計數就為 1。如果此時 writes_starved 值為 2,則我們認為此時饑餓程度還不足夠高,所以繼續處理讀請求。
只有當 starved = writes_starved 的時候,deadline 才回去處理寫請求??梢哉J為這個值是用來平衡 deadline 對讀寫請求處理優先級狀態的,這個值越大,則寫請求越被滯后處理,越小,寫請求就越可以獲得趨近于讀請求的優先級。
front_merges: 當一個新請求進入隊列的時候,如果其請求的扇區距離當前扇區很近,那么它就是可以被合并處理的。
而這個合并可能有兩種情況:
是向當前位置后合并
是向前合并。
在某些場景下,向前合并是不必要的,那么我們就可以通過這個參數關閉向前合并。默認 deadline 支持向前合并,設置為 0 關閉。
3、noop 調度器
noop 調度器是最簡單的調度器。它本質上就是一個鏈表實現的 fifo 隊列,并對請求進行簡單的合并處理。調度器本身并沒有提供任何可疑配置的參數。
4、各種調度器的應用場景選擇
根據以上幾種 io 調度算法的分析,我們應該能對各種調度算法的使用場景有一些大致的思路了。
從原理上看,cfq 是一種比較通用的調度算法,它是一種以進程為出發點考慮的調度算法,保證大家盡量公平。
deadline 是一種以提高機械硬盤吞吐量為思考出發點的調度算法,盡量保證在有 io 請求達到最終期限的時候進行調度。非常適合業務比較單一并且 IO 壓力比較重的業務,比如數據庫。
而 noop 呢? 其實如果我們把我們的思考對象拓展到固態硬盤,那么你就會發現,無論 cfq 還是 deadline,都是針對機械硬盤的結構進行的隊列算法調整,而這種調整對于固態硬盤來說,完全沒有意義。
對于固態硬盤來說,IO 調度算法越復雜, 額外要處理的邏輯就越多,效率就越低。
所以,固態硬盤這種場景下使用 noop 是 *** 的,deadline 次之,而 cfq 由于復雜度的原因,無疑效率 ***。
關于“怎么提高 Linux 下塊設備 IO 的整體性能”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,使各位可以學到更多知識,如果覺得文章不錯,請把它分享出去讓更多的人看到。
向 AI 問一下細節
丸趣 TV 網 – 提供最優質的資源集合!