不知道你有沒有遇到過當(dāng)一個(gè)文件夾下文件特別多,在下面執(zhí)行ls
命令的時(shí)候要等好長時(shí)間才能展現(xiàn)出來的問題妹窖?如果有纬朝,你有想過這是為什么嗎,我們?cè)撊绾谓鉀Q?
要想深入理解這個(gè)的問題產(chǎn)生的原因骄呼,我們就需要從文件夾占用的磁盤空間開始討論了共苛。
inode消耗驗(yàn)證
在《新建一個(gè)空文件占用多少磁盤空間?》中我提到了每一個(gè)文件會(huì)消耗其所在文件夾中的一點(diǎn)空間蜓萄。文件夾呢隅茎,其實(shí)也一樣會(huì)消耗inode的。 我們先看一下當(dāng)前inode的占用情況
# df -i
Filesystem Inodes IUsed IFree IUse% Mounted on
......
/dev/sdb1 2147361984 12785020 2134576964 1% /search
再創(chuàng)建一個(gè)空文件夾
# mkdir temp
# df -i
Filesystem Inodes IUsed IFree IUse% Mounted on
......
/dev/sdb1 2147361984 12785021 2134576963 1% /search
通過IUsed可以看到嫉沽,和空文件一樣辟犀,空的文件夾也會(huì)消耗掉一個(gè)inode。不過這個(gè)很小绸硕,我的機(jī)器上才是256字節(jié)而已堂竟,應(yīng)當(dāng)不是造成ls
命令卡主的元兇魂毁。
block消耗驗(yàn)證
文件夾的名字存在哪兒了呢?嗯跃捣,和《新建一個(gè)空文件占用多少磁盤空間漱牵?》里的文件類似,會(huì)消耗一個(gè)ext4_dir_entry_2
(今天用ext4舉例疚漆,它在linux源碼的fs/ext4/ex4.h文件里定義),放到其父目錄的block里了酣胀。根據(jù)這個(gè),相信你也很快能想到娶聘,如果它自己節(jié)點(diǎn)下創(chuàng)建一堆文件的話闻镶,就會(huì)占用它自己的block。我們來動(dòng)手驗(yàn)證一下:
# mkdir test
# cd test
# du -h
4.0K .
這里的4KB就表示消耗掉了一個(gè)block丸升。 空文件不消耗block铆农,空目錄為啥一開始就消耗block了呢,那是因?yàn)槠浔仨毮J(rèn)帶兩個(gè)目錄項(xiàng)"."和".."狡耻。另外這個(gè)4K在你的機(jī)器上不一定是這么大墩剖,它其實(shí)是一個(gè)block size,在你格式化的時(shí)候決定的夷狰。
我們?cè)傩陆▋蓚€(gè)空的文件岭皂,再查看一下:
# touch aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab
# touch aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
# du -h
4.0K .
貌似,沒有什么變化沼头。這是因?yàn)?/p>
- 第一爷绘、新的空文件不占用block,所以這里顯示的仍然是目錄占用的block进倍。
- 第二土至、之前文件夾創(chuàng)建時(shí)候分配的4KB里面空閑空間還有,夠放的下這兩個(gè)文件項(xiàng)
那么我再多創(chuàng)建一些試試猾昆,動(dòng)用腳本創(chuàng)建100個(gè)文件名長度為32Byte的空文件陶因。
#!/bin/bash
for((i=1;i<=100;i++));
do
file="tempDir/"$(echo $i|awk '{printf("%032d",$0)}')
echo $file
touch $file
done
# du -h
12K .
哈哈,這時(shí)我們發(fā)現(xiàn)目錄占用的磁盤空間變大了垂蜗,成了3個(gè)Block了坑赡。當(dāng)我們創(chuàng)建到10000個(gè)文件的時(shí)候,
# du -h
548K .
在每一個(gè)ext4_dir_entry_2
里都除了文件名以外么抗,還記錄著inode號(hào)等信息,詳細(xì)定義如下:
struct ext4_dir_entry_2 {
__le32 inode; /* Inode number */
__le16 rec_len; /* Directory entry length */
__u8 name_len; /* Name length */
__u8 file_type;
char name[EXT4_NAME_LEN]; /* File name */
};
我們計(jì)算一下亚铁,平均每個(gè)文件占用的空間=548K/10000=54字節(jié)蝇刀。也就是說,比我們的文件名32字節(jié)大一點(diǎn)點(diǎn)徘溢,基本對(duì)上了吞琐。 這里我們也領(lǐng)會(huì)到一個(gè)事實(shí)捆探,文件名越長,在其父目錄中消耗的空間也會(huì)越大站粟。
本文結(jié)論
一個(gè)文件夾當(dāng)然也是要消耗磁盤空間的黍图。
- 首先要消耗掉一個(gè)inode,我的機(jī)器上它是256字節(jié)
- 需要消耗其父目錄下的一個(gè)目錄項(xiàng)
ext4_dir_entry_2
奴烙,保存自己inode號(hào)助被,目錄名。 - 其下面如果創(chuàng)建文件夾或者文件的話切诀,它就需要在自己的block里
ext4_dir_entry_2
數(shù)組
目錄下的文件/子目錄越多揩环,目錄就需要申請(qǐng)?jiān)蕉嗟腷lock。另外ext4_dir_entry_2
大小不是固定的幅虑,文件名/子目錄名越長丰滑,單個(gè)目錄項(xiàng)消耗的空間也就越大。
對(duì)于開篇的問題倒庵,我想你現(xiàn)在應(yīng)該明白為什么了,問題出在文件夾的block身上褒墨。
這就是當(dāng)你的文件夾下面文件特別多,尤其是文件名也比較長的時(shí)候擎宝,它會(huì)消耗掉非常多的block郁妈。當(dāng)你遍歷文件夾的時(shí)候,如果Page Cache中沒有命中你要訪問的block认臊,就會(huì)穿透到磁盤上進(jìn)行實(shí)際的IO圃庭。在你的角度來看,就是你執(zhí)行完ls
后失晴,卡住了剧腻。
那么你肯定會(huì)問,我確實(shí)要保存許許多多的文件涂屁,我該怎么辦? 其實(shí)也很簡單书在,多創(chuàng)建一些文件夾就好了,一個(gè)目錄下別存太多拆又,就不會(huì)有這個(gè)問題了儒旬。工程實(shí)踐中,一般的做法就是通過一級(jí)甚至是二級(jí)hash把文件散列到多個(gè)目錄中帖族,把單目錄文件數(shù)量控制在十萬或萬以下栈源。
ext的bug
貌似今天的實(shí)踐應(yīng)該結(jié)束了,現(xiàn)在讓我們把剛剛創(chuàng)建的文件全部刪掉竖般,再看一下甚垦。
# rm -f *
# du -h
72K .
等等,什么情況?文件夾下的文件都已經(jīng)刪了艰亮,該文件夾為什么還占用72K的磁盤空間闭翩?
這個(gè)疑惑也伴隨了我很長時(shí)間,后來才算是解惑迄埃。問題關(guān)鍵在于ext4_dir_entry_2
中的rec_len
疗韵。這個(gè)變量存儲(chǔ)了當(dāng)前整個(gè)ext4_dir_entry_2
對(duì)象的長度,這樣操作系統(tǒng)在遍歷文件夾的時(shí)候侄非,就可以通過當(dāng)前的指針蕉汪,加上這個(gè)長度就可以找到文件夾中下一個(gè)文件的dir_entry
了。這樣的優(yōu)勢(shì)是遍歷起來非常方便彩库,有點(diǎn)像是一個(gè)鏈表肤无,一個(gè)一個(gè)穿起來的。
但是骇钦,如果要?jiǎng)h除一個(gè)文件的話宛渐,就有點(diǎn)小麻煩了,當(dāng)前文件結(jié)構(gòu)體變量不能直接刪眯搭,否則鏈表就斷了窥翩。
Linux的做法是在刪除文件的時(shí)候,在其目錄中只是把inode設(shè)置為0就拉倒鳞仙,并沒有回收整個(gè)ext4_dir_entry_2
對(duì)象寇蚊。其實(shí)和大家做工程的時(shí)候經(jīng)常用到的假刪除是一個(gè)道理。現(xiàn)在的xfs文件系統(tǒng)好像已經(jīng)沒有這個(gè)小問題了棍好,但具體咋解決的仗岸,暫時(shí)沒有深入研究,如果你有答案借笙,歡迎留言扒怖!
歡迎搜索微信公眾號(hào):開發(fā)內(nèi)功修煉