當(dāng)前位置:首頁 > 公眾號(hào)精選 > CPP開發(fā)者
[導(dǎo)讀]關(guān)于文件系統(tǒng),相信大家都不陌生。身為攻城獅的我們幾乎天天都會(huì)與之打交道,但是細(xì)深剖一下,其中又有多少是我們理解深度不夠的呢。那么讓我們一起來看一下下面這一組Linux文件系統(tǒng)相關(guān)的問題吧:1、機(jī)械磁盤隨機(jī)讀寫時(shí)速度非常慢,操作系統(tǒng)是采用什么技巧來提高隨機(jī)讀寫的性能的?2、touc...

關(guān)于文件系統(tǒng),相信大家都不陌生。身為攻城獅的我們幾乎天天都會(huì)與之打交道,但是細(xì)深剖一下,其中又有多少是我們理解深度不夠的呢。那么讓我們一起來看一下下面這一組 Linux 文件系統(tǒng)相關(guān)的問題吧:


1、機(jī)械磁盤隨機(jī)讀寫時(shí)速度非常慢,操作系統(tǒng)是采用什么技巧來提高隨機(jī)讀寫的性能的?
2、touch 一個(gè)新的空文件占用磁盤空間嗎?占用的話占用多少?
3、新建一個(gè)空目錄占用磁盤空間嗎?占用多少?和新建一個(gè)文件相比,哪個(gè)占用的更大?
4、你知道文件名是記錄在磁盤的什么地方嗎?
5、文件名最長多長?受什么制約?
6、文件名太長了會(huì)影響系統(tǒng)性能嗎?為什么會(huì)產(chǎn)生影響?
7、一個(gè)目錄下最多能建立多少個(gè)文件?
8、新建一個(gè)內(nèi)容大小 1 k 的文件,實(shí)際會(huì)占用多大的磁盤空間?
9、向操作系統(tǒng)發(fā)起讀取文件 2 Byte 的命令,操作系統(tǒng)實(shí)際會(huì)讀取多少呢?
10、我們使用文件時(shí)要怎么樣來能提高磁盤IO速度?


如果你能想也不用想的就回答上來百分八十的問題,那么請(qǐng)關(guān)掉本篇文章吧。如果不能,而且你也像作者一樣對(duì)有窺探操作系統(tǒng)隱私的嗜好,那么就請(qǐng)隨我一起來探索文件系統(tǒng)的這些有趣的地方,相信理解了這些之后對(duì)我們手中的工作會(huì)有很大的幫助。這篇文章實(shí)驗(yàn)所用文件系統(tǒng)是 ext 系的。


一、磁盤構(gòu)成及分區(qū)

1、磁盤物理結(jié)構(gòu)

還是先從最基本的磁盤物理結(jié)構(gòu)說起吧,注意本文只討論機(jī)械磁盤,SSD 不在本文討論范圍之內(nèi)。我們?nèi)祟惞芾砣魏问挛锟偸橇?xí)慣先劃分出一定的結(jié)構(gòu),在此規(guī)則的基礎(chǔ)上進(jìn)行管理。軍隊(duì)分軍、師、旅、團(tuán)和營。公司分事業(yè)群、部門、中心和小組。然后對(duì)于管理磁盤,分磁盤面、磁頭、磁道、柱面和扇區(qū)。


  • 磁盤面:磁盤是由一疊磁盤面組成,見圖。
  • 磁頭(Heads):每個(gè)磁頭對(duì)應(yīng)一個(gè)磁盤面,負(fù)責(zé)該磁盤面上的數(shù)據(jù)的讀寫。。
  • 磁道(Track):每個(gè)盤面會(huì)圍繞圓心劃分出多個(gè)同心圓圈,每個(gè)圓圈叫做一個(gè)磁道。
  • 柱面(Cylinders):所有盤片上的同一位置的磁道組成的立體叫做一個(gè)柱面。
  • 扇區(qū)(Sector):以磁道為單位管理磁盤仍然太大,所以計(jì)算機(jī)前輩們又把每個(gè)磁道劃分出了多個(gè)扇區(qū),見下右圖



本人愛上 Linux 的一個(gè)原因就是只要你愿意下功夫,你就能把 Linux 的內(nèi)部邏輯徹底鋪開來看,這點(diǎn)比 Windows 要好太多了。Linux 上可以通過 fdisk 命令,來查看當(dāng)前系統(tǒng)使用的磁盤的這些物理信息。



以上是我本人的一臺(tái)虛擬機(jī)的磁盤物理信息??梢钥闯鑫业拇疟P有 255 個(gè) heads,也就是說共有 255 個(gè)盤面。3263 個(gè) cylinders,也就是說每個(gè)盤面上都有 3263 個(gè)磁道, 63 sectors/track 說的是每個(gè)磁道上共有 63 個(gè)扇區(qū)。命令結(jié)果也給出了 Sector size 的值是 512 bytes。那我們動(dòng)筆算一下該磁盤的大小吧。


255 盤面  * 3263 柱面 * 63 扇區(qū) * 每個(gè)扇區(qū) 512 bytes = 26839088640 byte。


結(jié)果是 26.8 G,和磁盤的總大小基本相符(至于fdisk給出的詳細(xì)結(jié)果相差了約4M的大小,筆者也沒有弄徹底明白,有興趣的讀者可以繼續(xù)研究)。


不過要注意一點(diǎn)就是上面的盤面等數(shù)據(jù)是邏輯上的,是物理盤面映射轉(zhuǎn)化而來的,這個(gè)轉(zhuǎn)化關(guān)系我現(xiàn)在還沒搜到特別好的資料。


2、分區(qū)

分區(qū)是操作系統(tǒng)對(duì)磁盤進(jìn)行管理的第一步,這也是我們?nèi)魏我粋€(gè)計(jì)算機(jī)使用者都非常熟悉的概念。例如Windows下的C、D、E、F盤。那么請(qǐng)思考一下,


思考:前面的磁盤的詳細(xì)物理結(jié)構(gòu)已經(jīng)有了,如果讓你把整塊磁盤分成C、D等分區(qū),你會(huì)怎么分呢?


  • 方案一:255 個(gè)盤面,C 盤是 0-100 盤面, D 盤是 101-200 個(gè)盤面, ……
  • 方案二:3263 個(gè)柱面,C 盤 0-1000 個(gè)柱面,D 盤 1001-20001 個(gè)柱面, ……
對(duì)于以上的兩個(gè)方案,你會(huì)選擇哪一種呢??先說下磁盤 IO 時(shí)的過程。


  • 第一步,首先是磁頭徑向移動(dòng)來尋找數(shù)據(jù)所在的磁道。這部分時(shí)間叫尋道時(shí)間。
  • 第二步,找到目標(biāo)磁道后通過盤面旋轉(zhuǎn),將目標(biāo)扇區(qū)移動(dòng)到磁頭的正下方。
  • 第三步,向目標(biāo)扇區(qū)讀取或者寫入數(shù)據(jù)。到此為止,一次磁盤 IO 完成。
故單次磁盤 IO 時(shí)間 = 尋道時(shí)間 旋轉(zhuǎn)延遲 存取時(shí)間。


對(duì)于旋轉(zhuǎn)延時(shí),現(xiàn)在主流服務(wù)器上經(jīng)常使用的是1W轉(zhuǎn)/分鐘的磁盤,每旋轉(zhuǎn)一周所需的時(shí)間為60*1000/10000=6ms,故其旋轉(zhuǎn)延遲為(0-6ms)。對(duì)于存取時(shí)間,一般耗時(shí)較短,為零點(diǎn)幾 ms。對(duì)于尋道時(shí)間,現(xiàn)代磁盤大概在 3-15 ms,其中尋道時(shí)間大小主要受磁頭當(dāng)前所在位置和目標(biāo)磁道所在位置相對(duì)距離的影響。



其實(shí)采用哪一種,最主要看的是那種方式性能更快。因?yàn)橥环謪^(qū)下的數(shù)據(jù)經(jīng)常會(huì)一起讀取,假如采用第一種,那么這樣磁頭就需要在 3000 多個(gè) track 間不停地跳來跳去,這樣磁盤的尋道時(shí)間就會(huì)翻倍,磁盤性能就會(huì)下降。


而對(duì)于方案二,假如對(duì)于磁盤C,只需要在磁頭在 1-1000 個(gè)磁道間移動(dòng)就可以了,大大降低了尋道時(shí)間。(實(shí)際上分區(qū)并不是從 0 開始的,磁盤的第一個(gè)磁道對(duì)應(yīng)的柱面會(huì)被用來安裝引導(dǎo)加載程序以及磁盤分區(qū)表)。所以,方案二的分區(qū)方式可以降低磁盤 IO 時(shí)間中的尋道時(shí)間部分,所以所有的操作系統(tǒng)采用的都是方案二,沒有用方案一的。


在Linux下使用過fdisk進(jìn)行分區(qū)的話可以注意到以下信息。



這充分證明了操作系統(tǒng)是采用方案二的。


回到開篇問題 1,操作系統(tǒng)是采用什么技巧來降低隨機(jī)讀寫的性能問題的呢?操作系統(tǒng)通過按磁道對(duì)應(yīng)的柱面劃分分區(qū),來降低磁盤 IO 所花費(fèi)的的尋道時(shí)間 ,進(jìn)而提高磁盤的讀寫性能。


二、目錄與文件

1、引子

好了,磁盤基礎(chǔ)都說完了,那我們正式進(jìn)入主題,開始我們 Linux 文件系統(tǒng)相關(guān)的討論吧。文件系統(tǒng)不就是目錄和文件嗎?這二位可是我們熟悉的不能再熟悉的家伙了??赡愦_認(rèn)它不是你的那位熟悉的陌生人么?我先來來創(chuàng)建個(gè)空目錄和空文件吧,查看結(jié)果如下圖:



我們都知道第五列顯示的是占用的空間大小,那么我來提個(gè)幾個(gè)小小的問題吧。


  • 1)為什么目錄占用的空間是 4096?
  • 2)為什么空文件占用的空間卻是 0?
  • 3)如果空文件真占用 0 byte 空間,那么該文件的文件名、創(chuàng)建者以及權(quán)限-rw-rw-r—等文件夾相關(guān)的信息都存到哪兒去了?

2、我就不信空文件不占用空間

為了解開這個(gè)謎底,需要借助 df 命令。輸入 df –i,



Linux 結(jié)果中紅框位置處顯示的是 inodes 的相關(guān)信息,如果你對(duì) inode 的概念不熟悉,你可以暫時(shí)把它當(dāng)成一個(gè)操作系統(tǒng)秘密管理的一個(gè)家伙,會(huì)占用空間就行了。接下來我 touch 一個(gè)空的文件后再次 df -i。



雖然前面操作系統(tǒng)告訴我們,一個(gè)新建的空文件占用的空間是 0。但是這個(gè)實(shí)驗(yàn)卻證明操作系統(tǒng)“欺騙”了我們,它消耗掉了一個(gè) inode。那么 inode 的節(jié)點(diǎn)大小是多少呢,使用 dumpe2fs 命令可以幫助我們查看到這個(gè)東東的實(shí)際大小。在輸出的結(jié)果中我們可以找到下面這行。



它告訴我們每個(gè) inode 的大小是 256 Byte。當(dāng)然這個(gè)大小每臺(tái)機(jī)器都會(huì)不一樣,它實(shí)際上是在系統(tǒng)格式化磁盤的時(shí)候決定的。


好了,開篇第二個(gè)問題也有答案了。原來新建一個(gè)空的文件是會(huì)占用磁盤空間的,實(shí)際占用的是 256 Byte。哦,不,準(zhǔn)確的說法應(yīng)該是一個(gè) inode size,具體的值是在格式化時(shí)決定的。


再說說新建空目錄吧,前面說了新建空目錄會(huì)占用4KB的磁盤空間。那么僅僅如此嗎?我們同樣在新建目錄前后都使用df –i來監(jiān)視系統(tǒng) inode 的占用。


原來目錄也是會(huì)占用一個(gè) inode 節(jié)點(diǎn)的,第三個(gè)問題也有了答案了,新建一個(gè)空目錄會(huì)占用磁盤空間 4KB inode size。哦,這個(gè)在你的系統(tǒng)上也不一定是4K,它實(shí)際上一個(gè) block size。同樣在 dumpe2fs 下可以看到。



只不過我的磁盤在格式化時(shí)采用的是 4KB 的大小,呵呵!


3、神秘的空目錄的4KB

前面的謎團(tuán)解開了,可以作為攻城獅的我對(duì)另外一個(gè)東西產(chǎn)生了好奇心。就是空目錄占用的那 4KB,這些空間是用來存什么的呢?好神秘呀。cd 到我們新建的目錄下查看。



我們?cè)傩陆▋蓚€(gè)空的文件,再查看下目錄的空間占用情況。



貌似,沒有什么新發(fā)現(xiàn)。因?yàn)榭瘴募徽加?block,所以這里顯示的仍然是目錄占用的 block,和之前大小沒有變化。那么我繼續(xù)使用 php 腳本創(chuàng)建 100 個(gè)文件名長度為 32Byte 的空文件。



這時(shí)我們發(fā)現(xiàn)目錄占用的磁盤空間變大了,成了 3 個(gè) Block 了。哈哈,這就解答了我們開篇的第四個(gè)問題,文件名是存在目錄占用的 block 中的。接下來我又還證明了每個(gè)目錄 block 中能保存的文件名個(gè)數(shù)是和文件名的長度有關(guān)的(好像有點(diǎn)廢話的意思,不過親手證明自己的猜想還是有點(diǎn)小爽的)。我又另外新建了個(gè)空目錄,創(chuàng)建了 100 個(gè)文件名長度為 32*3 個(gè)空文件,該臨時(shí)目錄占用的磁盤空間如下:



你可能會(huì)問我為什么文件名變成了 3 倍后,占用的 block 數(shù)目為什么沒有變成 3 倍。其實(shí)Linux文件系統(tǒng)關(guān)于文件的結(jié)構(gòu)體中除了文件名以外,還有其它的一些字段的,文件名變長3倍不會(huì)導(dǎo)致結(jié)構(gòu)體變大 3 倍的,這點(diǎn)可以參考 Linux 系統(tǒng)內(nèi)核相關(guān)書籍。


好了,到現(xiàn)在開篇問題 6 也有了答案了。文件名長了當(dāng)然會(huì)對(duì)系統(tǒng)性能產(chǎn)生影響,因?yàn)檫@可能會(huì)導(dǎo)致更多的磁盤 IO。很多程序員都喜歡將文件命名為有意義的長串,使人一看文件名就知道用途。當(dāng)然我沒說這樣不好,但是如果你的文件數(shù)量相當(dāng)大的時(shí)候,你就要考慮你的文件名是否導(dǎo)致你的目錄 block 占用太多了。


占用的空間倒是小事,磁盤很便宜,但是你得考慮下在目錄下查找文件時(shí)操作系統(tǒng)的感受,操作系統(tǒng)可需要用你你提供的文件名進(jìn)行字符串比較,而且運(yùn)氣不好的話需要將其名下所有 block 都搞一遍才行啊。(當(dāng)然了,你的文件名長度不變態(tài),而且數(shù)量沒有達(dá)到十萬數(shù)量級(jí)的話實(shí)際上這個(gè)開銷也不會(huì)太大,但是這個(gè)開銷你還是知道的為好)


至于開篇問題 5,文件名最長多長。實(shí)際上Linux操作系統(tǒng)就是為了避免程序員不節(jié)制地使用長文件名,強(qiáng)加了個(gè)限制,不得超過 255 byte。


另外,大家有沒有經(jīng)驗(yàn),在目錄下文件很多的時(shí)候,我們使用ls命令時(shí)會(huì)很慢?,F(xiàn)在大家知道原因了吧,這時(shí)實(shí)際上操作系統(tǒng)在讀取當(dāng)前目錄的所有 block ,如果 block 比較多的話,可能得需要多次 IO 操作才能完成這個(gè)簡單的 ls 命令。


我在自己的電腦某個(gè)目錄下創(chuàng)建了一 100W 個(gè)空文件,ls 命令 1 分鐘還沒出結(jié)果,被我 ctrl c 掉了。在自己的項(xiàng)目中可不要這么干,雖然操作系統(tǒng)可以 cache 住你的目錄數(shù)據(jù),使你下次調(diào)用時(shí)會(huì)快很多,但我還是建議你單個(gè)目錄下文件數(shù)目不要過萬。否則你的程序在重啟后首次運(yùn)行時(shí)可能會(huì)出現(xiàn)性能不佳的情況。


好了,回到開篇問題 7,你有答案了嗎?一個(gè)目錄下最多能建多少個(gè)文件,這個(gè)最多其實(shí)是受限于你目錄所在分區(qū)的 inode 數(shù)量,你有 100W 個(gè) inode,你最多就可以新建 100W 個(gè)文件。但是,上面說了,單個(gè)目錄下文件數(shù)量最好不要過萬,否則會(huì)帶來系統(tǒng)性能的問題。


4、文件的block

再做個(gè)關(guān)于文件的實(shí)驗(yàn)。我新建了個(gè)空目錄,并在其下新建了個(gè)文件,里面只寫了一個(gè)空格數(shù)據(jù),保存后 du 命令顯示如下:



這 8K 里有 4K 是目錄的,也就可以算出操作系統(tǒng)為只包含一個(gè)空格的文件分配了 4KB。其實(shí)文件的 block 比較簡單的了,不像目錄的 block 里會(huì)存很多文件系統(tǒng)的結(jié)構(gòu)體,文件的 block 里只會(huì)保存文件的數(shù)據(jù)。上面這個(gè)實(shí)驗(yàn)表明,操作系統(tǒng)分配空間時(shí)是以 block 為最小單位。


也就是說只要你的文件數(shù)據(jù)不為空,操作系統(tǒng)就至少會(huì)給你分配一個(gè) block 來存儲(chǔ),直到你超過了 4KB,操作系統(tǒng)再給你分配下一個(gè) block,就是這樣。所以對(duì)于開篇問題 8,新建一個(gè)內(nèi)容大小為 1k 的文件,實(shí)際會(huì)占用 1個(gè) block(一般為4k)和一個(gè) inode(一般為256byte)。


其實(shí)文件系統(tǒng)在向磁盤發(fā)起 IO 請(qǐng)求的時(shí)候,也是以 block size 為單位的。哪怕你只向操作系統(tǒng)發(fā)起讀取文件的 2 Byte,但是操作系統(tǒng)會(huì)一次性給你讀取 4KB 回來。因此磁盤 IO 真的是很慢,而且我們只要訪問了這 2 Byte,確實(shí)很有可能接下來繼續(xù)訪問這 2byte 后面的內(nèi)容,這也就是程序局部性原理,所以操作系統(tǒng)索性一次性就多讀取些回來了。呵呵,這就是開篇問題9的答案。


這就像我們?nèi)ス涑?,逛一次真的是很浪費(fèi)時(shí)間,這可要比坑爹的磁盤 IO 也慢許多了。我們總不會(huì)逛了一圈超市就買了一個(gè)蘋果就回來了吧,我們肯定會(huì)多買些東西為家里以后的需求準(zhǔn)備著,反正買一堆東西比買一個(gè)蘋果也沒多花多少時(shí)間,何樂為不為呢,就是這個(gè)道理。


再說說開篇問題 10,我們攻城獅怎么樣設(shè)計(jì)你的文件能提高一些 IO 速度呢?那就是如果你知道你的要新建的文件大概會(huì)占用多大的空間的話,比如 1M。那么你新建文件時(shí)就順便和操作系統(tǒng)說一下,讓它幫你將文件的 size 預(yù)留下來。這樣實(shí)際上操作系統(tǒng)時(shí)會(huì)盡可能為你分配連續(xù)的 block,這樣你再讀取這個(gè)文件時(shí),磁頭就省去很多尋道時(shí)間了,IO 速度就顯得快多了。


三、寫在后面的話

前面我們說的都是基于我自己的文件系統(tǒng),情形是一個(gè) block size 是 4KB,一個(gè) inode size 是 256byte,包括我虛擬機(jī)上的 inode 數(shù)量才只有 140 多萬個(gè)。這些值實(shí)際上不是固定的,你完全可以在格式化你的硬盤的時(shí)候設(shè)置成其它的值。設(shè)置的原則就是看你的硬盤的容量,以及你的用途。


如果你的文件都是大于 4KB,甚至是幾 M,幾 G 的文件,那么建議你的 block 還是盡可能的大一點(diǎn)吧,這樣 inode 里就能少記幾個(gè)地址。


如果你的文件大部分都是1K以下的,那么確實(shí)使用4K的block會(huì)造成一點(diǎn)點(diǎn)浪費(fèi),如果你的老板對(duì)成本要求異??量痰脑挘憧梢赃m當(dāng)考慮把你的 block 設(shè)置得小一點(diǎn)。


另外,要關(guān)注你的文件系統(tǒng)的 inode。操作系統(tǒng)在查看目錄和文件占用的磁盤空間信息時(shí)把inode節(jié)點(diǎn)的占用給隱藏起來了,其用意在于為用戶提供一個(gè)白盒的環(huán)境,把數(shù)據(jù)占用的空間交給我們來認(rèn)知,而把 inode 信息隱藏起來為了降低我們理解操作系統(tǒng)的難度。


而實(shí)際上,我們作為非普通用戶的開發(fā)人員應(yīng)該具備這個(gè)知情權(quán)。這個(gè)東東直接關(guān)系到你文件系統(tǒng)能創(chuàng)建文件數(shù)量。否則哪天等你發(fā)現(xiàn)線上機(jī)器磁盤還剩大把大把的空間,但就是 inode 使用光了,那時(shí)候就只有重新格式化或者遷移服務(wù)器了。這兩個(gè)操作想想都覺得苦逼啊,還是能避免就盡量避免吧。


思考題:我們大家有個(gè)經(jīng)驗(yàn)就是目錄下小文件太多的情況下,往其它地方拷貝的話,速度會(huì)非常的慢,我們這時(shí)往往會(huì)把目錄壓縮一下再拷貝?,F(xiàn)在你能說出這樣做為什么會(huì)快嗎?


最后我想再多說一句,這篇九年的時(shí)寫的文章現(xiàn)如今看起來還是很有實(shí)用價(jià)值。


- EOF -


本站聲明: 本文章由作者或相關(guān)機(jī)構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點(diǎn),本站亦不保證或承諾內(nèi)容真實(shí)性等。需要轉(zhuǎn)載請(qǐng)聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請(qǐng)及時(shí)聯(lián)系本站刪除。

CPP開發(fā)者

234 篇文章

關(guān)注

發(fā)布文章

編輯精選

技術(shù)子站

關(guān)閉