從調(diào)度到緩存:解析 Linux 磁盤 I/O 性能優(yōu)化的核心設(shè)計
0.引言
在前一篇文章中我們講解了文件I/O與流的概念,幫助大家理解了上層的抽象設(shè)計。本文將深入更為底層的實現(xiàn)機制,探討Linux如何高效訪問磁盤的兩大關(guān)鍵技術(shù):磁盤調(diào)度算法和Page Cache。我們將分別從兩個維度進行分析:一是I/O調(diào)度策略的優(yōu)化,二是Page Cache的如何減少I/O操作。
1.磁盤I/O調(diào)度:減少尋道時間和延遲
Linux磁盤的I/O調(diào)度是系統(tǒng)性能調(diào)優(yōu)的一個重要組成部分,其核心目標在于根據(jù)存儲設(shè)備的物理特性,合理的去規(guī)劃I/O請求的順序,減少無效操作導(dǎo)致性能問題。因為調(diào)度的選擇涉及設(shè)備的物理特性,所以我們來看常見的兩種物理存儲設(shè)備,然后再來去介紹不同調(diào)度算法:
1)機械硬盤(HDD):機械硬盤是傳統(tǒng)的硬盤,其依賴于磁頭移動和盤片旋轉(zhuǎn)來實現(xiàn)讀寫,其尋道時間(磁頭移動)和旋轉(zhuǎn)延遲(盤片旋轉(zhuǎn))是其主要耗時,所以隨機I/O性能遠低于順序I/O,這就要求調(diào)度算法要通過合并、排序等方式來減少磁頭的移動。
2)固態(tài)硬盤(SSD):固態(tài)硬盤是由控制單元和存儲單元組成,沒有機械部件,所以尋道時間幾乎可以忽略不計,也就是說它有著很好的隨機I/O性能,但其存在擦寫次數(shù)限制,和寫入放大。所以SSD的調(diào)度需要考慮合并小寫入,減少CPU計算調(diào)度。
1.1 調(diào)度算法介紹(基于Linux 5.10)
調(diào)度算法可以分為單隊列(全局單一隊列)和多隊列(多CPU/硬件隊列獨立),因為現(xiàn)代主要使用多隊列模式,所以我們主要介紹多隊列的調(diào)度算法,先來看整體的調(diào)度結(jié)構(gòu):

整體多隊列調(diào)度的初始化函數(shù)如下,后面到具體算法因其實現(xiàn)涉及代碼比較多,我們會主要描述思路。
void elevator_init_mq(struct request_queue *q)
{
struct elevator_type *e; // 指向選中的I/O調(diào)度器類型結(jié)構(gòu)體
int err; // 函數(shù)返回值,用于檢查初始化是否成功
// 檢查當前隊列是否支持I/O調(diào)度器(如部分設(shè)備可能強制使用noop調(diào)度器)
// 若不支持,則直接返回,不進行調(diào)度器初始化
if (!elv_support_iosched(q))
return;
// 警告:若隊列已注冊(已完成初始化),則觸發(fā)BUG_ON警告(僅調(diào)試用)
// 確保調(diào)度器初始化僅在隊列未注冊時執(zhí)行
WARN_ON_ONCE(blk_queue_registered(q));
// 若隊列已關(guān)聯(lián)調(diào)度器(非空),則無需重復(fù)初始化,直接返回
if (unlikely(q->elevator))
return;
// 根據(jù)隊列需求選擇合適的I/O調(diào)度器
if (!q->required_elevator_features) {
// 若隊列無特殊功能需求,選擇默認調(diào)度器(由內(nèi)核配置或設(shè)備類型決定)
e = elevator_get_default(q);
} else {
// 若隊列有特殊功能需求(如支持層級調(diào)度、延遲控制等),
// 則根據(jù)需求匹配具備對應(yīng)功能的調(diào)度器
e = elevator_get_by_features(q);
}
// 若未找到合適的調(diào)度器(e為NULL),則退出初始化
if (!e)
return;
// 凍結(jié)隊列:阻止新的I/O請求進入隊列,確保初始化期間隊列狀態(tài)穩(wěn)定
blk_mq_freeze_queue(q);
// 暫停隊列:等待隊列中已有請求處理完成,避免初始化干擾正在進行的I/O
blk_mq_quiesce_queue(q);
// 初始化調(diào)度器:將選中的調(diào)度器(e)與隊列(q)綁定,創(chuàng)建調(diào)度器上下文
// 該函數(shù)會為多隊列的每個軟件隊列初始化調(diào)度器實例(如MQ-Deadline的每個隊列私有數(shù)據(jù))
err = blk_mq_init_sched(q, e);
// 恢復(fù)隊列運行:允許隊列重新接收并處理I/O請求
blk_mq_unquiesce_queue(q);
// 解凍隊列:完全恢復(fù)隊列的正常操作
blk_mq_unfreeze_queue(q);
// 檢查調(diào)度器初始化是否失敗
if (err) {
// 打印警告信息,提示當前調(diào)度器初始化失敗,將回退到"none"調(diào)度器(noop)
pr_warn("\"%s\" elevator initialization failed, "
"falling back to \"none\"\n", e->elevator_name);
// 釋放之前獲取的調(diào)度器引用,避免資源泄漏
elevator_put(e);
}
}1.1.1 BFQ調(diào)度
BFQ(Budget Fair Queueing)其含義為公平對待每個進程,其主要邏輯可以見下圖,其中實線代表IO請求的方向,通過add方法添加到IO隊列,然后使用調(diào)度器調(diào)度,由dispatch方法進行下發(fā)處理。
虛線箭頭budget表示每個進程被分配的訪問的最大扇區(qū)數(shù)目,其每訪問一個扇區(qū)就會進行減少,一旦消耗完就會選擇其他進程執(zhí)行IO,當前用完的進程會被重新估算下一次的budget數(shù)量。
然后再來看Next active application selection,這是所有IO調(diào)度器的核心功能,本質(zhì)就是選擇出一個下一個有訪問磁盤權(quán)力的隊列,BFQ是從符合條件的隊列中進行選擇(如budget沒用完的,等待時間較長的)。
圖片
1.1.2 mq-deadLine調(diào)度
其整體邏輯如下:通過兩個結(jié)構(gòu)進行管理,一個用來記錄磁盤位置排序(為了方便連續(xù)讀取),一個按照時間排序(為了deadline優(yōu)先),其為每個CPU配置一個隊列,減少鎖競爭,同時采用上述的雙重排序策略來提高性能。
圖片
1.1.3 kyber調(diào)度
其整體邏輯如下:在初始化階段時創(chuàng)建四類請求隊列(讀、寫、discard、other),初始化 Token 池(控制每種請求的最大并發(fā)數(shù)),并設(shè)置延遲目標(讀請求優(yōu)先低延遲,寫請求平衡吞吐);應(yīng)用請求先進入暫存隊列,完成請求合并(減少 I/O 次數(shù))和類型分類,再分別進入對應(yīng)類型的分發(fā)隊列;核心調(diào)度邏輯:
1)調(diào)度器按固定順序輪詢四類隊列,避免某類請求長期被忽略;
2)通過 Token 機制 控制并發(fā):每種請求需消耗 Token 才能被處理,Token 耗盡則隊列掛起,直到請求完成釋放 Token;
3)優(yōu)先保障有 Token 的隊列,避免無限制并發(fā)導(dǎo)致設(shè)備擁堵;
4)通過定期統(tǒng)計實際延遲來動態(tài)適應(yīng)設(shè)備負載。
圖片
1.1.4 總結(jié)比較
我們從調(diào)度器的優(yōu)劣以及適用場景進行比較,同時會描述我們修改調(diào)度器的方式。
調(diào)度器 | 優(yōu)勢 | 劣勢 | 適合場景 |
Kyber | 讀寫分離,動態(tài)調(diào)整 | 機械硬盤適應(yīng)較差,隨機碎片請求多 | 現(xiàn)代存儲SSD和單一場景 |
BFQ | 高交互性,公平性好 | 吞吐量略低 | 多任務(wù)環(huán)境、桌面系統(tǒng) |
mq-deadline | 并行性好,適度排序,適配多種存儲 | 公平性較弱 | 多對列SSD設(shè)備以及高負載場景 |
切換調(diào)度器方式如下,中間路徑需要根據(jù)實際存儲類型變化:
sudo echo kyber > /sys/block/hda/queue/scheduler2.Page Cache
Page Cache是Linux用于緩存數(shù)據(jù)的核心機制,通過將磁盤數(shù)據(jù)暫存到內(nèi)存中,減少磁盤IO次數(shù),我們將從Page Cache的查看、緩存管理、讀寫交互以及頁面回收四個部分進行介紹。
2.1 如何查看Page Cache
可以使用vmstat -n 1查看讀寫,其中主要信息就是cache字段,另外更為詳細的信息可以使用cat /proc/meminfo查看,其部分內(nèi)容如圖:
圖片
2.2 緩存管理
緩存管理主要的三個結(jié)構(gòu)就是inode、page和address_space,其代表的含義和核心關(guān)聯(lián)如下:
1)inode表示文件元數(shù)據(jù),并通過i_mapping關(guān)聯(lián)對應(yīng)的address_space。
2)address_space是連接元數(shù)據(jù)inode和物理頁page的核心,每個address_space對應(yīng)一個打開的文件,根據(jù)index來找到對應(yīng)頁,并通過統(tǒng)一抽象的operations來適配不同文件系統(tǒng)。
3)page是物理頁結(jié)構(gòu),描述實際數(shù)據(jù)信息。
圖片
2.3 緩存交互
緩存交互可以分為讀寫操作,先來看讀:
1)讀操作,先檢查緩存再實際讀取,其流程圖和交互圖示如下:
圖片
圖片
2)寫操作,先寫緩存然后標記為臟頁,異步回寫,主要流程如下:
圖片
2.4 緩存淘汰
緩存淘汰邏輯較為簡單,使用的是LRU算法進行Page Cahce中頁的淘汰。
3.總結(jié)
本文從兩個角度來描述了IO高效的實現(xiàn)方式,一個是合理調(diào)度磁盤,一個是減少磁盤訪問。了解了實現(xiàn)磁盤高效IO的思想,下一篇將會講解特殊的優(yōu)化,零拷貝技術(shù)。



























