文件過多時ls命令爲何會卡住?

不知道你有沒有遇到過當一個文件夾下文件特別多,在下面執行ls命令的時候要等好長時間才能展示出來的問題?若是有,你有想過這是爲何嗎,咱們該如何解決?
要想深刻理解這個的問題產生的緣由,咱們就須要從文件夾佔用的磁盤空間開始討論了。node

inode消耗驗證

在《新建一個空文件佔用多少磁盤空間?》中我提到了每個文件會消耗其所在文件夾中的一點空間。文件夾呢,其實也同樣會消耗inode的。 咱們先看一下當前inode的佔用狀況linux

# df -i  
Filesystem            Inodes   IUsed   IFree IUse% Mounted on
......
/dev/sdb1            2147361984 12785020 2134576964    1% /search

再建立一個空文件夾數組

# mkdir temp
# df -i
Filesystem            Inodes   IUsed   IFree IUse% Mounted on
......
/dev/sdb1            2147361984 12785021 2134576963    1% /search

經過IUsed能夠看到,和空文件同樣,空的文件夾也會消耗掉一個inode。不過這個很小,個人機器上纔是256字節而已,應當不是形成ls命令卡主的元兇。bash

block消耗驗證

文件夾的名字存在哪兒了呢?嗯,和《新建一個空文件佔用多少磁盤空間?》裏的文件相似,會消耗一個ext4_dir_entry_2 (今天用ext4舉例,它在linux源碼的fs/ext4/ex4.h文件裏定義),放到其父目錄的block裏了。根據這個,相信你也很快能想到,若是它本身節點下建立一堆文件的話,就會佔用它本身的block。咱們來動手驗證一下:服務器

# mkdir test
# cd test  
# du -h
4.0K    .

這裏的4KB就表示消耗掉了一個block。 空文件不消耗block,空目錄爲啥一開始就消耗block了呢,那是由於其必須默認帶兩個目錄項"."和".."。另外這個4K在你的機器上不必定是這麼大,它實際上是一個block size,在你格式化的時候決定的。操作系統

咱們再新建兩個空的文件,再查看一下:指針

# touch aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab
# touch aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
# du -h
4.0K    .

貌似,沒有什麼變化。這是由於code

  • 第1、新的空文件不佔用block,因此這裏顯示的仍然是目錄佔用的block。
  • 第2、以前文件夾建立時候分配的4KB裏面空閒空間還有,夠放的下這兩個文件項

那麼我再多建立一些試試,動用腳本建立100個文件名長度爲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    .

哈哈,這時咱們發現目錄佔用的磁盤空間變大了,成了3個Block了。當咱們建立到10000個文件的時候,開發

# du -h
548K     .

在每個ext4_dir_entry_2裏都除了文件名之外,還記錄着inode號等信息,詳細定義以下:

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 */
};

咱們計算一下,平均每一個文件佔用的空間=548K/10000=54字節。也就是說,比咱們的文件名32字節大一點點,基本對上了。 這裏咱們也領會到一個事實,文件名越長,在其父目錄中消耗的空間也會越大。

本文結論

一個文件夾固然也是要消耗磁盤空間的。

  • 首先要消耗掉一個inode,個人機器上它是256字節
  • 須要消耗其父目錄下的一個目錄項ext4_dir_entry_2,保存本身inode號,目錄名。
  • 其下面若是建立文件夾或者文件的話,它就須要在本身的block裏ext4_dir_entry_2數組

目錄下的文件/子目錄越多,目錄就須要申請越多的block。另外ext4_dir_entry_2大小不是固定的,文件名/子目錄名越長,單個目錄項消耗的空間也就越大。

對於開篇的問題,我想你如今應該明白爲何了,問題出在文件夾的block身上。
這就是當你的文件夾下面文件特別多,尤爲是文件名也比較長的時候,它會消耗掉很是多的block。當你遍歷文件夾的時候,若是Page Cache中沒有命中你要訪問的block,就會穿透到磁盤上進行實際的IO。在你的角度來看,就是你執行完ls後,卡住了。

那麼你確定會問,我確實要保存許許多多的文件,我該怎麼辦? 其實也很簡單,多建立一些文件夾就行了,一個目錄下別存太多,就不會有這個問題了。工程實踐中,通常的作法就是經過一級甚至是二級hash把文件散列到多個目錄中,把單目錄文件數量控制在十萬或萬如下。

ext的bug

貌似今天的實踐應該結束了,如今讓咱們把剛剛建立的文件所有刪掉,再看一下。

# rm -f *
# du -h
72K     .

等等,什麼狀況?文件夾下的文件都已經刪了,該文件夾爲何還佔用72K的磁盤空間?
這個疑惑也伴隨了我很長時間,後來纔算是解惑。問題關鍵在於ext4_dir_entry_2中的rec_len。這個變量存儲了當前整個ext4_dir_entry_2對象的長度,這樣操做系統在遍歷文件夾的時候,就能夠經過當前的指針,加上這個長度就能夠找到文件夾中下一個文件的dir_entry了。這樣的優點是遍歷起來很是方便,有點像是一個鏈表,一個一個穿起來的。
可是,若是要刪除一個文件的話,就有點小麻煩了,當前文件結構體變量不能直接刪,不然鏈表就斷了。
Linux的作法是在刪除文件的時候,在其目錄中只是把inode設置爲0就拉倒,並無回收整個ext4_dir_entry_2對象。其實和你們作工程的時候常常用到的假刪除是一個道理。如今的xfs文件系統好像已經沒有這個小問題了,但具體咋解決的,暫時沒有深刻研究,若是你有答案,歡迎留言!


file


開發內功修煉之硬盤篇專輯:


個人公衆號是「開發內功修煉」,在這裏我不是單純介紹技術理論,也不僅介紹實踐經驗。而是把理論與實踐結合起來,用實踐加深對理論的理解、用理論提升你的技術實踐能力。歡迎你來關注個人公衆號,也請分享給你的好友~~~

相關文章
相關標籤/搜索