🔥 本文專欄:Linux
🌸作者主頁:努力努力再努力wz
💪 今日博客勵志語錄:
世界上只有一種個人英雄主義,那么就是面對生活的種種失敗卻依然熱愛著生活
內容回顧
那么在之前的學習中,我們知道了操作系統是如何管理我們未打開的文件,我們知道文件的數據是存儲在磁盤上的,而操作系統要管理磁盤上的所有數據,那么必然在操作系統中建立一個磁盤的邏輯映射,那么其中磁盤中的數據是保存在盤面上的一圈一圈的磁道的扇區當中,那么我們可以將該立體空間中每一個扇區按照一個線性的一維數組來進行一個排列,那么該一維數組就是磁盤的邏輯結構,其中該一維數組中的每一個元素就是一個扇區,其中為了區分不同位置的扇區,那么我們就得給扇區一個唯一的標識符,那么我們扇區在一維數組對應的數組下標便是扇區的編號從0到n分配給不同位置的扇區,而該編號也稱之為LAB地址,那么當我們有了扇區的LAB地址之后,由于LAB地址是邏輯地址而不是扇區在實際的磁盤中的物理地址,而我們需要定位扇區在磁盤中真實的物理地址,而定位其在磁盤中的位置則需要該位置對應的磁頭以及磁道和扇區這三個坐標,所以有了LAB地址之后,操作系統會采取特定的算法將LAB地址映射轉換該位置的三個坐標即可
那么扇區是我們磁盤存儲的基本單元,那么磁盤讀寫數據是以扇區為單位來進行讀寫,但是由于扇區的存儲的內容量一般只有512字節,而現在的文件的大小動輒就是幾十甚至上百個GB,而磁盤讀取的速度取決于機器運動的次數,那么以扇區為單位讀寫必然io的速度就會很慢,所以不同的文件系統會采取將多個扇區給組合形成該磁盤讀寫的最小單元,以EXT4文件系統為例,那么它是以8個扇區為一組作為一個邏輯塊,那么磁盤一次讀寫的數據量就是4KB,那么這樣就提高了io的效率,所以在文件系統所定義的邏輯塊的視角下,那么原本的一維線性數組的每一個元素便不再是一個個扇區而是邏輯塊,所以給一維數組的元素個數以及每個元素的編號就會發生變化
其中由于磁盤的數據量很大,那么操作系統管理如此龐大的數據,那么采取的策略就是分區,其中將該一維線性數組分成不同的區域來進行管理,但是即使將其分區,那么每一個區的數據量依然很大,所以會進行更為細致的劃分,將每一個分區在劃分成不同的分組,那么其中管理好整個磁盤就是管理好該分區的每一個分組以及不同的分區,那么這就是對前置知識的回顧,如果感到陌生或者說好奇其中的細節的話,可以閱讀之前的文章
★★★ 本文前置知識:
文件系統
重新理解文件
那么了解了磁盤的一個邏輯結構以及操作系統管理磁盤的一個方式之后,那么我們知道文件是由兩部分所構成,分別是文件的屬性以及文件的內容,那么對于文件的屬性來說,操作系統則是會為其定義一個inode結構體,其中封裝了該文件的各個屬性字段其中就包括文件的權限以及文件的創建時間以及最近修改時間,那么inode數據則是保存在inode區域中的特定的邏輯塊中,而對于文件的內容,那么保存文件內容的邏輯塊則是和保存文件屬性的邏輯塊位于不同的區域,所以在Linux下,文件的屬性以及內容是分開來管理,而其中對于inode結構體中還有一個非常重要的字段便是一個索引數組,那么該數組則是保存了該文件的內容的數據塊的索引,那么意味著有了inode結構體,那么我們既能獲取文件的屬性也同時能夠獲取文件相關聯的數據塊
所以現在我們就便能理解當我們創建以及一個文件時,系統會做什么
創建文件
那么文件是有內容+屬性這兩部分構成,當我們創建一個文件時,那么意味著首先就得定義該文件對應的inode結構體,而既然要創建inode結構體,那么我們就得為該文件分配一個空閑的邏輯塊來保存該inode結構體的數據,所以系統會到分區的各個分組中,首先會查看該分組的gbt字段,因為gbt字段保存了該分組的整體的邏輯塊的使用情況,其中記錄了該文件的多少inode邏輯塊被使用,多少inode邏輯塊是剩余空閑,那么接著再去查看inode表,那么inode表是一個位圖結構,其中每一個比特位對應特定位置編號的邏輯塊,那么該比特位的值就表示該邏輯塊是否被使用,那么為inode分配好一個邏輯塊之后后,接下來就是對inode結構體的相關屬性進行一個初始化,那么這就是系統創建文件的一個過程
刪除文件
那么文件是由屬性和內容兩部分構成,那么刪除一個文件必然就是要刪除這兩部分數據,而我們知道文件本質就是由保存屬性的inode結構體以及保存文件的內容的數據塊所構成,而我們的inode結構體中內部有一個索引數組能夠找到該文件關聯的數據塊,那么也就是意味著刪除一個文件,我們只需要找到該文件的inode結構體,那么即可獲取該文件的全部內容,而獲取文件的inode結構體的方式那么就是得需要知道保存該inode結構體的數據的邏輯塊的編號,所以一旦獲取到inode編號之后,那么我們根據該編號確定該inode對應的邏輯塊是在哪一個分組中,然后該分組的塊表則記錄那些邏輯塊被使用哪些邏輯塊未被使用,而inode表則是記錄了哪些inode結構體被使用哪些未被使用,所以我們獲取到inode結構體的編號之后,那么意味著也能同時獲取到數據塊的編號,接著就只需要將塊表以及inode表中對應位置的比特位設置為0即可,無需要覆蓋inode塊以及數據塊的內容,然后更新gbt,這樣就邏輯上完成了對文件的刪除
所以刪除一個文件,意味著會更新相應的塊表以及inode表等屬性,而不會直接采取覆蓋數據塊以及對應的inode塊,那么也就意味著其實刪除一個文件本質上是可以恢復的,但是恢復的過程其實比較復雜還要涉及到專業的工具,并且文件系統的格式化其實也就是在重新初始化對應的分區的每個分組的塊表以及inode表等屬性,也不會覆蓋所謂的數據塊以及inode塊
那么對于其中的文件的刪除,過程道理想必大家都懂,那么關鍵是我怎么獲取目標文件的inode編號呢,我們在Linux上刪除文件,我們知道是輸入rm指令,而其中我們輸入rm指令刪除目標文件都是后面直接輸入的是刪除目標文件的文件名,而不是輸入的是其inode編號,但是系統也確實成功刪除了目標文件,那么這又是怎么回事呢?
那么我們刪除一個文件核心肯定是需要文件的inode編號,那么既然我們只需要輸入文件名就可以達到刪除的效果,那么只能說明一點,那么就是系統有我們該文件名到inode編號的映射,那么要說清楚這點,那么就得重新來認識一下我們的目錄了
目錄文件
那么我們之前在Linux的學習中,我們知道可以輸入mkdir指令來創建一個目錄,那么我們也知道目錄本質上其實也是一個文件,有著自己的屬性,那么既然是一個文件,那么不用說,它肯定也有一個inode結構體在其中的一個特定的分組當中,那么它的inode結構體肯定也記錄其相應的屬性比如權限以及創建時間等,那么同理它也一定有一個索引數組指向其關聯的數據塊,那么我們對于普通文件來說,其inode關聯的數據塊就是其文件內容,那么對于我們目錄文件來說,它也有自己所屬的數據塊,那么對于目錄文件來說,它的數據塊保存的是什么內容呢?
答案就是它的數據塊保存的就是其目錄當中子目錄以及子文件的文件名到inode編號的映射,那么每一個數據塊也就是目錄項保存的都是這個key-value模型的一個映射關系的內容,那么也就是說,我們查找一個目標文件的inode編號,那么就需要到目標文件所處的目錄中的目錄項中去匹配找到對應的映射關系,獲取到其對應的inode編號,所以這就是我們為什么同一個目錄下,不能有重名文件的文件的原因
所以我們有了目錄文件的概念之后,那么我們就得對之前上文所說的創建文件以及刪除文件的過程進行一個完善,那么對于其中創建文件,我們知道會為該文件定義一個inode結構體,然后為在分組中為其分配一個未被使用的邏輯塊來保存inode的數據,而其中對于其所處的目錄文件中,那么目錄文件的數據塊也就是目錄項中也會添加該文件關于文件名到其文件編號的映射
同理對于刪除文件來說,那么首先我們得獲取該文件的inode編號,那么我們就得從其所處的目錄中的目錄項中找到其對應的inode編號,也就是要獲得其所處目錄的目錄項里面的內容,那么我們也得遞歸的去該目錄的上一級目錄中得到該目錄的inode編號,而我們的文件是以樹狀的數據結構來組織的,那么其中整個文件系統的根節點便是根目錄,而其inode編號是已知的,例如在EXT4文件系統中它對應的inode編號是2,那么我們要得到目標文件的文件編號,我們只需要得到該目錄的絕對路徑,也就是從根目錄到該目標文件的路徑,那么系統就會從根目錄往下逐層解析,從根目錄開始,掃描其目錄項中尋找下一級目錄的映射關系獲取到其inode編號,那么再同理遞歸到下一級的目錄當中掃描其目錄項獲取其下一級的目錄或者文件的inode編號直到達到目標文件
那么由此便能解釋之前的問題,為什么我們rm指令輸入目標文件的文件名而不是inode編號,也能夠刪除目標文件
那么如果我們輸入刪除的目標文件帶有絕對路徑,那么就是按照上述過程,解析該絕對路徑找到目標文件的inode編號然后刪除
但是如果是相對路徑的話,那么系統會獲取到該進程的內核的環境變量中的CWD字段,那么該字段保存了所處的工作目錄的inode編號,那么從該目錄文件的目錄項找到目標文件的inode編號從而刪除
而至于進程的內核的環境變量中的CWD字段為什么能夠直接獲取到所處的目錄的inode編號,那么則和dentry緩存有關,因為我們知道從根目錄開始解析到目標文件,這其中的過程要涉及掃描每一個目錄的目錄項的映射關系,那么時間代價就很大,所以dentry就是一個數據結構記錄了每一級目錄的inode編號,那么我們進程切換目錄的時候會利用緩存來得到當前目錄的inode編號從而更新CWD,而無需解析整個路徑
軟鏈接
那么有了目錄以及目錄項的概念之后,那么我們其中便可以引入軟鏈接
那么第一個問題:
軟鏈接是什么
那么我們在解釋其原理之前,我們先來看看軟鏈接長什么樣子,那么我們可以輸入該指令來創建軟鏈接一個指向test.c文件的名為soft的軟鏈接
ln -s [目標文件或目錄] [軟鏈接名稱]
那么創建完之后軟鏈接之后,我們再來使用ls -l 指令來查看一下當前目錄下的所有子目錄以及文件的屬性,
我們發現創建的軟鏈接本質上其實也就是一個文件,那么既然它是一個文件,那么它肯定就由文件的內容和屬性所構成,也就意味著其一定有對應的inode結構體,那么我們可以輸入ls -li查詢inode編號
根據結果我們發現其軟鏈接以及該軟鏈接指向的目標文件test.c的inode編號是不同的,那么說明其有自己獨立的inode結構體以及其相關聯的數據塊,那么其獨立的inode結構體肯定存儲其相關的屬性,那么其相關聯的數據塊那么存儲的是什么內容呢?
那么我們可以輸入cat指令來打印器軟鏈接的文件內容到終端上
test.c文件內容:
cat內容:
那么我們發現軟鏈接打印的內容竟然就是其指向的目標文件的內容
軟鏈接怎么做到的
那么我們軟連接本身有一個獨立的inode結構體以及相關聯的數據塊,軟連接本身的數據塊存儲的內容便是目標文件的路徑,所以當我們輸入cat指令來訪問到軟鏈接的時候,那么首先會得到軟鏈接的inode編號并且識別到該文件的類型,然后從inode編號中獲取其關聯的數據塊,而其數據塊存儲的內容便是其目標文件的路徑,那么接著系統會解析這個路徑得到目標文件的inode結構體從而間接訪問到目標文件的數據塊,然后打印的是指向的文件的文件內容,所以我們的軟鏈接和我們c語言的指針其實非常的像,那么指針的本質其實就是一個變量,只不過該變量的內容就是指向的目標數據的地址,而同理我們的軟鏈接,也是一個文件,只不過內容保存的是指向的目標文件的路徑
注:軟鏈接指向的目標文件一定要存在,如果不存在,那么我們軟連接保存的內容是無效的,那么此時該軟鏈接的狀態就是懸浮的就類似于我們指針不能為空,不然解引用就會出錯
軟鏈接的應用場景
那么在我們Windows下我們的一個可執行文件的成功執行需要編譯各種配置的源文件,那么可執行文件和其源文件會封裝到一個文件夾在特定的盤的特定路徑下保存,那么我們運行這個可執行文件,那么我們就得知道其路徑,但是我們用戶通常不需要記住每一個可執行文件它保存的路徑在哪里,而是通過桌面的快捷方式點開即可運行,那么其實這個快捷方式本質上就是一個軟鏈接,那么它內部記錄了可執行文件的路徑,那么打開該快捷方式其實本質上就是解析其路徑然后運行目標的可執行文件即可
所以學習了Linux之前,相信大家都有過這么的經歷,那么就是刪除一個文件,很多人以為我將快捷方式放到回收站刪除即可那么現在我們知道了,你刪除的快捷方式其實本質上是刪除了一個軟鏈接,那么該程序的可執行文件以及源文件其實沒有任何影響
硬鏈接
那么有軟連接,我們Linux還有硬鏈接的存在,那么它的作用其實和軟連接是差不多的
那么我們先來看看在Linux下我們是如何創建我們的硬鏈接:
ln 目標文件 硬鏈接
硬鏈接是什么
我們在認識硬鏈接是什么之前,我們還是先來看一下硬鏈接長什么樣子吧,那么我們假設在當前目錄下創建一個test.c文件,然后創建一個名為hard的硬鏈接來指向該test.c文件,然后首先我們還是輸入ls -l指令來查看我們當前目錄下的子目錄以及文件屬性,我們發現當前目錄下有我們的硬鏈接文件以及指向的目標文件test.c
然后我們再來輸入ls -li來查看該該目錄下的文件的inode編號,我們發現硬鏈接文件以及指向的目標文件的inode編號是相同的
下一步我們再來輸入cat指令來打印我們的硬鏈接的文件的內容時
test.c內容:
cat 硬鏈接:
我們發現其硬鏈接打印的內容竟然還是指向的目標文件test.c的文件內容
硬鏈接怎么做到的
那么我們知道硬鏈接文件是和其指向的目標文件的inode編號相同,那么說明其共享一個inode結構體,并其硬鏈接和指向的目標文件是共享inode結構體和其關聯的數據塊,那么當我們創建一個硬鏈接的時候,那么會在該硬鏈接所處的目錄中添加一個新的映射,也就是該硬鏈接的文件名到inode編號的映射,所以當我們訪問該硬鏈接的時候,那么其實本質上就是直接訪問了其指向的目標文件的inode結構體,所以打印的內容就是目標文件的內容
并且有了硬鏈接的概念之后,那么我們輸入ls -l指令所展示的文件的屬性中,其中擁有者以及所屬組和其他后面那個數字便是該文件的硬鏈接數,而我們創建一個普通文件,那么該文件的硬鏈接數初始化是1,因為它所處的目錄文件的目錄項會指向它,而對于目錄文件來說,則是2,是因為它所處的目錄文件會有一個指向其子目錄的目錄項,并且對于該目錄的目錄項來說,那么它也有一個目錄項,也就是文件名為".“指向自己的inode的映射,同時還有一個文件名為”…“指向其上級目錄的映射,而對于根目錄來說,其”.“與”…"都指向自己
注:一般不建議創建指向目錄的硬鏈接,因為我們知道了目錄的目錄項中有自己以及所處子目錄的引用,而如果你在當前所處的目錄下創建一個比如其上級目錄的應用,那么我們在解析路徑的時候就會陷入循環導致崩潰,所以硬鏈接使用的很少,一般都選擇軟鏈接
所以當我們刪除一個文件的時候,我們要確定是否徹底刪除清理該文件的屬性以及內容的數據的時候,我們系統其實首先會得到其文件的硬鏈接數,然后減一,如果該硬鏈接數不為0的話,那么意味著還有其指向該文件的inode結構體,所以不會清空該文件的inode結構體以及內容數據,但是如果為0,那么該文件不再被需要,那么則會刪除該文件的inode結構體以及其相關聯的數據塊
結語
那么這就是本篇文章關于文件系統以及軟硬件鏈接的全部內容了,那么這篇文章也就是我們Linux文件系統的收官了,那么文件系統也就此完結撒花告一段落啦,那么恭喜你看到這里,成功的翻閱了文件系統這道大山🎆🎆,當然,對于我這幾篇文章來說,肯定是不可能全部覆蓋到所有的Linux文件系統的知識,只是覆蓋了大部分并且其中最高頻的知識,那么其中文件系統跟Linux其他的內容比如進程之間的聯系,那么又是說來又是話長,那么我也考慮要不要出一期文章來解析,總之感謝你的耐心觀看!
那么我下一期的文章便是動靜態庫的實現,那么我會持續更新,希望你能夠多多關注支出,如果本篇文章有幫組到你的話,那么還請你多多三連加關注哦,你的支持就是我創作的最大的動力!