日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

一招修复内存不能read_read文件一个字节实际会发生多大的磁盘IO?

發(fā)布時間:2025/3/20 编程问答 21 豆豆
生活随笔 收集整理的這篇文章主要介紹了 一招修复内存不能read_read文件一个字节实际会发生多大的磁盘IO? 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

先講一個作者大約5-6年前我在某當時很火的一個應(yīng)用分發(fā)創(chuàng)業(yè)公司的面試小插曲,該公司安排了一個剛工作1年多的一個同學來面我,聊到我們項目中的配置文件里寫的一個開關(guān),這位同學就跳出來說,你這個讀文件啦,每個用戶請求來了還得多一次的磁盤IO,性能肯定差。借由這個故事其實我發(fā)現(xiàn)了一個問題,雖然我們中的大部分人都是計算機科班出身,代碼也寫的很遛。但是在一些看似司空見慣的問題上,我們中的絕大多數(shù)人并沒有真正理解,或者理解的不夠透徹。

不管你用的是啥語言,C/PHP/GO、還是Java,相信大家都有過讀取文件的經(jīng)歷。我們來思考兩個問題,如果我們讀取文件中的一個字節(jié):

  • 是否會發(fā)生磁盤IO?

  • 發(fā)生的話,Linux實際向磁盤讀取多少字節(jié)了呢?

為了便于理解問題,我們把c的代碼也列出來:

int main() ?
{ ?
? ?char ? ?c; ?
? ?int ? ? in;
? ?in = open("in.txt", O_RDONLY);
? ?read(in,&c,1);
? ?return 0; ?
}

如果不是從事c/c++開發(fā)工作的同學,這個問題想深度理解起來確實不那么容易。因為目前常用的主流語言,PHP/Java/Go啥的封裝層次都比較高,把內(nèi)核的很多細節(jié)都給屏蔽的比較徹底。要想把上面的兩個問題搞的比較清楚,需要剖開Linux的內(nèi)部來理解Linux的IO棧。

1Linux IO棧簡介

廢話不多說,我們直接把Linux IO棧的一個簡化版本畫出來:(官方的IO棧參考這個:http://www.ilinuxkernel.com/files/Linux.IO.stack_v1.0.pdf)

圖1 Linux硬盤IO棧

我們在前面也分享了幾篇文章討論了上圖圖中的硬件層,還有文件系統(tǒng)模塊。但通過這個IO棧我們發(fā)現(xiàn),我們對Linux文件的IO的理解還是遠遠不夠,還有好幾個內(nèi)核組件:IO引擎、VFS、PageCache、通用塊管理層、IO調(diào)度層等模塊我們并沒有了解太多。別著急,讓我們一一道來:

1. IO引擎

我們開發(fā)同學想要讀寫文件的話,在lib庫層有很多種函數(shù)可以選擇,比如read,write,mmap等。這事實上就是在選擇Linux提供的IO引擎。我們最常用的read、write函數(shù)是屬于sync引擎,除了sync,還有map、psync、vsync、libaio、posixaio等。sync,psync都屬于同步方式,libaio和posixaio屬于異步IO。

當然了IO引擎也需要VFS、通用塊層等更底層的支持才能實現(xiàn)。在sync引擎的read函數(shù)里會進入VFS提供的read系統(tǒng)調(diào)用。

2. VFS虛擬文件系統(tǒng)

在內(nèi)核層,第一個看到的是VFS。VFS誕生的思想是抽象一個通用的文件系統(tǒng)模型,對我們開發(fā)人員或者是用戶提供一組通用的接口,讓我們不用care具體文件系統(tǒng)的實現(xiàn)。VFS提供的核心數(shù)據(jù)結(jié)構(gòu)有四個,它們定義在內(nèi)核源代碼的include/linux/fs.h和include/linux/dcache.h中。

  • superblock:Linux用來標注具體已安裝的文件系統(tǒng)的有關(guān)信息

  • inode:Linux中的每一個文件都有一個inode,你可以把inode理解為文件的身份證

  • file:內(nèi)存中的文件對象,用來保存進程和磁盤文件的對應(yīng)關(guān)系

  • desty:目錄項,是路徑中的一部分,所有的目錄項對象串起來就是一棵Linux下的目錄樹。

圍繞這這四個核心數(shù)據(jù)結(jié)構(gòu),VFS也都定義了一系列的操作方法。比如,inode的操作方法定義inode_operations(include/linux/fs.h),在它的里面定義了我們非常熟悉的mkdir和rename等。

struct inode_operations {
? ? ? ? ? ? ? ?......
? ? ? ?int (*link) (struct dentry *,struct inode *,struct dentry *);
? ? ? ?int (*unlink) (struct inode *,struct dentry *);
? ? ? ?int (*mkdir) (struct inode *,struct dentry *,umode_t);
? ? ? ?int (*rmdir) (struct inode *,struct dentry *);
? ? ? ?int (*rename) (struct inode *, struct dentry *,
? ? ? ? ? ? ? ? ? ? ? ?struct inode *, struct dentry *, unsigned int);
? ? ? ?......

在file對應(yīng)的操作方法file_operations里面定義了我們經(jīng)常使用的read和write:

struct file_operations {
? ? ? ? ? ? ? ?......
? ? ? ?ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
? ? ? ?ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
? ? ? ? ? ? ? ?......
? ? ? ?int (*mmap) (struct file *, struct vm_area_struct *);
? ? ? ?int (*open) (struct inode *, struct file *);
? ? ? ?int (*flush) (struct file *, fl_owner_t id);

3. Page Cache

在VFS層往下看,我們注意到了Page Cache。它的中文譯名叫頁高速緩存,是Linux內(nèi)核使用的主要磁盤高速緩存,是一個純內(nèi)存的工作組件,其作用就是來給訪問相對比較慢的磁盤來進行訪問加速。如果要訪問的文件block正好存在于Page Cache內(nèi),那么并不會有實際的磁盤IO發(fā)生。如果不存在,那么會申請一個新頁,發(fā)出缺頁中斷,然后用磁盤讀取到的block內(nèi)容來填充它 ,下次直接使用。Linux內(nèi)核使用搜索樹來高效管理大量的頁面。

如果你有特殊的需求想要繞開Page Cache,只要設(shè)置DIRECT_IO就可以了。有兩種情況需要繞開:

  • 測試磁盤IO的真實性能

  • 節(jié)約使用Page Cache時系統(tǒng)調(diào)用陷入到內(nèi)核態(tài),以及內(nèi)核內(nèi)存向用戶進程內(nèi)存拷貝到開銷。

4. 文件系統(tǒng)

在我在之前的文章《新建一個空文件占用多少磁盤空間?》、《理解格式化原理》里討論的都是具體的文件系統(tǒng)。文件系統(tǒng)里最重要的兩個概念就是inode和block,這兩個我們在之前的文章里也都見過了。一個block是多大呢,這是運維在格式化的時候決定的,一般默認是4KB。

除了inode和block,每個文件系統(tǒng)還會定義自己的實際操作函數(shù)。例如在ext4中定義的ext4_file_operations和ext4_file_inode_operations如下:

const struct file_operations ext4_file_operations = {
? ? ? ?.read_iter ? ? ?= ext4_file_read_iter,
? ? ? ?.write_iter ? ? = ext4_file_write_iter,
? ? ? ?.mmap ? ? ? ? ? = ext4_file_mmap,
? ? ? ?.open ? ? ? ? ? = ext4_file_open,
? ? ? ?......
};
const struct inode_operations ext4_file_inode_operations = {
? ? ? ?.setattr ? ? ? ?= ext4_setattr,
? ? ? ?.getattr ? ? ? ?= ext4_file_getattr,
? ? ? ?......
};

5. 通用塊層

通用塊層是一個處理系統(tǒng)中所有塊設(shè)備IO請求的內(nèi)核模塊。它定義了一個叫bio的數(shù)據(jù)結(jié)構(gòu)來表示一次IO操作請求(include/linux/bio.h)。

那么一次bio里對應(yīng)的IO大小單位是頁面,還是扇區(qū)呢?都不是,是段!每個bio可能會包含多個段。一個段是一個完整的頁面,或者是頁面的一部分,具體請參考這個(https://www.ilinuxkernel.com/files/Linux.Generic.Block.Layer.pdf)。

為什么要搞出個段這么讓人費解的東西呢?這是因為在磁盤中連續(xù)存儲的數(shù)據(jù),到了內(nèi)存Page Cache里的時候可能內(nèi)存并不連續(xù)了。這種狀況出現(xiàn)是正常的,不能說磁盤中連續(xù)的數(shù)據(jù)我在內(nèi)存中就非得用連續(xù)的空間來緩存。段就是為了能讓一次磁盤IO能DMA到多“段”地址并不連續(xù)的內(nèi)存中的。

一個常見的扇區(qū)/段/頁的大小對比如下圖:

圖2 Linux的頁/段/扇區(qū)的關(guān)系示例

6. IO調(diào)度層

當通用塊層把IO請求實際發(fā)出以后,并不一定會立即被執(zhí)行。因為調(diào)度層會從全局出發(fā),盡量讓整體磁盤IO性能最大化。大致的工作方式是讓磁頭類似電梯那樣工作,先往一個方向走,到頭再回來,這樣磁盤效率會比較高一些。具體的算法有noop,deadline和cfg等。

在你的機器上,通過dmesg | grep -i scheduler來查看你的Linux支持的算法,并在測試的時候可以選擇其中的一種。

2讀文件過程

我們已經(jīng)把Linux IO棧里的各個內(nèi)核組件都介紹一邊了。現(xiàn)在我們再從頭整體過一下讀取文件的過程

  • lib里的read函數(shù)首先進入系統(tǒng)調(diào)用sys_read

  • 在sys_read再進入VFS里的vfs_read、generic_file_read等函數(shù)

  • 在vfs里的generic_file_read會判斷是否緩存命中,命中則返回

  • 若不命中內(nèi)核在Page Cache里分配一個新頁框,發(fā)出缺頁中斷,

  • 內(nèi)核向通用塊層發(fā)起塊I/O請求,塊設(shè)備屏蔽了磁盤、U盤的差異

  • 通用塊層把用bio代表的I/O請求放到IO請求隊列中

  • IO調(diào)度層通過電梯算法來調(diào)度隊列中的請求

  • 驅(qū)動程序向磁盤控制器發(fā)出讀取命令控制,DMA方式直接填充到Page Cache中的新頁框

  • 控制器發(fā)出中斷通知

  • 內(nèi)核將用戶需要的1個字節(jié)填充到用戶內(nèi)存中

  • 然后你的進程被喚醒

可以看到,如果Page Cache命中的話,根本就沒有磁盤IO產(chǎn)生。所以,大家不要覺得代碼里出現(xiàn)幾個讀寫文件的邏輯就覺得性能會慢的不行。操作系統(tǒng)已經(jīng)替你優(yōu)化了很多很多,內(nèi)存級別的訪問延遲大約是ns級別的,比機械磁盤IO快了2-3個數(shù)量級。如果你的內(nèi)存足夠大,或者你的文件被訪問的足夠頻繁,其實這時候的read操作極少有真正的磁盤IO發(fā)生。

我們再看第二種情況,如果Page Cache不命中的話,Linux實際進行了多少個字節(jié)的磁盤IO。整個IO過程中涉及到了好幾個內(nèi)核組件。?而每個組件之間都是采用不同長度的塊來管理磁盤數(shù)據(jù)的。

  • Page Cache是以頁為單位的,Linux頁大小一般是4KB(避免有大神挑刺,這里說下Linux能設(shè)置大內(nèi)存頁)

  • 文件系統(tǒng)是以塊為單位來管理的。使用dumpe2fs可以查看,一般一個塊默認是4KB

  • 通用塊層是以段為單位來處理磁盤IO請求的,一個段為一個頁或者是頁的一部分

  • IO調(diào)度程序通過DMA方式傳輸N個扇區(qū)到內(nèi)存,扇區(qū)一般為512字節(jié)

  • 硬盤也是采用“扇區(qū)”的管理和傳輸數(shù)據(jù)的

可以看到,雖然我們從用戶角度確實是只讀了1個字節(jié)(開篇的代碼中我們只給這次磁盤IO留了一個字節(jié)的緩存區(qū))。但是在整個內(nèi)核工作流中,最小的工作單位是磁盤的扇區(qū),為512字節(jié),比1個字節(jié)要大的多。另外block、page cache等高層組件工作單位更大,所以實際一次磁盤讀取是很多字節(jié)一起進行的。假設(shè)段就是一個內(nèi)存頁的話,一次磁盤IO就是4KB(8個512字節(jié)的扇區(qū))一起進行讀取。

Linux內(nèi)核中我們沒有講到的是還有一套復雜的預讀取的策略。所以,在實踐中,可能比8更多的扇區(qū)來一起被傳輸?shù)絻?nèi)存中。

3最后

操作系統(tǒng)的本意是做到讓你簡單可依賴, 讓你盡量把它當成一個黑盒。你想要一個字節(jié),它就給你一個字節(jié),但是自己默默干了許許多多的活兒。我們雖然國內(nèi)絕大多數(shù)開發(fā)都不是搞底層的,但如果你十分關(guān)注你的應(yīng)用程序的性能,你應(yīng)該明白操作系統(tǒng)的什么時候悄悄提高了你的性能,是怎么來提高的。以便在將來某一個時候你的線上服務(wù)器扛不住快要掛掉的時候,你能迅速找出問題所在。

我們再擴展一下,假如Page Cache沒有命中,那么一定會有傳動到機械軸上的磁盤IO嗎??

其實也不一定,為什么,因為現(xiàn)在的磁盤本身就會帶一塊緩存。另外現(xiàn)在的服務(wù)器都會組建磁盤陣列,在磁盤陣列里的核心硬件Raid卡里也會集成RAM作為緩存。只有所有的緩存都不命中的時候,機械軸帶著磁頭才會真正工作。

總結(jié)

以上是生活随笔為你收集整理的一招修复内存不能read_read文件一个字节实际会发生多大的磁盘IO?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。