Linux磁盤級文件/文件系統理解
1. 磁盤的物理結構
磁盤的核心是一個利用磁性介質和機械運動進行數據讀寫的、非易失性的存儲設備。
1.1 盤片
盤片是傳統機械硬盤中最核心的部件,它是數據存儲的物理載體。盤片是一個堅硬的、表面極度光滑的圓形碟片,被安裝在硬盤內部的主軸上。多個盤片會疊放在一起,共同繞主軸旋轉。
盤片的上下兩個表面都涂有一層非常薄的磁性材料,這層涂層是數據真正被記錄的地方。數據就通過改變這層材料上微小區域的磁場方向來記錄(代表 0 和 1)。
1.2 主軸(馬達)
主軸用于固定并旋轉所有盤片。轉速越高,盤片轉動越快,磁頭就能越快地訪問到目標數據,讀寫速度也越快,但同時功耗、發熱和噪音也更大。
1.3 磁道(Track)
盤片旋轉時,磁頭會保持在一個固定位置,這樣就會在盤片上畫出一個圓形路徑,這個圓形就稱為磁道。整個磁盤是由一圈一圈的磁道組成的。
1.4 磁頭(Head)
負責讀取和寫入數據的部件。每個盤片的上下表面都對應一個獨立的磁頭。
在讀寫操作時,磁頭會懸浮在盤片表面上方一個極小的距離(現代硬盤這個距離只有幾納米,比灰塵還小得多)。它永遠不會接觸盤片表面,否則會導致磁頭碰撞(Head Crash),劃傷磁性涂層,造成數據丟失。
所有磁頭臂都連接在同一個 傳動器 上,因此所有磁頭總是同步移動。
1.5 柱面(Cylinder)
所有盤片上半徑相同的磁道組成了一個柱面。因為所有磁頭是同步移動的,當第一個盤片的第N磁道被訪問時,其他盤片上的第N磁道也可以被同時訪問。柱面是操作系統進行磁盤分區和空間分配時的一個重要邏輯概念。
1.6 扇區(Sector)
將一條磁道劃分成若干個弧段,每一個弧段就稱為一個扇區。它是磁盤進行數據讀寫的最小物理單位。傳統硬盤的一個扇區大小是512字節,而現代高級格式硬盤(Advanced Format)則采用4096字節(4K) 為一個扇區。
1.7 塊(Block)
這是操作系統層面的概念。操作系統為了管理方便,會將一個或多個連續的扇區組合起來(通常為8個扇區4KB),塊(在Linux/Ext文件系統中)。它是操作系統進行文件存儲和讀寫的最小邏輯單位。
1.8 外殼
硬盤的所有精密組件都被密封在一個金屬外殼中。外殼內部是無塵的,任何微小的灰塵都可能損壞盤片和磁頭。
1.9 圖示
1.10 CHS尋址
-
扇區是從磁盤讀出和寫?信息的最?單位,通常??為 512 字節。
-
磁頭(head)數:每個盤??般有上下兩?,分別對應1個磁頭,共2個磁頭。
-
磁道(track)數:磁道是從盤?外圈往內圈編號0磁道,1磁道…,靠近主軸的同?圓?于停靠磁頭,不存儲數據。
-
柱?(cylinder)數:磁道構成柱?,數量上等同于磁道個數。
-
扇區(sector)數:每個磁道都被切分成很多扇形區域,每道的扇區數量相同。
-
圓盤(platter)數:就是盤?的數量。
-
磁盤容量 = 磁頭數 × 磁道(柱?)數 × 每道扇區數 × 每扇區字節數。
CHS是三個參數的縮寫,代表了一個扇區在磁盤上的物理位置:
-
C - Cylinder(柱面):所有盤片上半徑相同的磁道組成的集合。可以想象為以主軸為圓心的一系列同心圓柱面。柱面號確定了磁頭臂的徑向位置。
-
H - Head(磁頭):指定哪個磁頭來讀寫。因為每個盤面都有一個磁頭,所以磁頭號實際上就指定了要使用哪一個盤面。
-
S - Sector(扇區):在確定的磁道上,指定從哪個扇區開始讀寫。扇區是磁盤最小的物理存儲單元(通常為512字節)。
CHS尋址 就是通過給出 (C, H, S) 這三個坐標值,來唯一確定硬盤上的一個扇區。
2. Linux文件系統
2.1 概念
首先,傳動臂上的磁頭是共進退的,柱面是一個邏輯上的概念,本質是每一面,相同半徑的磁道邏輯上構成柱面,邏輯上,磁盤是由柱面卷起來的。
-
某一盤面上的磁道按扇區展開(一維數組)。
-
整個磁盤所有盤面的同一個磁道,即柱面展開(二維數組)。
- 柱面上的每個磁道,扇區數是一樣的。
-
一個磁盤是由N個柱面組成的(三維數組)。
-
三維數組本質也是一維數組,所以每一個扇區都有一個對應的數組下標,稱為LBA(Logical Block Address),所以在操作系統文件系統概念中只有LBA地址,LBA地址向CHS地址的轉換由硬件自己完成。
-
所以磁盤邏輯結構是由一個一個柱面展開并連接在一起的,形成一個一維數組。
2.2 CHS和LBA地址
2.2.1 CHS轉LBA
- LBA=柱?號C?單個柱?的扇區總數+磁頭號H?每磁道扇區數+扇區號S?1LBA = 柱?號C*單個柱?的扇區總數 + 磁頭號H*每磁道扇區數 + 扇區號S - 1LBA=柱?號C?單個柱?的扇區總數+磁頭號H?每磁道扇區數+扇區號S?1
2.2.2 LBA轉CHS
-
柱?號C=LBA//(磁頭數?每磁道扇區數)【就是單個柱?的扇區總數】柱?號C = LBA // (磁頭數*每磁道扇區數)【就是單個柱?的扇區總數】柱?號C=LBA//(磁頭數?每磁道扇區數)【就是單個柱?的扇區總數】
-
磁頭號H=(LBA%(磁頭數?每磁道扇區數))//每磁道扇區數磁頭號H = (LBA \% (磁頭數*每磁道扇區數)) // 每磁道扇區數磁頭號H=(LBA%(磁頭數?每磁道扇區數))//每磁道扇區數
-
扇區號S=(LBA%每磁道扇區數)+1扇區號S = (LBA \% 每磁道扇區數) + 1扇區號S=(LBA%每磁道扇區數)+1
//: 表?除取整。
LBA地址是從0開始,CHS地址是從1開始。
2.3 分塊
操作系統為了管理方便,會將一個或多個連續的扇區組合起來(通常為8個扇區4KB)
為什么要分塊?
-
減少管理開銷。
-
一次性讀寫一個較大的塊,比多次讀寫小單元更能減少IO次數。
2.4 分區
文件系統分區是指將物理存儲設備(如硬盤、SSD)劃分為多個獨立的、邏輯上隔離的區塊,每個區塊單獨格式化并掛載到 Linux 目錄樹的特定位置(如/
、/home
),從而實現對存儲資源的有序管理。
為什么要分區?
- 將存儲資源拆分,降低管理復雜度。
2.5 分組
2.5.1 為什么要分組?
想象一下,如果沒有分組,整個硬盤就像一個巨大的、沒有分隔的房間。所有文件的數據塊(data blocks)和文件屬性集合(inodes)都混雜在一起。磁頭需要在整個盤片上來回大幅度移動來讀寫文件,這非常低效(產生大量尋道時間),并且容易產生碎片。
因此,磁盤被先分區,再分組,每個組相對獨立,擁有自己的內部結構。管理的精髓在于可復制性。成功管理一個組,便掌握了管理所有組的核心能力;能管理好所有組,便意味著能管理好一個分區;
2.5.2 理解inode
-
Linux下文件的存儲是屬性和內容分離的。
-
Linux下,保存文件屬性的集合叫做inode,一個文件,一個inode,通過inode編號唯一標識。
一個 inode 主要包含以下元信息,也就是文件屬性(可以使用 stat
filename 命令查看):
-
inode 編號:每個 inode 在它所在的文件系統中都有唯一的編號。
-
文件數據的磁盤塊地址:指向存儲文件內容的那些數據塊(Data Blocks)的指針。這是 inode 最關鍵的作用。
-
文件大小:字節數。
-
文件的擁有者 User ID (UID):哪個用戶擁有這個文件。
-
文件的所屬組 Group ID (GID):哪個用戶組擁有這個文件。
- 文件的訪問權限:讀、寫、執行的權限(rwx for user, group, others)。
-
文件的時間戳:
-
訪問時間 (Access):最后一次讀取文件內容的時間。
-
修改時間 (Modify):最后一次修改文件內容的時間。
-
改變時間 (Change):最后一次改變文件屬性(如權限、所有者) 的時間。
-
-
鏈接數:有多少個文件名指向這個 inode(后面硬鏈接會講到,可以理解為引用計數)。
-
其他信息:如設備文件(/dev/)的主設備號和次設備號等。
特別注意:
-
inode 里面唯獨不包含文件的名稱! 文件名是存放在目錄文件里的。
-
任何文件的內容大小可以不同,但是屬性大小一定是相同的(通常是128或256字節)。
2.5.3 目錄和文件名在哪里?
理解了 inode 不存文件名后,自然會有這個疑問。答案是:在目錄里。
目錄也是文件,但是磁盤上沒有目錄的概念,只有文件屬性 + 文件內容的概念。
目錄(Directory)本身也是一個文件,它也有自己的 inode 和數據塊。它的數據塊里的內容非常簡單,就是一個 文件名 和 inode 映射表:
文件名 (Filename) | inode 編號 (inode number) |
---|---|
report.txt | 256790 |
cat.jpg | 256791 |
music.mp3 | 256792 |
所以,訪問文件,必須打開當前目錄,根據文件名,獲取對應的inode號,然后進行文件的訪問。
當你執行 ls -l
時,系統是這樣做的:
-
讀取當前目錄文件的內容,得到一系列
文件名
和對應的inode 號
。 -
根據每個
inode 號
去找到文件的inode
信息。 -
從
inode
信息中讀出文件的權限、所有者、大小等,并和文件名一起顯示給你。
2.5.4 Linux路徑解析
當你訪問 /home/user/report.txt
時,系統會:
-
在根目錄
/
的映射表里找到home
對應的 inode 號。 -
根據 home 文件名的 inode 號找到
/home
目錄的數據塊,在里面找到user
文件名對應的 inode 號。 -
再根據 user 文件名的 inode 號找到
/home/user
目錄的數據塊,在里面找到report.txt
文件名對應的 inode 號(比如 256790)。 -
最后,根據 inode 號 256790 找到文件的 inode 信息,再根據 inode 信息里的指針找到文件的數據塊,讀取內容。
2.5.5 Linux dentry(歷史路徑緩存)
這是解決路徑解析性能問題的最關鍵武器。
-
dentry
(directory entry)是內核在內存中構建的一個數據結構,用于表示路徑中的一個組成部分(如home
,user
,report.txt
)。 -
它建立了文件名到 inode 的映射關系。
-
它本身不存儲文件數據,甚至不存儲完整的元數據,只是一個快速的文件名 -> inode 的查找結構。
-
每個文件都要有對應的dentry結構,在內存中形成整個樹形結構。
-
整個樹形節點也同時會?屬于LRU(Least Recently Used,最近最少使?)結構中,進?節點淘汰。
-
整個樹形節點也同時會?屬于Hash,?便快速查找。
擴展:樹的構建過程(以 /home/alice/file.txt
為例)
讓我們看看這棵樹是如何一步步在內存中建立起來的。假設系統啟動后,首次訪問這個路徑。
第0步:樹的根
內核啟動后,會為根目錄 /
創建一個 dentry 對象(我們稱之為 dentry_root
)。它是這棵樹的根,它的 d_parent
指向它自己。
第1步:解析 /home
-
進程請求打開
/home/alice/file.txt
。 -
解析器從根
dentry_root
開始。 -
它查看
dentry_root
的 inode,并讀取/
目錄的內容(從磁盤或緩存)。 -
它在目錄內容中查找字符串
"home"
。 -
找到后,內核為
home
創建一個新的 dentry 對象(dentry_home
)。-
設置
dentry_home->d_parent = dentry_root
(home
的父目錄是/
)。 -
將
dentry_home
添加到dentry_root
的子目錄鏈表(d_subdirs
)中。 -
將
"home"
這個名稱和dentry_home
的映射關系,存入一個高效的哈希表中以便快速查找。
-
-
現在樹的結構是:
dentry_root(name:/) -> dentry_home(name:home)
第2步:解析 alice
-
解析器現在位于
dentry_home
。 -
它查看
dentry_home
的 inode,并讀取/home
目錄的內容。 -
在內容中查找字符串
"alice"
。 -
找到后,內核為
alice
創建一個新的 dentry 對象(dentry_alice
)。-
設置
dentry_alice->d_parent = dentry_home
(alice
的父目錄是home
)。 -
將
dentry_alice
添加到dentry_home
的子目錄鏈表中。 -
在哈希表中記錄
"alice"
到dentry_alice
的映射。
-
-
現在的樹結構是:
dentry_root(name:/) -> dentry_home(name:home) -> dentry_alice(name:alice)
第3步:解析 file.txt
-
解析器現在位于
dentry_alice
。 -
它讀取
/home/alice
目錄的內容。 -
在內容中查找字符串
"file.txt"
。 -
找到后,內核為
file.txt
創建一個新的 dentry 對象(dentry_file
)。-
設置
dentry_file->d_parent = dentry_alice
(file.txt
的父目錄是alice
)。 -
將
dentry_file
添加到dentry_alice
的子目錄鏈表中。 -
在哈希表中記錄
"file.txt"
到dentry_file
的映射。
-
-
最終的樹結構形成:
dentry_root(name:/) -> dentry_home(name:home) -> dentry_alice(name:alice) -> dentry_file(name:file.txt)
樹的查詢過程(第二次訪問)
現在,另一個進程請求打開 /home/alice/file.txt
。此時,完整的路徑樹已經在 dentry cache 中存在。
解析過程不再是昂貴的磁盤I/O遍歷,而是變成了高效的內存樹遍歷:
-
解析器從
dentry_root
開始。 -
它不會去讀磁盤上的
/
目錄,而是直接查看dentry_root
的子目錄鏈表(或者更常見的是,直接查詢哈希表:“home”
對應的 dentry 是什么?)。 -
哈希表查找:內核使用一個全局的哈希表。它計算字符串
"home"
的哈希值,并在哈希桶中找到dentry_home
。命中! -
解析器跳到
dentry_home
。 -
同樣,它計算
“alice”
的哈希值,在哈希表中查找,直接找到dentry_alice
。再次命中! -
解析器跳到
dentry_alice
。 -
計算
“file.txt”
的哈希值,在哈希表中找到dentry_file
。最終命中! -
通過
dentry_file
找到對應的 inode,完成路徑解析。
2.5.4 為什么需要 inode?/ inode 的好處?
-
解耦文件名與數據:
- 使用 inode 的核心目的是將文件的元數據與文件的數據本身分離開來。這種設計帶來了巨大的靈活性和強大的功能。想象一下如果沒有 inode,文件名、權限、數據位置等信息都必須和文件數據緊耦合地放在一起,會非常難以管理。
- 硬鏈接(Hard Link):創建硬鏈接就是在另一個目錄里增加一個不同的文件名,但指向同一個 inode 號。兩個文件名完全平等,刪除其中一個,另一個依然能訪問數據(只要 inode 的鏈接數不為 0,本質上就是引用計數)。
-
權限和訪問控制:所有權限信息都存儲在 inode 里,與文件名分離,安全且統一。
-
高效遍歷:系統通過 inode 號來查找文件,比通過冗長的路徑名查找要快得多。
2.6 文件系統塊組的內部構成
這是文件系統設計中最精妙的部分。Linux文件系統將整個分區劃分為多個塊組 (Block Groups)。每個塊組是自包含的,擁有自己管理文件和數據所需的元數據。
2.6.1 超級塊(Super Block)
存放?件系統本?的結構信息,描述整個分區的?件系統信息。記錄的信息主要有:Data Bolcks 和 inode 的總量,未使?的 Data Blocks 和 inode 的數量(標識的是整個分區的),?個 Data Blocks 和 inode 的??,最近?次掛載的時間,最近?次寫?數據的時間,最近?次檢驗磁盤的時間等其他?件系統的相關信息。Super Block的信息被破壞,可以說整個?件系統結構就被破壞了。
并非每個組中都存在一個完整的超級塊,但是會在多個塊組中備份,為了保證因為硬件出現物理問題時,導致的 Super Block 損壞,還能繼續正常使用。
2.6.2 塊組(Group Descriptor Table)
塊組描述符表,描述塊組屬性信息,整個分區分成多個塊組就對應有多少個塊組描述符。每個塊組描述符存儲?個塊組的描述信息,如在這個塊組中從哪?開始是inode Table,從哪?開始是Data Blocks,空閑的inode和數據塊還有多少個等等(標識的是當前這組的)。塊組描述符在每個塊組的開頭都有?份拷?。
2.6.3 塊位圖(Block Bitmap)
Block Bitmap中每一位(bit)對應本組內的一個數據塊(Data Blocks)。1
表示該塊已分配使用,0
表示空閑。
2.6.4 inode位圖(inode Bitmap)
inode Bitmap中每一位(bit)對應本組內的一個inode。
2.6.5 inode表(inode Table)
存放?件屬性 如 ?件??,所有者,最近修改時間和當前分組所有inode屬性的集合。inode編號以分區為單位,不可跨分區。
-
最重要的:指向文件數據塊的指針。
-
12直接塊指針:直接指向12個數據塊(12?4KB12 * 4KB12?4KB),適合小文件。
-
3間接指針:指向一個塊,該塊本身不存數據,而是存儲指向數據塊的指針。
-
一級間接塊索引指針:1024?4KB1024 * 4KB1024?4KB
-
二級間接塊索引指針:1024?1024?4KB1024 * 1024 * 4KB1024?1024?4KB
-
三級間接塊索引指針:1024?1024?1024?4KB1024 * 1024 * 1024 * 4KB1024?1024?1024?4KB
-
-
以上按照一個塊4KB,指針大小4Byte計算的。
2.6.6 數據塊(Data Blocks)
真正存儲文件內容和目錄結構的地方。目錄文件的數據塊里并不直接存儲文件,而是存儲一個類似<inode編號> <文件名>
的列表(映射關系)。這就實現了文件名與文件本身的解耦。
Data Blocks編號以分區為單位,不可跨分區。
2.7 掛載分區
假設你有一塊新硬盤,分了區(比如 sdb1
),并格式化了文件系統(比如 EXT4)。現在你想用它來存放你的文件。
創建掛載點:你需要一“入口目錄。通常我們會在 /mnt
或 /media
下創建。
sudo mkdir /mnt/dir_name
掛載:使用 mount
命令將分區 sdb1
掛載到剛創建的目錄上。
sudo mount /dev/sdb1 /mnt/dir_name
訪問:現在,當你進入 /mnt/dir_name
目錄時,你看到的就是分區 sdb1
里的內容。你在這里新建一個 file.txt
文件,這個文件實際就寫在了 sdb1
這個物理硬盤分區上。
卸載:當你不用時,可以卸載它。卸載后,目錄 /mnt/dir_name
又變回一個普通的空目錄。
sudo umount /mnt/my_movies
結論:
-
分區寫??件系統,?法直接使?,需要和指定的?錄關聯,進?掛載才能使?。
-
所以,可以根據訪問?標?件的路徑前綴準確判斷我在哪?個分區。