1?引?"塊"概念
其實硬盤是典型的“塊”設備,操作系統讀取硬盤數據的時候,其實是不會?個個扇區地讀取,這樣 效率太低,?是?次性連續讀取多個扇區,即?次性讀取?個”塊”(block)。
硬盤的每個分區是被劃分為?個個的”塊”。?個”塊”的??是由格式化的時候確定的,并且不可 以更改,最常?的是4KB,即連續?個扇區組成?個”塊”。”塊”是?件存取的最?單位。
注意:
? 磁盤就是?個三維數組,我們把它看待成為?個"?維數組",數組下標就是LBA,每個元素都是扇 區
? 每個扇區都有LBA,那么8個扇區?個塊,每?個塊的地址我們也能算出來。
? 知道LBA:塊號=LBA/8
?? 知道塊號:LAB=塊號*8+n.(n是塊內第?個扇區)
2 引?"分區"概念
其實磁盤是可以被分成多個分區(partition)的,以Windows觀點來看,你可能會有?塊磁盤并且將 它分區成C,D,E盤。那個C,D,E就是分區。分區從實質上說就是對硬盤的?種格式化。但是Linux的設備 都是以?件形式存在,那是怎么分區的呢?
柱?是分區的最?單位,我們可以利?參考柱?號碼的?式來進?分區,其本質就是設置每個區的起 始柱?和結束柱?號碼。此時我們可以將硬盤上的柱?(分區)進?平鋪,將其想象成?個?的平 ?,如下圖所?: ?
3 ext2?件系統
3.1 inode Table&Data Blocks
操作系統在給磁盤分區后,由于每個區的大小可能還是很大,為了方便管理,還會進行分組。我們知道文件=內容+屬性,在Linux下,內容和屬性是分開存儲的,內容存儲在Data Blocks中【以快4kb進行存儲】。在Linux中,正常文件都要有自己的屬性集合{文件大小,創建時間,權限,類型等},而這些屬性則放在一個叫iNode的結構體【128字節】中。
這里有一個特殊情況,文件名不會作為屬性,保存在文件的iNode當中!為什么呢?
以我們現在膚淺的理解:inode節點的大小是固定的,而每個文件名大小是不定的,如果讓inode保存文件名就會讓這個節點處于一個很尷尬的地位。我們也不可能做一個定長的字符數組,太短不行,太長也勢必會浪費很多空間。
在一個組中,有一個inode表,所有的inode節點都放在這張表中。內存讀取數據是以快為單位,也就是一次讀取4kb【一個數據塊,會保存32個inode】。那我們訪問一個inode時,其他inode是不是也加載到內存當中了呢?雀氏如此,有些inode我們可能不會訪問,即使加載到內存當中。不過,這樣做也有一定的原因,連在一起的inode一本來說都是一起創建有一定聯系的文件。它們有更大的概率會被同時訪問,這樣它們就不用多次加載了,這也就是局部性原理,和vector擴容有異曲同工之妙。
在inode中沒有存儲文件名,那我們如何區分一個文件的唯一性呢?
答案就是int inode_number,每個inode都有一個編號來區分文件的唯一性。
我們用ls -li 就可以查看一個文件的inode編號了。
3.2 Block Bitmap&inode Bitmap
我們現在已經知道文件的屬性和內容分別存儲在inode表和數據塊當中,但是,文件系統是如何管理這些區域呢,比如,文件系統是如何知道哪些數據塊使用了,哪些沒有使用,哪些inode節點使用了,哪些inode節點沒有使用呢?
很簡單,在一個分組中有兩張位圖Block Bitmap和inode Bitmap,兩張位圖當中的每個比特位對應的就是每個數據塊和節點。如果比特位是0則表示這塊區域沒有被使用,為1則表示已經被使用。
3.3?GDT(Group Descriptor Table)
塊組描述符表,描述塊組屬性信息,整個分區分成多個塊組就對應有多少個塊組描述符。每個塊組描 述符存儲?個塊組的描述信息,如在這個塊組中從哪?開始是inode Table,從哪?開始是Data Blocks,空閑的inode和數據塊還有多少個等等。塊組描述符在每個塊組的開頭都有?份拷貝。
// 磁盤級blockgroup的數據結構
/** Structure of a blocks group descriptor*/
struct ext2_group_desc
{__le32 bg_block_bitmap; /* Blocks bitmap block */__le32 bg_inode_bitmap; /* Inodes bitmap */__le32 bg_inode_table; /* Inodes table block*/__le16 bg_free_blocks_count; /* Free blocks count */__le16 bg_free_inodes_count; /* Free inodes count */__le16 bg_used_dirs_count; /* Directories count */__le16 bg_pad;__le32 bg_reserved[3];
};
3.4 超級塊(Super Block)??
存放?件系統本?的結構信息,描述整個分區的?件系統信息。記錄的信息主要有:bolck 和inode的總量,未使?的block和inode的數量,?個block和inode的??,最近?次掛載的時間,最近?次寫?數據的時間,最近?次檢驗磁盤的時間等其他?件系統的相關信息。Super Block的信息被破壞,可以說整個?件系統結構就被破壞了超級塊在每個塊組的開頭都有?份拷?(第?個塊組必須有,后?的塊組可以沒有)。
為了保證?件系統在磁盤部分扇區出現物理問題的情況下還能正常?作,就必須保證?件系統的super block信息在這種情況下也能正常訪問。所以?個?件系統的super block會在block group中進?備份,這些super block區域的數據保持?致。
3.5 一些細節
> 格式化的本質就是寫入文件系統的管理信息。
> Super Block在分區中有多份備份,在某些分組中存在。為什么不在一個分區的開始記錄一份,因為超級快實在太重要了,需要多份備份。
> inode和數據塊是跨組編號的。【即在一個分區內,所有的inode編號和塊號都是唯一的】
> inode和數據塊不能跨分區。
> 在Linux中看待目錄和文件一樣,目錄也有自己的inode和數據塊。只不過,目錄的數據塊中存儲的內容是該目錄下的文件名和該文件inode的映射關系。我們之前說一個文件的文件名不會被看做屬性被記錄到inode當中,現在我們知道文件名是被記錄到該文件所屬目錄的數據塊當中了。但是,這里有一個問題:我們知道可以用一個文件的inode找到文件【也就是說,文件系統只認inode編號】,但是,從我們用戶的角度來看,我們是用文件名來找到一個文件的。所以,文件系統是如何通過我們提供的文件名找到文件的呢?
有了上面的鋪墊,我們也可以很好理解:當我們要查找一個磁盤文件時,文件系統先打開該文件所在的當前目錄【也就是路徑】,得到文件名和inode之間的映射關系,最后也是用該文件的inode找到該文件。所以,我們訪問任何文件都必須要有路徑,找文件的本質是:從根目錄開始,進行路徑解析,找到對應的文件。
3.6 路徑緩存
我們訪問任何路徑都要從根目錄下開始進行路徑解析嗎?這樣不就是一直在做磁盤IO嗎,效率是不是太低了??
操作系統在我們進行路徑解析的時候,會把歷史路徑【目錄】記錄下來,形成一課多叉樹【這顆多叉樹和PCB一樣是內存級別的】,進行保存。這也就是Linux的樹狀目錄結構!
Linux中,在內核中維護樹狀路徑結構的內核結構體叫做: struct dentry
struct dentry {atomic_t d_count;unsigned int d_flags; /* protected by d_lock */spinlock_t d_lock; /* per dentry lock */struct inode *d_inode; /* Where the name belongs to - NULL is* negative *//** The next three fields are touched by __d_lookup. Place them here* so they all fit in a cache line.*/struct hlist_node d_hash; /* lookup hash list */struct dentry *d_parent; /* parent directory */struct qstr d_name;struct list_head d_lru; /* LRU list *//** d_child and d_rcu can share memory*/union {struct list_head d_child; /* child of parent list */struct rcu_head d_rcu;} d_u;struct list_head d_subdirs; /* our children */struct list_head d_alias; /* inode alias list */unsigned long d_time; /* used by d_revalidate */struct dentry_operations *d_op;struct super_block *d_sb; /* The root of the dentry tree */void *d_fsdata; /* fs-specific data */
#ifdef CONFIG_PROFILINGstruct dcookie_struct *d_cookie; /* cookie, if any */
#endifint d_mounted;unsigned char d_iname[DNAME_INLINE_LEN_MIN]; /* small names */
};
注意:
? 每個?件其實都要有對應的dentry結構,包括普通?件。這樣所有被打開的?件,就可以在內存中
形成整個樹形結構
? 整個樹形節點也同時會?屬于LRU(Least Recently Used,最近最少使?)結構中,進?節點淘汰
? 整個樹形節點也同時會?屬于Hash,?便快速查找?
? 更重要的是,這個樹形結構,整體構成了Linux的路徑緩存結構,打開訪問任何?件,都在先在這
棵樹下根據路徑進?查找,找到就返回屬性inode和內容,沒找到就從磁盤加載路徑,添加dentry
結構,緩存新路徑
3.7 掛載分區
我們已經可以通過inode在指定分區中找到文件了,也可以通過目錄文件內容找到inode,但是inode不是不能跨分區嗎,我們怎么知道這個文件在哪個分區呢?
在Linux中,某個分區是無法直接訪問的,這時候就需要將目錄掛載到指定分區。也就是說,分區寫入文件系統,需要和指定的目錄關聯,進行掛載才能使用。
如此一來,我們就可以根據訪問目標文件的“路徑前綴”來準確判斷我在哪一個分區!
至此,我們也可以更深刻的理解fopen時,操作系統干了什么了:首先拿到當前文件的路徑,再根據文件名和inode的映射關系得到inode,拿著inode在磁盤中進行索引找到當前文件,如果有可能,也會在dentry樹中索引提高效率。接著系統會在內存中創建struct file對象,將文件的屬性信息拷貝到file對象中的inode字段中,將文件的全部內容或部分內容寫入文件內核緩沖區當中。同時,系統也會創建對應的文件描述符表,并返回對應的文件描述符!
3.8 文件系統相關總結圖表
4. 軟硬鏈接?
4.1 軟連接
我們先來看一個現象:
可以看出,軟連接是一個獨立的文件,因為它有獨立的inode number。?
那軟連接有什么用呢?
在一些大型工程當中,我們的可執行文件可能被藏在一些比較深的路徑,直接找到這個路徑可能比較麻煩,這時候,使用軟鏈接我們就可以更方便的執行該程序,如下:
這就相當于Windows當中的快捷鍵,而軟鏈接文件的內容就是目標文件的路徑。當然,軟鏈接還有其他的作用,以后再說。?
4.2 硬鏈接
我們先來看一個現象:
我們直接使用了命令ln不帶選項就是硬鏈接,我們發現這兩個文件的inode編號都是相同的,所以硬鏈接文件就是不是獨立的文件,而是一組新的文件名和inode的映射關系。?
我們再看到這個數字,這個數字其實就是硬鏈接數。當這個硬鏈接數為2的時候,說明這個文件有兩個,也就是說硬鏈接可以幫我們完成一個文件的備份,當我們以一個文件名刪除文件時,這個文件的內容和屬性其實還在,我們通過另一個文件名就可以得到。這也就是硬鏈接的作用!
上面操作創建了一個目錄dir,這個目錄的硬鏈接數為什么是2呢,我們明明沒有注定完成硬鏈接操作,原因就是,在dir目錄下的影藏文件【.】就是當前路徑【也就是代表dir目錄】,所以 . 就是dir目錄的硬鏈接!?.. 也是同樣的道理啊。
至此,我們也明白了.和..的本質就是硬鏈接!
但是,在Linux中,我們用戶卻并不可以對目錄進行硬鏈接!但是,系統當中的.和..不就是目錄的硬鏈接文件嗎!這不是只許州官放火,不許百姓點燈嗎?事實就是如此,但是為什么系統不讓用戶自定義硬鏈接文件,卻又自己設計.和..的硬鏈接文件呢?
如果,我們在樹狀的目錄結構當中,其中一個文件對根目錄進行硬鏈接,那這就容易形成路徑換問題。那我們在目錄查找文件時就容易出現問題。而對于.和..硬鏈接文件雖然也形成了路徑換,但是由于名字特殊,做特殊處理即可!.和..存在的意義也是為了用戶方便進行命令操作。
還有個問題,軟鏈接不也形成路徑環了嗎,但是我們要看到改文件的類型是被l標識的!做特殊判斷即可!所以,在Linux中有些東西有其存在的考量和并不存在的理由。