1 認識硬件——磁盤
1.1 物理構成
??磁盤是計算機中唯一的機械設備,同時也是一種外部存儲設備(外設)。早期的計算機通常配備的是機械硬盤(HDD),依靠磁頭和盤片的機械運動來進行數據的讀寫。但隨著用戶對計算機速度要求的不斷提高,如今大多數個人電腦已普遍采用SSD(固態硬盤)。
??相比機械硬盤,SSD沒有機械運動部件,具有讀寫速度快、體積小、低功耗、耐用性好、無噪音等優點,且仍有廣闊的技術發展空間。因此,在桌面電腦領域,SSD幾乎已經全面取代了機械硬盤。
??但在企業級存儲領域,機械硬盤仍被大量使用,主要因為其低成本、大容量的特點,非常適合海量數據的長期存儲,短期內仍難以被完全淘汰。
1.2 磁盤的工作原理及特點
??磁盤的核心工作原理是:二進制序列通過磁頭的充放電過程寫入盤片。任何硬件本質上只能識別二進制信號(高低電平),磁頭通過充電或放電,將電信號轉化為磁信號,改變盤片表面磁性區域的排列,進而完成數據的寫入。
主要特點:
- 計算機內部信息流動通常以電子或光電信號形式高速傳輸,而磁盤依靠機械運動,速度相對較慢。
- 磁盤工作時,盤片高速旋轉,磁頭沿盤面半徑方向微調位置,但始終懸浮于盤面之上,不直接接觸。
- 磁盤制造必須在無塵密封環境中進行,灰塵若進入磁盤,會劃傷盤面,造成數據丟失。
- 內存(RAM)屬于易失性存儲,掉電后數據丟失,而磁盤則是持久性存儲,即使斷電,數據仍可保存。
補充:磁盤是有壽命的,在企業級存儲中,磁盤接近報廢時不會隨意丟棄,因為盤內存有大量敏感數據。磁盤密封性強,不易物理銷毀,因此大公司通常會通過專業安全部門,采用嚴格流程銷毀磁盤,以防數據泄露風險。
1.3 存儲構成
1.3.1 磁盤的基本工作方式
- 磁頭的左右擺動:磁頭臂在磁盤表面徑向移動,定位到特定的磁道(柱面)
- 盤片的旋轉:當磁頭靜止在某個磁道上時,高速旋轉的盤片使目標扇區經過磁頭下方
1.3.2 存儲的最小單位:扇區
??磁盤被訪問的最基本單位是扇區,傳統大小為512字節,現代高級格式磁盤通常采用4KB扇區。基于這一特性,我們可以將磁盤視為由無數個扇區構成的線性存儲介質。
1.3.3 三維定位:如何訪問一個扇區
- 磁頭選擇:確定使用哪個盤面(對于多碟片磁盤)
- 磁道定位:在選定盤面上找到具體的磁道(柱面)
- 扇區識別:在旋轉的磁道上等待目標扇區經過磁頭
1.4 邏輯抽象
1.4.1 磁帶的線性存儲特性
??從物理結構來看,磁帶是一種順序訪問的存儲介質,數據以線性方式排列在磁帶上。如果我們將磁帶邏輯上攤開,可以將其抽象為一個連續的線性存儲空間。這種抽象允許我們使用邏輯塊尋址(LBA, Logical Block Addressing)的方式來組織和管理磁帶上的數據。
1.4.2 邏輯扇區與LBA地址
- 每個邏輯扇區(通常為固定大小,如512字節或4KB)對應磁帶上的一個存儲單元
- 通過LBA地址(即邏輯扇區號,類似于數組下標)唯一標識每個扇區
- 整個磁帶可視為一個由LBA地址索引的大數組,支持隨機訪問的邏輯模型(盡管物理上仍需順序訪問)
問題1:如何通過下標找到我們要寫入的扇區?
假設有以下磁盤參數:
- 每個盤面有2w個扇區
- 每個盤面有50個磁道(50個圈)
- 每個磁道有400個扇區(每一圈分了400份)
以LBA地址28888為例:
- 28888 / 20000 = 1
- 28888 % 20000 = 8888
- 8888 / 400 = 22
- 8888 % 400 = 88
- 第一個磁盤(下標從0開始)的第22個磁道的第88個扇區。
- Cylinder Header Sector ==> CHS尋址方式
- C = 22 ;H = 1;S = 88
問題2:為什么扇區大小不均勻,但 LBA 地址是均勻的?
??因為磁盤的磁道是從中心向外輻射分布的,靠近中心的磁道周長小,外圈磁道周長大。在早期磁盤設計中,為了簡化控制邏輯,通常會采用固定扇區數設計,即每個磁道分配相同數量的扇區。但這樣導致內圈扇區物理尺寸小,外圈扇區物理尺寸大,扇區大小在物理空間上是不均勻的。
??通過調整數據編碼密度(比如讓內圈磁道的數據編碼更稠密,外圈更稀疏),理論上可以讓每個扇區實際存儲的數據量更均衡。現在的磁盤也普遍采用了這種區段分區(Zoned Recording)技術,讓外圈磁道能存儲更多數據,提升存儲效率。
??至于LBA(Logical Block Addressing)地址,它是邏輯地址,按“塊”遞增編號,與物理扇區大小無關,映射層可以屏蔽底層磁道結構差異,保證邏輯地址空間連續、均勻,方便操作系統統一管理磁盤數據。
??當然,如果對物理扇區大小進行調整,底層算法和控制邏輯也需要相應適配,現有磁盤固件已能很好支持這種變化。
1.4.3 回歸硬件
問題:我們究竟是如何和硬件交互的?
??雖然大多數人只接觸操作系統層面的文件操作(如 read / write),但在底層,其實我們與硬件的交互是通過“寄存器”完成的 —— 不止 CPU 有寄存器,所有外設也都有自己的寄存器集,磁盤也不例外。
當 CPU 向磁盤發起一次 I/O 請求時,交互過程大致如下:
- 控制寄存器(Control Register):CPU 通過它告訴磁盤“我要做什么”——是要讀數據,還是要寫數據。也可能指定其他控制位,如啟動位、復位位等。(告訴磁盤是打算讀還是打算寫)
- 數據寄存器(Data Register):用于傳輸數據。如果是寫操作,CPU 會將要寫入的數據寫入磁盤的數據寄存器;如果是讀操作,磁盤會將讀出的數據放入此寄存器,由 CPU 讀取。((告訴磁盤要寫入哪些數據,讀那些數據)
- 地址寄存器(Address Register):用于告訴磁盤“要讀/寫哪一塊數據”,一般是使用邏輯塊地址(LBA)表示。磁盤控制器會把 LBA 轉換為實際的 CHS(柱面-磁頭-扇區)地址,定位到具體的物理位置。
- 狀態/結果寄存器(Status Register):用于反饋 I/O 操作是否成功,或當前磁盤狀態(如是否就緒、是否發生錯誤、空間是否不足等)。CPU 通過輪詢、或中斷機制讀取此寄存器判斷操作結果。
2 文件系統
??通過邏輯抽象,雖然我們可以把磁盤扇區的地址抽象成線性的 LBA 地址,但實際操作系統要處理很多關鍵問題,比如:
- 磁盤有多大?
- 已用了多少扇區?
- 哪些扇區還沒用?
- 哪些扇區存放的是元數據(如文件屬性)?
- 哪些扇區存的是實際內容?
- 新寫入的數據該寫到哪個扇區?
這些問題決定了操作系統必須設計一套機制,把磁盤空間合理地組織和管理起來!
2.1 磁盤分區
??首先,物理磁盤通常會被劃分成多個分區,每個分區就像一個邏輯磁盤,獨立管理。分區的劃分可以靈活配置(如圖所示)。
2.2 文件系統結構
??每個分區內部,通常會被組織成若干 Block Group,而每個 Block Group 里又由多個區域組成,比如:
Boot Block:文件系統中的特殊塊,位于起始位置,存放 Boot Loader 引導信息,作用是啟動操作系統。
Block Group:分區內部會被劃分為一個個 Block,Block 大小在格式化時確定,通常不可修改。
每個 Block Group 典型結構包括:Super Block、Group Descriptor、Block Bitmap、Inode Bitmap、Inode Table、Data Blocks
2.3 inode機制
- inode 存儲文件的全部屬性信息(比如權限、所有者、大小、修改時間),但不存儲文件名!
- 每個文件都有唯一的 inode 號,inode 號是文件的唯一標識。
- 文件的實際內容存放在 Data Block 區域,inode 里會記錄指向這些塊的地址。
- 可以通過
ls -li
命令查看文件的 inode 編號。
2.4 Data Block
Data Block:是文件系統中用于存放文件實際內容的區域。數據以塊(Block)為單位存儲,常見的 Block 大小是 4KB,通常 一個塊只存放自己的數據,不會混合多個文件的數據。
問題:一個文件對應多少個 Data Block?
- 每個文件在文件系統中只有一個 inode,inode 記錄了文件的屬性信息,同時也記錄了 該文件的內容存放在哪些數據塊里。
- 如果文件很大,可能需要占用很多個 Data Block,因此 inode 里需要有“指向數據塊”的信息。
- 具體來說,inode 結構體里有一個 block 數組,用于存儲數據塊的編號(Block Number)。
問題:如果文件非常大,那 block 數組是不是要非常大?
不是的,block 數組通常設計得很有限,里面分為兩種類型的指針:
- 一部分是直接索引(Direct Pointer),直接指向實際的數據塊。
- 還有一部分是間接索引(Indirect Pointer):
- 一級間接索引:指向一個“塊號列表”塊,該塊里存儲更多數據塊的塊號;
- 二級間接索引、三級間接索引也是同理,逐層擴展。
- 通過這種直接+間接索引的設計,文件系統可以用很小的 inode 結構,支持非常大的文件,同時又不浪費空間。
??block 數組并不需要無限增大,而是通過“多級間接索引”機制來高效管理大文件的數據塊映射,兼顧了小文件的訪問效率和大文件的存儲能力!
2.5 Bitmap
??操作系統如何知道磁盤里哪些塊/哪些 inode 被用過、哪些還空閑?這就靠 位圖(Bitmap) 來管理!
- Block Bitmap 記錄著 Data Block 的使用情況:
- 每一個 bit 對應一個 Data Block
- bit=1 表示該塊已被占用
- bit=0 表示該塊空閑,可用
- 通過 Block Bitmap,文件系統可以快速找到空閑的數據塊,分配給新文件或追加的文件內容
- inode Bitmap 記錄著 inode 的使用情況:
- 每一個 bit 對應一個 inode
- bit=1 表示該 inode 已被占用(已有文件或目錄)
- bit=0 表示該 inode 空閑,可用
- 文件系統創建新文件時,先查 inode Bitmap,找到一個空閑 inode,分配給新文件,建立文件屬性信息。
問題1:為什么下載一個文件很久,刪除一個文件卻很快?
??因為 刪除文件時并不會真正“擦除”文件內容,操作系統只是在 Block Bitmap 和 inode Bitmap 里把對應 bit 標記為“空閑”,表示可以被新文件覆蓋即可,整個過程非常快。相反,下載/寫入文件需要實際分配 inode 和數據塊,還要寫入磁盤數據,過程自然更慢。
問題2:誤刪了文件,還能恢復嗎?
??其實誤刪后,文件內容本身還在磁盤上,只是對應的 Bitmap 標記為“可用”了。如果沒有新數據覆蓋,理論上是可以恢復的!
??恢復過程一般是先找到文件的 inode 編號,讀取 inode 里記錄的 block 位置,重新拼接數據。通常需要依靠專業工具或人員,因為一旦新文件寫入,會覆蓋舊數據,恢復難度就變大。
??Block Bitmap 和 inode Bitmap 是文件系統中非常關鍵的機制,通過簡單高效的 bit 標記,幫助操作系統快速管理和分配磁盤空間,也是文件刪除“秒刪”背后的原因!
2.6 GDT (Group Descriptor Table)和超級塊(Super Block)
??在文件系統中,除了我們常說的 inode 和 Data Block,其實還有一些 “元信息” 結構,來描述整個文件系統的組織和狀態,GDT 和超級塊就是最核心的兩部分:
- GDT(Group Descriptor Table,塊組描述符表)
- 該組的數據塊使用情況
- 該組的 inode 使用情況
- 該組內各區域的位置(如 Bitmap、inode 表)
- 可以理解為 “塊組的小管家”,幫助系統快速定位某個組內資源。
- Super Block(超級塊)
- 總共有多少 block / inode
- 當前未用的 block / inode 數量
- block / inode 的大小
- 最近掛載、修改、檢查的時間
- 其他文件系統配置信息
- Super Block 是整個文件系統的“核心配置表”,如果它損壞,整個文件系統就無法正常工作。
超級塊的備份機制
- 雖然 Super Block 是存在塊組內部的,但是它記錄的是整個分區的信息,理論上一個 Super Block 足夠。
- 但因為 Super Block 極為重要,一旦損壞文件系統就崩,所以文件系統一般會在分區內的多個組中存放 Super Block 的備份。
- 這樣即使主 Super Block 損壞,操作系統也可以用備份 Super Block 來恢復文件系統結構。
問題:操作系統怎么找到超級塊?它在那么多塊里怎么定位?
??靠“魔數”,超級塊內部有一個固定偏移位置,存放一個特殊值,叫做 魔數(Magic Number),這個魔數是文件系統格式定義的唯一標識。操作系統讀取磁盤塊時,只要在固定偏移處找到正確的魔數,就知道這個塊是超級塊,從而完成定位。
??GDT 負責管理塊組內的資源,超級塊負責記錄整個文件系統的元信息,魔數幫助操作系統準確定位超級塊,保證文件系統的可靠性和穩定性!
2.7 格式化
??在一個分區使用之前,必須先格式化 ——也就是提前將一部分文件系統的屬性信息(比如超級塊、GDT、Bitmap、inode 表等)寫入分區對應的位置,這樣才能保證后續系統在使用該分區時,知道如何管理磁盤空間,如何存取數據。
??另外,格式化也可以讓磁盤恢復到“未使用”狀態,Bitmap 置零,邏輯上所有空間重新可用,方便新的數據寫入。
3 對目錄的理解
3.1 新建 & 刪除文件,系統背后做了什么
新建文件
- 系統首先會根據路徑信息,定位到對應的分區,讀取該分區的超級塊。
- 超級塊里知道當前 block 總數、inode 總數等基礎信息。
- 系統再通過 GDT 確認該分區中哪些 block group 狀態最好(空間最多、inode 最空閑)。
- 然后通過 inode Bitmap 找到一個空閑 inode,分配給新文件,并填寫文件屬性信息。
- 如果文件需要寫入數據,還會根據內容大小,確定需要多少 數據塊,通過 Block Bitmap 查找空閑塊,把塊號寫入 inode 結構體的 block 數組里,最后把文件內容寫入這些數據塊中。
一句話總結:新建文件,其實就是分配 inode + 分配數據塊 + 填寫屬性 + 寫入數據。
刪除文件
- 系統同樣先根據路徑定位分區,找到對應的 inode。
- 然后修改 inode Bitmap 和 Block Bitmap,把對應位置的 bit 置 0,表示 inode 和數據塊已釋放,可再次使用。
- 文件數據實際上還在磁盤上,直到被新的文件覆蓋。
一句話總結:刪除文件,只是修改 Bitmap 標志位,數據本身不會立刻被清空。
3.2 目錄文件
??在 Linux 文件系統中,目錄也是一種特殊的文件。它也有自己的 inode,也由 屬性 + 內容 兩部分組成。但它的內容部分不是普通數據,而是由“文件名 → inode編號” 的映射關系表組成,起到類似鍵值對的效果。
問題1:為什么一個目錄下不能有同名文件?
??因為在目錄中,文件名是 key,inode 是 value,key 必須唯一。如果有兩個文件名相同,系統將無法通過文件名準確查找 inode,因此這是被禁止的。
問題2:為什么沒有寫權限(w)無法創建文件?
??因為創建文件時,系統要把“文件名 → inode”寫入當前目錄的內容區域。如果目錄本身不可寫,就無法建立這條映射關系,自然就無法創建新文件。
問題3:為什么沒有讀權限(r)無法查看目錄內容?
??因為查看目錄其實是讀取目錄文件的內容,只有具備讀權限,系統才能讀出文件名 → inode 的映射,進而顯示目錄下有哪些文件。沒有讀權限,系統就“看不到”這個映射表,自然就“看不到”目錄下的文件。
問題4:為什么沒有執行權限(x)無法進入目錄?
??因為進入一個目錄本質上是對該目錄執行“路徑解析 + 目錄跳轉”的操作,系統需要“執行”該目錄條目,將其 inode 載入并更新工作目錄。沒有執行權限,系統無法完成這一步,就無法 cd 進入該目錄。
問題5:如何找到目錄本身的 inode?
??目錄文件本身也有 inode,但它不會出現在自己內部的映射里。要找到一個目錄的 inode,需要從它的父目錄讀取其文件名對應的 inode 號,再從父目錄的父目錄……一路遞歸直到根目錄 /,再反向解析回目標目錄路徑。這也是為什么:訪問一個文件必須指定路徑(即目錄樹),因為只有路徑才能層層查找到 inode,進而找到目標文件。
3.3 dentry緩存(擴展)
??在 Linux 文件系統中,訪問一個文件必須先從路徑解析出對應的 inode,而路徑是分層的,也就是:
/home/user/docs/file.txt → 要先找到 / → 然后找 home → 再找 user → 最后找到 file.txt
??而每一級目錄都需要讀取目錄文件,找到對應文件名的 inode,這個過程就涉及到不斷查找、不斷磁盤 IO,如果每次都從磁盤遞歸查找,效率會非常低!
??所以Linux提供了dentry緩存,將常用文件的inode信息緩存起來!
??dentry緩存,簡稱dcache,是Linux為了提高目錄項對象的處理效率而設計的。它是一個slab cache,保存在全局變量dentry_cache中,用于保存目錄項的緩存。dentry結構是一種含有指向父節點和子節點指針的雙向結構,多個這樣的雙向結構構成一個內存里面的樹狀結構,也就是文件系統的目錄結構在內存中的緩存了。
4 軟硬鏈接
4.1 軟鏈接
??軟連接(Symbolic Link),也稱為符號鏈接,本質上是一個獨立的文件,擁有自己的 inode 和數據塊。它的內容并不是實際的數據,而是一個指向目標文件路徑的字符串。可以類比為 Windows 系統中的快捷方式。
??由于軟鏈接只是路徑的引用,如果原始文件被刪除或移動,軟鏈接就會“失效” —— 我們常說的“斷鏈”(broken link),
4.1.1 應用場景舉例
??當一個可執行程序放在了某個路徑很深的目錄中(如 /usr/local/lib/xxx/target/bin/run.sh),每次手動執行很麻煩。我們可以在當前目錄下創建一個指向它的軟鏈接:
ln -s /usr/local/lib/xxx/target/bin/run.sh ./run
??這樣以后只要在當前目錄下執行 ./run 就可以了,可執行文件的位置再深也無所謂,有軟鏈接就能隨時調用,非常適合常用工具的快速訪問。
4.2 硬鏈接
??和軟鏈接不同,硬鏈接不是一個獨立的文件,因為它沒有獨立的 inode。所謂“創建硬鏈接”,本質上就是:
- 在某個目錄的數據塊中新增一個“文件名 → inode編號”的映射關系,也就是說,只是給現有 inode 多取了一個“別名”。
- 多個硬鏈接共享同一個 inode 和同一組數據塊,誰被打開其實都等價于訪問原始文件。
4.2.1 硬鏈接下的引用計數機制
??每個 inode 都有一個引用計數,表示有多少個目錄項指向它。
問題1:為什么一個目錄的引用計數是 2
- 因為一個目錄創建完成后會包含兩個默認的子項:
.
→ 指向自己..
→ 指向父目錄
問題2:當目錄下再新建一個文件,為什么引用計數變為 3
- 因為新建的文件會指向該目錄作為“父目錄”,即該目錄被
..
多引用了一次,所以引用計數加 1。
補充:
- 某個目錄的引用計數 - 2 = 它包含的實際子目錄數量
- 刪除文件時,系統會先刪除該文件名與 inode 的映射,然后 inode 的引用計數減 1,只有當引用計數減為 0,才會真正釋放對應的 inode 和數據塊(即清空位圖)
問題3:為什么 Linux 不允許對目錄建立硬鏈接?
??操作系統禁止用戶對目錄創建硬鏈接,最主要的原因是:防止目錄結構出現“環”!
Linux 的路徑解析是遞歸向上回溯到根目錄,再逐層向下查找回來。如果在中間某一層手動創建了一個指向“上級目錄”的硬鏈接,可能會造成無限回溯,形成死循環,文件系統就崩了。
問題4:但是 .
和 ..
不就是對目錄的硬鏈接嗎?
??.
和 ..
本質上確實是目錄的硬鏈接!
- 但是這兩個特殊鏈接是由操作系統自動創建的,我們無法手動創建;
- 操作系統還強制規定路徑查找時不會對
.
和..
做“循環遍歷”,所以不會構成環; - 它們的存在是為了支持相對路徑的便捷訪問,否則用戶只能用繁瑣的絕對路徑定位文件。
硬鏈接應用場景:通常用來做路徑定位!!可以通過硬鏈接進行目錄切換!(不常用)