一切都是檔案,Linux通過VFS中間層,支援多種檔案系統,對APP統一接口;
檔案系統的本質是将使用者資料和中繼資料(管理資料的資料),組織成有序的目錄結構。
1 EXT2檔案系統總體存儲布局
一個磁盤可以劃為多個分區,每個分區必須先用格式化工具(某種mkfs)格式化成某種格式的檔案系統,然後才能存儲檔案,格式化的過程會在磁盤上寫一些管理存儲布局資訊。一個典型的ext格式化檔案系統存儲布局如下:
檔案系統最小存儲機關是Block,Block大小格式化時确定,一般4K;
啟動塊(BootBlock):
大小1K,是由PC标準規定的,用來存儲磁盤分區資訊和啟動資訊,任何檔案系統都不能使用啟動塊。啟動塊之後才是EXT檔案系統的開始;
超級塊(Superblock):
描述整個分區的檔案系統資訊,比如塊大小,檔案系統版本号,上次mount時間等;
超級塊在每個塊組的開頭都有一份拷貝;
塊組描述符表(GDT, Group Descriptor Table)
由很多塊組描述符(Group Descriptor) 組成,整個分區分成多少個塊組就對應多少個GD;
每個GD存儲一個塊組的描述資訊,比如這個塊組從哪裡開始是inode表,哪裡開始是資料塊,空閑的inode和資料塊還有多少個等。
和超級塊類似,GDT在每個塊組開頭也有一份拷貝;
塊位圖(Block Bitmap)
用來描述整個塊組中那些塊空閑,本身占用一個塊,每個bit代表本塊組的一個塊,bit為1表示對應塊被占用,0表示空閑;
tips:
df指令統計整個磁盤空間非常快,因為隻需要檢視每個塊組的塊位圖即可;
du指令檢視一個較大目錄會很慢,因為需要搜尋整個目錄的所有檔案;
inode位圖(inode Bitmap)
和塊位圖類似,本身占用一個塊,每個bit表示一個inode是否空閑可用;
inode表(inode Table)
每個檔案對應一個inode,用來描述檔案類型,權限,大小,建立/修改/通路時間等資訊;
一個塊組中的所有inode組成了inode表;
Inode表占用多少個塊,格式化時就要确定,mke2fs工具預設政策是每8K配置設定一個inode。
就是說當檔案全部是8K時,inode表會充分利用,當檔案過大,inode表會浪費,檔案過小,inode不夠用;
硬連結指向同一個inode;
資料塊(Data Block)
a.正常檔案:
檔案的資料存儲在資料塊中;
b.目錄
該目錄下所有檔案名和目錄名存儲在資料塊中;
檔案名儲存在目錄的資料塊中,ls –l看到的其他資訊儲存在該檔案的inode中;
目錄也是檔案,是一種特殊類型的檔案;
c.符号連結
如果目标路徑名較短,直接儲存在inode中以便查找,如果過長,配置設定一個資料塊儲存。
d.裝置檔案、FIFO和socket等特殊檔案
沒有資料塊,裝置檔案的主,次裝置号儲存在inode中。
2 執行個體解析檔案系統結構:
用一個檔案來模拟一個磁盤;
1.建立一個1M檔案,内容全是0;
dd if=/dev/zero of=fs count=256 bs=4k
2.對檔案fs格式化
格式化後的fs檔案大小依然是1M,但内容已經不是全零。
3.用dumpe2fs工具檢視這個分區的超級塊和塊組描述表資訊
(base)
dumpe2fs 1.42.13 (17-May-2015)
Filesystem volume name: <none>
Last mounted on: <not available>
Filesystem UUID: a00715b2-528b-4ca6-8c2b-953389a5ab00
Filesystem magic number: 0xEF53
Filesystem revision #: 1 (dynamic)
Filesystem features: ext_attr resize_inode dir_index filetype sparse_super
large_file
Filesystem flags: signed_directory_hash
Default mount options: user_xattr acl
Filesystem state: clean
Errors behavior: Continue
Filesystem OS type: Linux
Inode count: 128
Block count: 1024
Reserved block count: 51
Free blocks: 986
Free inodes: 117
First block: 1
Block size: 1024
Fragment size: 1024
Reserved GDT blocks: 3
Blocks per group: 8192
Fragments per group: 8192
Inodes per group: 128
Inode blocks per group: 16
Filesystem created: Fri Aug 21 16:48:02 2020
Last mount time: n/a
Last write time: Fri Aug 21 16:48:02 2020
Mount count: 0
Maximum mount count: -1
Last checked: Fri Aug 21 16:48:02 2020
Check interval: 0 (<none>)
Reserved blocks uid: 0 (user root)
Reserved blocks gid: 0 (group root)
First inode: 11
Inode size: 128
Default directory hash: half_md4
Directory Hash Seed: e5c519af-d42e-43b5-bc8d-c67c5a79bcbe
Group 0: (Blocks 1-1023)
主 superblock at 1, Group descriptors at 2-2
保留的GDT塊位于 3-5
Block bitmap at 6 (+5), Inode bitmap at 7 (+6)
Inode表位于 8-23 (+7)
986 free blocks, 117 free inodes, 2 directories
可用塊數: 38-1023
可用inode數: 12-128
(base)
塊大小1024位元組,一共1024個塊,第0塊是啟動塊,第一個塊開始才是EXT2檔案系統,
Group0占據1到1023個塊,共1023個塊。
超級塊在塊1,GDT2,預留3-5,
塊位圖在塊6,占用一個塊,1024x8=8192bit,足夠表示1023個塊,隻需一個塊就夠了;
inode bitmap在塊7
inode表在8-23,占用16個塊,預設每8K對應一個inode,共1M/8K=128個inode。每個inode占用128位元組,128x128=16k
4用普通檔案制作的檔案系統也可以像磁盤分區一樣mount到某個目錄
$ sudo mount
-o loop選項告訴mount這是一個正常檔案,不是塊裝置,mount會把它的資料當作分區格式來解釋;
檔案系統格式化後,在根目錄自動生成三個字目錄: ., …, lost+found
Lost+found目錄由e2fsck工具使用,如果在檢查磁盤時發生錯誤,就把有錯誤的塊挂在這個目錄下。
現在可以在/mnt 讀寫檔案,umount解除安裝後,確定所有改動都儲存在fs檔案中了。
5.解讀fs二進制檔案
od –tx1 –Ax fs
*開頭行表示省略全零資料。
000000開始的1KB是啟動塊,由于不是真正的磁盤,這裡全零;
000400到0007ff是1KB的超級塊,對照dumpe2fs輸出資訊對比如下:
超級塊:
Ext2各字段按小端存儲。
塊組描述符
整個檔案系統1M,每個塊1KB,一共1024個塊,除了啟動塊0,其他1-1023全部屬于group0.
Block1是超級塊,
塊位圖Block6,
inode位圖Block7,
inode表從Block8開始,由于超級塊中指出每個塊組有128inode,每個inode大小128位元組,是以共占用16個塊(8-23)從Block24開始就是資料塊。
檢視塊位圖,6x1024=0x1800
前37位(ff ff ff ff 1f)已經被占用,空閑塊是連續的Block38-1023=986 free blocks
檢視inode位圖,7x1024=0x1c00
已用11個inode中,前10個inode是被ext2檔案系統保留的,其中第二個inode是根目錄,第11個inode是lost+found目錄。塊組描述符也指出該組有兩個目錄,就是根目錄和lost+found目錄。
解析根目錄的inode,位址Block8*1024+inode2(1*128)=0x2080
st_mode用八進制表示,包含了檔案類型和權限,最高位4表示為檔案類型目錄,755表示權限,size是大小,說明根目錄現在隻有一個塊。Links=3表示根目錄有三個硬連結,分别是根目錄下的.,
…和lost+found字目錄下的…,
這裡的Blockcount是以512位元組為一個塊統計的,磁盤最小讀寫機關一個扇區(Sector)512位元組,而不是格式化檔案系統時指定的塊大小。是以Blockcount是磁盤的實體塊數量,而不是分區的邏輯塊數量。
根據上圖Block[0]=24塊,在檔案系統中24x1024位元組=0x6000,從od指令查找0x6000位址
目錄資料塊由許多不定長記錄組成,每條記錄描述該目錄下的一個檔案;
記錄1,inode号為2,就是根目錄本身,記錄長12位元組,檔案名長度1(“.”),類型2;
記錄2,inode号為2,也是根目錄本身,記錄長12位元組,檔案名長度(“…”),類型2;
記錄3,inode号為11,記錄長1000位元組,檔案名長度(”lost+found”),類型2;
debugfs指令,不需要mount就可以檢視這個檔案系統的資訊
debugfs fs
stat / cd ls 等
将fs挂載,在根目錄建立一個hello.txt檔案,寫入内容”hello fs!”,重新解析根目錄
檢視塊位圖
可見前38bit被占用,第38塊位址38x1027=0x9800
檢視inode位圖,7x1024=0x1c00
由圖知,用掉了12個inode
檢視根目錄的資料塊内容
debug fs檢視t.txt屬性
stat
t.txt檔案inode号=12
Inode12的位址=Block8*1024+inode12(11*128)=0x2580
檢視t.txt的inode
檔案大小10位元組=stlen(“hello fs!”),資料塊位址0x26x1024 = 0x9800
檢視内容
3 資料塊尋址
如果一個檔案很大,有多個資料塊,這些塊可能不是連續存放的,那如何尋址所有塊呢?
在上面根目錄資料塊是通過inode的索引項Blocks[0]找到的,實際上這樣的索引項一共有15個,從Blocks[0]到Blocks[14],每個索引項占4位元組,前12個索引項都表示塊編号,例如上面Blocks[0]儲存塊24,如果塊大小是1KB,這樣就可以表示12KB的檔案,剩下的三個索引項Blocks[12]~
Blocks[14],如果也這麼用,就隻能表示最大15KB檔案,這遠遠不夠。
實際上剩下的這3個索引項都是間接索引,Blocks[12]所指向的間接尋址塊(Indirect
Block),其中存放類似Blocks[0]這種索引,再由索引項指向資料塊。假設塊大小是b,那麼一級間接尋址加上之前的12個索引項,最大可尋址b/4+12個資料塊=1024/4+12=268KB的檔案。
同理Blocks[13]作為二級尋址,最大可尋址(b/4)*(b/4)+12=64.26MB
Blocks[14]作為三級尋址,最大可尋址(b/4)*(b/4) *(b/4)+12=16.06GB
可見,這種尋址方式對于通路不超過12資料塊的小檔案,是非常快的。通路任意資料隻需要兩次讀盤操作,一次讀Inode,一次讀資料塊。
而通路大檔案資料最多需要5次讀盤操作,inode,
一級尋址塊、二級尋址塊、三級尋址塊、資料塊。
實際上磁盤中的inode和資料塊往往會被核心緩存,讀大檔案的效率也不會太低。
在EXT4,支援Extents,其描述連續資料塊的方式,可以節省中繼資料空間。
4 檔案和目錄操作的系統函數
Linux提供一些檔案和目錄操作的常用系統函數,檔案操作指令比如ls,
cp,mv等都是基于這些系統調用實作的。
stat:
讀取檔案的inode, 把inode中的各種檔案屬性填入struct stat結構體傳回;
假如讀一個檔案/opt/file,其查找順序是
1.讀出inode表中第2項,也就是根目錄的inode,從中找出根目錄資料塊的位置
2.從根目錄的資料塊中找出檔案名為opt的記錄,從記錄中讀出它的inode号
3. 讀出opt目錄的inode,從中找出它的資料塊的位置
4. 從opt目錄的資料塊中找出檔案名為file的記錄,從記錄中讀出它的inode号
5.讀出file檔案的inode
還有另外兩個類似stat的函數:fstat(2)函數,lstat(2)函數
access(2):
檢查執行目前程序的使用者是否有權限通路某個檔案,access去取出檔案inode中的st_mode字段,比較通路權限,傳回0表示允許通路,-1不允許。
chmod(2)和fchmod(2):
改變檔案的通路權限,也就是修改inode中的st_mode字段。
chmod(1)指令是基于chmod(2)實作的。
chown(2)/fchown(2)/lchown(2):
改變檔案的所有者群組,也就是修改inode中的User和Group字段。
utime(2):
改邊檔案通路時間和修改時間,也就是修改inode中的atime和mtime字段。touch(1)指令是基于utime實作的。
truncate(2)/ftruncate(2):
截斷檔案,修改inode中的Blocks索引項以及塊位圖中的bit.
link(2):
建立硬連結,就是在目錄的資料塊中添加一條記錄,其中的inode号字段與源檔案相同。
syslink(2):
建立符号連結,需要建立一個新的inode,其中st_mode字段的檔案類型是符号連結。指向路徑名,不是inode,替換掉同名檔案,符号連結依然可以正常通路。
ln(1)指令是基于link和symlink函數實作的。
unlink(2):
删除一個連結,如果是符号連結則釋放符号連結的inode和資料塊,清除inode位圖和塊位圖中相應位。如果是硬連結,從目錄的資料塊中清除檔案名記錄,如果目前檔案的硬連結數已經是1,還要删除它,同時釋放inode和資料塊,清除inode位圖和塊位圖相應位,這時檔案就真的删除了。
rename(2):
修改檔案名,就是修改目錄資料塊中的檔案名記錄,如果新舊檔案名不在一個目錄下,則需要從原目錄資料中清楚記錄,然後添加到新目錄的資料塊中。mv(1)指令是基于rename實作的。
readlink(2):
從符号連結的inode或資料塊中讀出儲存的資料。
rmdir(2):
删除一個目錄,目錄必須是空的(隻含.和…)才能删除,釋放它的inode和資料塊,清除inode位圖和塊位圖的相應位,清除父目錄資料塊中的記錄,父目錄的硬連結數減1,rmdir(1)指令是基于rmdir函數實作的。
opendir(3)/readdir(3)/closedir(3):
用于周遊目錄資料塊中的記錄。
目錄,是一個特殊的檔案,其存放inode号與檔案名的映射關系;
5 VFS:
Linux支援各種檔案系統格式,ext2,ext3,ext4,fat,ntfs,yaffs等,核心在不同的檔案系統格式之上做了一個抽象層,使得檔案目錄通路等概念成為抽象層概念,對APP提供統一通路接口,由底層驅動去實作不同檔案系統的差異,這個抽象層叫虛拟檔案系統(VFS, Virtual Filesystem)。
File,dentry,inode,super_block這幾個結構體組成了VFS的核心概念。
6 icache/dcache
通路過的檔案或目錄,核心都會做cache;
inode_cachep = kmem_cache_create()
dentry_cache=KMEM_CACHE()
這兩個函數申請的slab可以回收,記憶體自動釋放;
Linux配置回收優先級
(1).free pagecache:
echo 1 >>
(2)free reclaimable slab objects (includes dentries and inodes)
echo 2 >>
(3)free slab objects and pagecache:
echo 3 >>