linux 文件系統(tǒng)工作原理簡介
文件系統(tǒng)時對存儲設(shè)備上的文件進(jìn)行組織管理的機制,組織方式不同就形成了不同的文件系統(tǒng)類型。
linux 中一切皆文件:不僅時普通的文件和目錄,連塊設(shè)備,套接字,管道等都是通過統(tǒng)一的文件系統(tǒng)來進(jìn)行管理的。
索引節(jié)點和目錄項
linux 系統(tǒng)為每個文件都分配了兩個數(shù)據(jù)結(jié)構(gòu)(索引節(jié)點 index node 和目錄項 dictionary entry),來對文件進(jìn)行管理。其中索引節(jié)點(index node)記錄文件的元數(shù)據(jù)信息,目錄項(dictionary entry)記錄文件的目錄結(jié)構(gòu)信息。
索引節(jié)點:簡稱為
inode,用來記錄文件的元數(shù)據(jù)信息:如inode編號,文件大小,訪問權(quán)限,修改日期,數(shù)據(jù)位置等。索引節(jié)點和文件一一對應(yīng),跟文件內(nèi)容一樣都會被持久化存儲到磁盤中,所以索引節(jié)點也同樣占據(jù)存儲空間。目錄項:簡稱為
dentry,用來記錄文件的名字,索引節(jié)點指針以及其他目錄項的關(guān)聯(lián)關(guān)系。 多個關(guān)聯(lián)的目錄項構(gòu)成了文件系統(tǒng)的目錄結(jié)構(gòu),與索引節(jié)點不同,目錄項是內(nèi)核維護的一個內(nèi)存數(shù)據(jù)結(jié)構(gòu),所以也通常稱為目錄項緩存。
即索引節(jié)點是每個文件的唯一標(biāo)志,而目錄項維護的正是文件系統(tǒng)的樹狀結(jié)構(gòu)。目錄項和索引節(jié)點的關(guān)系是多對一,可以簡單理解為,一個文件可以有多個別名。如,通過硬鏈接為文件創(chuàng)建的別名,就會對應(yīng)不同的目錄項,不過這些目錄項本質(zhì)上還是鏈接同一個文件,所以,它們的索引節(jié)點是相同的。
文件數(shù)據(jù)的存儲方式
磁盤讀寫的最小單位是扇區(qū),然而扇區(qū)只有 512B 大小,如果每次都讀寫這么小的單位,效率一定很低。所以,文件系統(tǒng)又把連續(xù)的扇區(qū)組成了邏輯塊,然后每次都以邏輯塊為最小單元來管理數(shù)據(jù)。常見的邏輯塊大小為 4KB,由連續(xù)的 8 個扇區(qū)組成。
目錄項、索引節(jié)點以及文件數(shù)據(jù)的關(guān)系:

目錄項本身就是一個內(nèi)存緩存,而索引節(jié)點則是存儲在磁盤中的數(shù)據(jù)。在前面的 Buffer 和 Cache 原理中,為了協(xié)調(diào)慢速磁盤與快速 CPU 的性能差異,文件內(nèi)容會緩存到頁緩存 Cache 中。所以這些索引節(jié)點自然也會緩存到內(nèi)存中,加速文件的訪問。
磁盤在執(zhí)行文件系統(tǒng)格式化時,會被分成三個存儲區(qū)域,超級塊、索引節(jié)點區(qū)和數(shù)據(jù)塊區(qū)。其中,
- 超級塊,存儲整個文件系統(tǒng)的狀態(tài)。
- 索引節(jié)點區(qū),用來存儲索引節(jié)點。
- 數(shù)據(jù)塊區(qū),則用來存儲文件數(shù)據(jù)。
虛擬文件系統(tǒng)
目錄項,索引節(jié)點、邏輯塊以及超級塊構(gòu)成了linux文件系統(tǒng)的四大基本要素。另外,為了支持各種不同類型的文件系統(tǒng),linux內(nèi)核在用戶進(jìn)程和文件系統(tǒng)之間又加入了一個抽象層-- 虛擬文件系統(tǒng)VFS(virtual file system).
VFS 定義了一組所有文件系統(tǒng)都支持的數(shù)據(jù)結(jié)構(gòu)和標(biāo)準(zhǔn)接口。這樣,用戶進(jìn)程和內(nèi)核中的其他子系統(tǒng),只需要跟VFS提供的統(tǒng)一的接口進(jìn)行交互即可,而不需要關(guān)心各種底層的文件系統(tǒng)的實現(xiàn)細(xì)節(jié)。
系統(tǒng)調(diào)用,VFS,緩存,文件系統(tǒng)以及塊存儲之間的關(guān)系如下:

在VFS的下方,linux支持各種類型的文件系統(tǒng),如Ext4、XFS、NFS等。按照存儲位置的不同,文件系統(tǒng)可以分為三類:
基于磁盤的文件系統(tǒng),即把數(shù)據(jù)直接存儲在計算機本地掛載的磁盤中,常見又: Ext4, XFS, OverlayFS等類型的文件系統(tǒng)。
基于內(nèi)存的文件系統(tǒng),即常說的虛擬文件系統(tǒng)。此類文件系統(tǒng)不需要任何的磁盤分配空間,但會占用內(nèi)存,常見的有 /proc, /sys(主要向用戶空間導(dǎo)出層次化的內(nèi)核對象)等
網(wǎng)絡(luò)文件系統(tǒng),即用來訪問其他計算機數(shù)據(jù)的文件系統(tǒng),如NFS,SMB, iSCSI等。
以上提到的這些文件系統(tǒng),要先掛載到VFS目錄樹中的某個子目錄(掛載點),然后才能訪問其中的文件。如在安裝系統(tǒng)時,要先掛載一個根目錄(/),在根目錄下在把其他文件系統(tǒng)(其他磁盤分區(qū)、/proc文件系統(tǒng)、/sys文件系統(tǒng)、NFS等)掛載進(jìn)來。
文件系統(tǒng) IO
當(dāng)把文件系統(tǒng)掛載到掛載點之后,就能夠通過掛載點去訪問管理的文件了。 VFS提供了一組標(biāo)準(zhǔn)的文件訪問接口,以系統(tǒng)調(diào)用的方式提供給應(yīng)用程序使用。
如cat命令,首先調(diào)用open()打開一個文件;然后調(diào)用read(),讀取文件內(nèi)容;最后調(diào)用write()把文件內(nèi)容輸出到控制臺的標(biāo)準(zhǔn)輸出中。
strace cat vi
execve("/usr/bin/cat", ["cat", "vi"], 0x7ffcc99897c8 /* 48 vars */) = 0
brk(NULL) = 0x55b305e3d000
arch_prctl(0x3001 /* ARCH_??? */, 0x7ffdb0bafeb0) = -1 EINVAL (Invalid argument)
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=71314, ...}) = 0
mmap(NULL, 71314, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fa9e9c0b000
close(3) = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\360q\2\0\0\0\0\0"..., 832) = 832
pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784
pread64(3, "\4\0\0\0\20\0\0\0\5\0\0\0GNU\0\2\0\0\300\4\0\0\0\3\0\0\0\0\0\0\0", 32, 848) = 32
pread64(3, "\4\0\0\0\24\0\0\0\3\0\0\0GNU\0\t\233\222%\274\260\320\31\331\326\10\204\276X>\263"..., 68, 880) = 68
fstat(3, {st_mode=S_IFREG|0755, st_size=2029224, ...}) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fa9e9c09000
pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784
pread64(3, "\4\0\0\0\20\0\0\0\5\0\0\0GNU\0\2\0\0\300\4\0\0\0\3\0\0\0\0\0\0\0", 32, 848) = 32
pread64(3, "\4\0\0\0\24\0\0\0\3\0\0\0GNU\0\t\233\222%\274\260\320\31\331\326\10\204\276X>\263"..., 68, 880) = 68
mmap(NULL, 2036952, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fa9e9a17000
mprotect(0x7fa9e9a3c000, 1847296, PROT_NONE) = 0
mmap(0x7fa9e9a3c000, 1540096, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x25000) = 0x7fa9e9a3c000
mmap(0x7fa9e9bb4000, 303104, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x19d000) = 0x7fa9e9bb4000
mmap(0x7fa9e9bff000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1e7000) = 0x7fa9e9bff000
mmap(0x7fa9e9c05000, 13528, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fa9e9c05000
close(3) = 0
arch_prctl(ARCH_SET_FS, 0x7fa9e9c0a580) = 0
mprotect(0x7fa9e9bff000, 12288, PROT_READ) = 0
mprotect(0x55b3058ca000, 4096, PROT_READ) = 0
mprotect(0x7fa9e9c4a000, 4096, PROT_READ) = 0
munmap(0x7fa9e9c0b000, 71314) = 0
brk(NULL) = 0x55b305e3d000
brk(0x55b305e5e000) = 0x55b305e5e000
openat(AT_FDCWD, "/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=5699248, ...}) = 0
mmap(NULL, 5699248, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fa9e94a7000
close(3) = 0
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0), ...}) = 0
openat(AT_FDCWD, "vi", O_RDONLY) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=60, ...}) = 0
fadvise64(3, 0, 0, POSIX_FADV_SEQUENTIAL) = 0
mmap(NULL, 139264, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fa9e9485000
read(3, "{\n \"registry-mirrors\": [\"http"..., 131072) = 60
write(1, "{\n \"registry-mirrors\": [\"http"..., 60{
"registry-mirrors": ["http://hub-mirror.c.163.com"]
}
) = 60
read(3, "", 131072) = 0
munmap(0x7fa9e9485000, 139264) = 0
close(3) = 0
close(1) = 0
close(2) = 0
exit_group(0) = ?
+++ exited with 0 +++
文件讀寫方式的各種差異,導(dǎo)致了IO分類的多種多樣,最常見的有,緩沖于非緩沖IO,直接于非直接IO,阻塞于非阻塞IO,同步于異步IO。各種分類方式的依據(jù)如下:
- 根據(jù)是否利用了標(biāo)準(zhǔn)庫緩存,可以把文件IO分為緩沖于非緩沖IO。
緩沖IO,利用標(biāo)準(zhǔn)庫緩存來加速文件的訪問,而標(biāo)準(zhǔn)庫內(nèi)部在通過系統(tǒng)調(diào)度訪問文件。
非緩沖IO,直接通過系統(tǒng)調(diào)用來訪問文件,不經(jīng)過標(biāo)準(zhǔn)庫緩存。
所說的"緩沖",是指標(biāo)準(zhǔn)庫內(nèi)部實現(xiàn)的緩存,如很多程序遇到換行時才真正輸出,而換行前的內(nèi)容就是被標(biāo)準(zhǔn)庫暫時緩存了。
無論時緩沖IO還是非緩沖IO,它們最終都需要經(jīng)過系統(tǒng)調(diào)用來訪問文件,所以在系統(tǒng)調(diào)用后,還會通過頁緩存來減少磁盤的IO操作。
- 根據(jù)是否利用操作系統(tǒng)的頁緩存,可以把文件系統(tǒng)IO分為直接IO與非直接IO。
直接IO,是指跳過操作系統(tǒng)的頁緩存,直接跟文件系統(tǒng)交互來訪問文件。
非直接IO,是指進(jìn)行文件讀寫時要先經(jīng)過操作系統(tǒng)的頁緩存,然后再由內(nèi)核或額外的系統(tǒng)調(diào)用將文件內(nèi)容真正寫入磁盤。
如果要實現(xiàn)直接IO,需要再系統(tǒng)調(diào)用中指定O_DIRECT 標(biāo)志,如果沒有設(shè)置,默認(rèn)是非直接IO。另外不管是直接IO還是非直接IO,本質(zhì)上都是和文件系統(tǒng)的交互,在其他一些場景中(數(shù)據(jù)庫場景),還會有跳過文件系統(tǒng)直接讀寫磁盤的場景(裸IO)。
- 根據(jù)應(yīng)用程序是否阻塞自身運行,可以把文件IO分為阻塞IO和非阻塞IO。
所謂阻塞IO,是指應(yīng)用程序執(zhí)行IO操作后,如果沒有獲得響應(yīng),就會阻塞當(dāng)前線程,不能執(zhí)行其他任務(wù)。
非阻塞IO與之相反,是指應(yīng)用程序進(jìn)行IO操作后,不會阻塞當(dāng)前的線程,可以執(zhí)行其他任務(wù),隨后再通過輪詢或者事件通知的形式,獲取調(diào)用結(jié)果。
如訪問管道或者網(wǎng)絡(luò)套接字時,設(shè)置了O_NONBLOCK標(biāo)志,就表示用非阻塞的方式進(jìn)行訪問,如果不做任何設(shè)置,默認(rèn)是阻塞的方式進(jìn)行訪問。
- 根據(jù)是否等待響應(yīng)結(jié)果,可以把文件IO分為同步和異步IO。
同步IO,應(yīng)用程序執(zhí)行IO操作后,要一直等到整個IO完成后才能獲得IO的響應(yīng)。
異步IO,應(yīng)用程序執(zhí)行IO操作后,不用等到完成和完成后的響應(yīng),而是繼續(xù)執(zhí)行即可,等到此次IO完成后,響應(yīng)會通過事件通知的方式告知應(yīng)用程序。
如,再操作文件時如果設(shè)置了O_SYNC或者O_DSYNC標(biāo)志,就代表同步IO, 如果設(shè)置了O_DSYNC,需要等文件數(shù)據(jù)寫入磁盤后才能返回,而O_SYNC在O_DSYNC的基礎(chǔ)上,要求文件元數(shù)據(jù)也要寫入磁盤后才能返回。
在訪問管道或者套接字時, O_ASYNC設(shè)置選項標(biāo)志IO是異步操作,此時內(nèi)核會通過SIGIO 或者SIGPOLL信號來通知進(jìn)程文件是否可寫。
很多IO的概念頁出現(xiàn)在網(wǎng)絡(luò)編程中,如非阻塞IO通常會跟select/poll配合使用。
至此,可以理解“l(fā)inux 一切皆文件” 的含義:無論是普通文件和塊設(shè)備、還是網(wǎng)絡(luò)套接字和管道等,它們都是通過統(tǒng)一的VFS接口來訪問的。