技術乾貨 | 漫遊Linux塊IO
前言
在計算機的世界裡,我們可以將業務進行抽象簡化為兩種場景—— 計算密集型和 IO密集型。這兩種場景下的表現,決定這一個計算機系統的能力。資料庫作為一個典型的基礎軟體,它的所有業務邏輯同樣可以抽象為這兩種場景的混合。因此,一個資料庫系統效能的強悍與否,往往跟作業系統和硬體提供的計算能力、IO能力緊密相關。
除了硬體本身的物理極限,作業系統在軟體層面的處理以及提供的相關機制也尤為重要。因此,想要資料庫發揮更加極限的效能,對作業系統內部相關機制和流程的理解就很重要。
本篇文章,我們就一起看下 Linux中一個IO請求的生命週期。Linux發展到今天,其內部的IO子系統已經相當複雜。每個點展開都能自成一篇,所以本次僅是對塊裝置的寫IO做一個快速的漫遊,後續再對相關專題進行詳細分解。
從使用者態程式出發
首先需要明確的是,什麼是塊裝置?我們知道IO裝置可以分為字元裝置和塊裝置,字元裝置以位元組流的方式訪問資料,比如我們的鍵盤滑鼠。而塊裝置則是以塊為單位訪問資料,並且支援隨機訪問,典型的塊裝置就是我們常見的機械硬碟和固態硬碟。
一個應用程式想將資料寫入磁碟,需要透過系統呼叫來完成:open開啟檔案 ---> write寫入檔案 ---> close關閉檔案。
下面是write系統呼叫的定義,我們可以看到,應用程式只需要指定三個引數:
1. 想要寫入的檔案
2. 寫入資料所在的記憶體地址
3. 寫入資料的長度
SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf, size_t, count) { struct fd f = fdget_pos(fd); ssize_t ret = -EBADF; if (f.file) { loff_t pos = file_pos_read(f.file); ret = vfs_write(f.file, buf, count, &pos); if (ret >= 0) file_pos_write(f.file, pos); fdput_pos(f); } return ret; }
而剩下的工作就進入到核心中的虛擬檔案系統(VFS)中進行處理。
虛擬檔案系統(VFS)
在Linux中 一切皆檔案,它提供了虛擬檔案系統VFS的機制,用來抽象各種資源,使應用程式無需關心底層細節,只需透過open、read/write、close這幾個通用介面便可以管理各種不同的資源。不同的檔案系統透過實現各自的通用介面來滿足不同的功能。
devtmpfs
掛載在/dev目錄,devtmpfs中的檔案代表各種裝置。因此,對devtmpfs檔案的讀寫操作,就是直接對相應裝置的操作。
如果應用程式開啟的是一個塊裝置檔案,則說明它直接對一個塊裝置進行讀寫,呼叫塊裝置的write函式:
const struct file_operations def_blk_fops = { .open = blkdev_open, ... ... .read = do_sync_read, .write = do_sync_write, ... ... };
磁碟檔案系統(ext4等)
這是我們最為熟悉的檔案系統型別,它的檔案就是我們一般理解的檔案,對應實際磁碟中按照特定格式組織並管理的區域。對這類檔案的讀寫操作,都會按照固定規則轉化為對應磁碟的讀寫操作。
應用程式如果開啟的是一個ext4檔案系統的檔案,則會呼叫ext4的write函式:
const struct file_operations_extend ext4_file_operations = { .kabi_fops = { ... ... .read = do_sync_read, .write = do_sync_write, ... ... .open = ext4_file_open, ... ... };
buffer/cache
Linux提供了快取來提高IO的效能,無論開啟的是裝置檔案還是磁碟檔案,一般情況IO會先寫入到系統快取中並直接返回,IO生命週期結束。後續系統重新整理快取或者主動呼叫sync,資料才會被真正寫入到塊裝置中。有意思的是,針對塊裝置的稱為buffer,針對磁碟檔案的稱為cache。
ssize_t __generic_file_aio_write(struct kiocb *iocb, const struct iovec *iov, unsigned long nr_segs, loff_t *ppos) ... ... if (io_is_direct(file)) { ... ... written = generic_file_direct_write(iocb, iov, &nr_segs, pos, ppos, count, ocount); ... ... } else { written = generic_file_buffered_write(iocb, iov, nr_segs, pos, ppos, count, written); } ... ...
Direct IO
當開啟檔案時候指定了O_DIRECT標誌,則指定檔案的IO為direct IO,它會繞過系統快取直接傳送給塊裝置。在傳送給塊裝置之前,虛擬檔案系統會將write函式參數列示的IO轉化為dio,在其中封裝了一個個bio結構,接著呼叫submit_bio將這些bio提交到通用塊層進行處理。
do_blockdev_direct_IO -> dio_bio_submit -> submit_bio
通用塊層
核心結構
1. bio/request
-
bio是Linux通用塊層和底層驅動的IO基本單位,可以看到它的最重要的幾個屬性,一個bio就可以表示一個完整的IO操作:
-
struct bio { sector_t bi_sector; //io的起始扇區 ... ... struct block_device *bi_bdev; //對應的塊裝置 ... ... bio_end_io_t *bi_end_io; //io結束的回撥函式 ... ... struct bio_vec *bi_io_vec; //記憶體page列表 ... ... };
-
request代表一個獨立的IO請求,是通用塊層和驅動層進行IO傳遞的結構,它容納了一組連續的bio。通用塊層提供了很多IO排程策略,將多個bio合併生成一個request,以提高IO的效率。
2. gendisk
每個塊裝置都對應一個gendisk結構,它定義了裝置名、主次裝置號、請求佇列,和裝置的相關操作函式。透過add_disk,我們就真正在系統中定義一個塊裝置。
3. request_queue
這個即是日常所說的IO請求佇列,通用塊層將IO轉化為request並插入到request_queue中,隨後底層驅動從中取出完成後續IO處理。
struct request_queue { ... ... struct elevator_queue *elevator; //排程器 request_fn_proc *request_fn; //請求處理函式 make_request_fn *make_request_fn; //請求入隊函式 ... ... softirq_done_fn *softirq_done_fn; //軟中斷處理 struct device *dev; unsigned long nr_requests; ... ... };
處理流程
在收到上層檔案系統提交的bio後,通用塊層最主要的功能就是根據bio建立request,並插入到request_queue中。
在這個過程中會對bio進行一系列處理:當bio長度超過限制會被分割,當bio訪問地址相鄰則會被合併。
request建立後,根據request_queue配置的不同elevator排程器,request插入到對應排程器佇列中。在底層裝置驅動程式從request_queue取出request處理時,不同elevator排程器返回request策略不同,從而實現對request的排程。
void blk_queue_bio(struct request_queue *q, struct bio *bio) { ... ... el_ret = elv_merge(q, &req, bio); //嘗試將bio合併到已有的request中 ... ... req = get_request(q, rw_flags, bio, 0); //無法合併,申請新的request ... ... init_request_from_bio(req, bio); } void blk_flush_plug_list(struct blk_plug *plug, bool from_schedule) { ... ... __elv_add_request(q, rq, ELEVATOR_INSERT_SORT_MERGE); //將request插入request_queue的elevator排程器 ... ... }
請求佇列
Linux中提供了不同型別的request_queue,一個是本文主要涉及的single-queue,另外一個是multi-queue。single-queue是在早期的硬體裝置(例如機械硬碟)只能序列處理IO的背景下建立的,而隨著更快速的SSD裝置的普及,single-queue已經無法發揮底層儲存的效能了,進而誕生了multi-queue,它最佳化了很多機制,使IOPS達到了百萬級別以上。至於multi-queue和single-queue的詳細區別,本篇不做討論。
每個佇列都可以配置不同的排程器,常見的有noop、deadline、cfq等。不同的排程器會根據IO型別、程式優先順序、deadline等因素,對request請求進一步進行合併和排序。我們可以透過sysfs進行配置,來滿足業務場景的需求:
#/sys/block/sdx/queue scheduler #排程器配置 nr_requests #佇列深度 max_sectors_kb #最大IO大小
裝置驅動
在IO經過通用塊層的處理和排程後,就進入到了裝置驅動層,就開始需要和儲存硬體進行互動。
以scsi驅動為例:在scsi的request處理函式scsi_request_fn中,迴圈從request_queue中取request,並建立scsi_cmd下發給註冊到scsi子系統的裝置驅動。需要注意的是,scsi_cmd中會註冊一個scsi_done的回撥函式。
static void scsi_request_fn(struct request_queue *q) { for (;;) { ... ... req = blk_peek_request(q); //從request_queue中取出request ... ... cmd->scsi_done = scsi_done; //指定cmd完成後回撥 rtn = scsi_dispatch_cmd(cmd); //下發將request對應的scsi_cmd ... ... } } int scsi_dispatch_cmd(struct scsi_cmnd *cmd) { ... ... rtn = host->hostt->queuecommand(host, cmd); ... ... }
IO完成
軟中斷
每個request_queue都會註冊軟中斷號,用來進行IO完成後的下半部處理,scsi驅動中註冊的為:scsi_softirq_done
struct request_queue *scsi_alloc_queue(struct scsi_device *sdev) { ... ... q = __scsi_alloc_queue(sdev->host, scsi_request_fn); ... ... blk_queue_softirq_done(q, scsi_softirq_done); ... ... }
硬中斷
當儲存裝置完成IO後,會透過硬體中斷通知裝置驅動,此時裝置驅動程式會呼叫scsi_done回撥函式完成scsi_cmd,並最終觸發BLOCK_SOFTIRQ軟中斷。
void __blk_complete_request(struct request *req) { ... ... raise_softirq_irqoff(BLOCK_SOFTIRQ); ... ... }
而BLOCK_SOFTIRQ軟中斷的處理函式就是之前註冊的scsi_softirq_done,透過自下而上層層回撥,到達bio_end_io,完成整個IO的生命週期。
-> scsi_finish_command -> scsi_io_completion -> scsi_end_request -> blk_update_request -> req_bio_endio -> bio_endio
總結
以上,我們很粗略地漫遊了Linux中一個塊裝置IO的生命週期,這是一個很複雜的過程,其中很多機制和細節只是點到為止,但是我們有了對整個IO路徑的整體的認識。當我們再遇到IO相關問題的時候,可以更加快速地找到關鍵部分,並深入研究解決。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/28218939/viewspace-2916509/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 技術乾貨 | WebRTC 技術解析之 Android VDMWebAndroid
- [乾貨分享]1000篇乾貨好文!量子技術——進階篇
- [乾貨分享]1000篇乾貨好文!量子技術——資訊篇
- 阿里技術精華乾貨整理阿里
- 【技術乾貨】原來ARM+Linux音訊方案如此簡單!Linux音訊
- 技術乾貨| MongoDB時間序列集合MongoDB
- 2020文章合集 技術乾貨
- [乾貨分享]1000篇乾貨好文!量子技術——專家觀點篇
- 技術乾貨:Applewatch APP設計規範APP
- 技術乾貨:RabbitMQ面試題及答案MQ面試題
- 技術乾貨:ActiveMQ面試題及答案MQ面試題
- 技術乾貨:Hadoop面試題及答案Hadoop面試題
- 技術乾貨 | WebRTC ADM 原始碼流程分析Web原始碼
- 乾貨!天翼雲DPU技術解碼
- 【乾貨】區塊鏈技術生態的設計|《白話區塊鏈》作者蔣勇分享實錄區塊鏈
- 3D人臉技術漫遊指南3D
- Spring Boot乾貨系列總綱 | 掘金技術徵文Spring Boot
- 技術乾貨:spring boot面試題及答案Spring Boot面試題
- 技術乾貨:LeetCode1- 20題詳解LeetCode
- 「技術乾貨」Pontus-用友雲限流服務
- 圖解Linux的IO模型和相關技術圖解Linux模型
- CSDN社群乾貨技術分享:探尋技術進階之道(Python和AI)PythonAI
- 【技術乾貨】Oracle資料庫漏洞掃描指南Oracle資料庫
- 技術乾貨:Kotlin面試題彙總及答案Kotlin面試題
- 乾貨 | 知識圖譜的技術與應用
- 技術乾貨:Tomcat面試題彙總及答案Tomcat面試題
- 技術乾貨:Kubernetes面試題彙總及答案面試題
- 技術乾貨|如何實現分鐘級故障管理
- 深度乾貨 | OceanBase 主動切主技術解讀
- 技術乾貨| 如何在MongoDB中輕鬆使用GridFS?MongoDB
- 技術乾貨 | 利用systemd管理MySQL單機多例項MySql
- 技術乾貨 | 基於MindSpore更好的理解Focal Loss
- 5⃣️ Java IO 技術Java
- 區塊鏈技術指北社群啟用新域名 chainon.io區塊鏈AI
- 最新《區塊鏈+加密貨幣技術全套教程》區塊鏈加密
- 漫談 SLAM 技術(上)SLAM
- 乾貨 | 一份我的前端技術進階指南前端
- 乾貨 | 大型網站專案架構技術一覽網站架構