天天看點

Linux核心之IO2:EXT檔案系統詳解(案例解析)

一切都是檔案,Linux通過VFS中間層,支援多種檔案系統,對APP統一接口;

Linux核心之IO2:EXT檔案系統詳解(案例解析)

檔案系統的本質是将使用者資料和中繼資料(管理資料的資料),組織成有序的目錄結構。

1 EXT2檔案系統總體存儲布局

一個磁盤可以劃為多個分區,每個分區必須先用格式化工具(某種mkfs)格式化成某種格式的檔案系統,然後才能存儲檔案,格式化的過程會在磁盤上寫一些管理存儲布局資訊。一個典型的ext格式化檔案系統存儲布局如下:

Linux核心之IO2: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格式化

Linux核心之IO2:EXT檔案系統詳解(案例解析)

格式化後的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      
Linux核心之IO2:EXT檔案系統詳解(案例解析)

*開頭行表示省略全零資料。

000000開始的1KB是啟動塊,由于不是真正的磁盤,這裡全零;

000400到0007ff是1KB的超級塊,對照dumpe2fs輸出資訊對比如下:

超級塊:

Linux核心之IO2:EXT檔案系統詳解(案例解析)

Ext2各字段按小端存儲。

塊組描述符

Linux核心之IO2:EXT檔案系統詳解(案例解析)

整個檔案系統1M,每個塊1KB,一共1024個塊,除了啟動塊0,其他1-1023全部屬于group0.

Block1是超級塊,

塊位圖Block6,

inode位圖Block7,

inode表從Block8開始,由于超級塊中指出每個塊組有128inode,每個inode大小128位元組,是以共占用16個塊(8-23)從Block24開始就是資料塊。

檢視塊位圖,6x1024=0x1800

Linux核心之IO2:EXT檔案系統詳解(案例解析)

前37位(ff ff ff ff 1f)已經被占用,空閑塊是連續的Block38-1023=986 free blocks

檢視inode位圖,7x1024=0x1c00

Linux核心之IO2:EXT檔案系統詳解(案例解析)

已用11個inode中,前10個inode是被ext2檔案系統保留的,其中第二個inode是根目錄,第11個inode是lost+found目錄。塊組描述符也指出該組有兩個目錄,就是根目錄和lost+found目錄。

解析根目錄的inode,位址Block8*1024+inode2(1*128)=0x2080

Linux核心之IO2:EXT檔案系統詳解(案例解析)

st_mode用八進制表示,包含了檔案類型和權限,最高位4表示為檔案類型目錄,755表示權限,size是大小,說明根目錄現在隻有一個塊。Links=3表示根目錄有三個硬連結,分别是根目錄下的.,

…和lost+found字目錄下的…,

這裡的Blockcount是以512位元組為一個塊統計的,磁盤最小讀寫機關一個扇區(Sector)512位元組,而不是格式化檔案系統時指定的塊大小。是以Blockcount是磁盤的實體塊數量,而不是分區的邏輯塊數量。

根據上圖Block[0]=24塊,在檔案系統中24x1024位元組=0x6000,從od指令查找0x6000位址

Linux核心之IO2:EXT檔案系統詳解(案例解析)

目錄資料塊由許多不定長記錄組成,每條記錄描述該目錄下的一個檔案;

記錄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!”,重新解析根目錄

檢視塊位圖

Linux核心之IO2:EXT檔案系統詳解(案例解析)

可見前38bit被占用,第38塊位址38x1027=0x9800

檢視inode位圖,7x1024=0x1c00

Linux核心之IO2:EXT檔案系統詳解(案例解析)

由圖知,用掉了12個inode

檢視根目錄的資料塊内容

Linux核心之IO2:EXT檔案系統詳解(案例解析)
debug fs檢視t.txt屬性

stat      
Linux核心之IO2:EXT檔案系統詳解(案例解析)

t.txt檔案inode号=12

Inode12的位址=Block8*1024+inode12(11*128)=0x2580

檢視t.txt的inode

Linux核心之IO2:EXT檔案系統詳解(案例解析)

檔案大小10位元組=stlen(“hello fs!”),資料塊位址0x26x1024 = 0x9800

檢視内容

Linux核心之IO2:EXT檔案系統詳解(案例解析)

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

Linux核心之IO2:EXT檔案系統詳解(案例解析)

可見,這種尋址方式對于通路不超過12資料塊的小檔案,是非常快的。通路任意資料隻需要兩次讀盤操作,一次讀Inode,一次讀資料塊。

而通路大檔案資料最多需要5次讀盤操作,inode,

一級尋址塊、二級尋址塊、三級尋址塊、資料塊。

實際上磁盤中的inode和資料塊往往會被核心緩存,讀大檔案的效率也不會太低。

在EXT4,支援Extents,其描述連續資料塊的方式,可以節省中繼資料空間。

Linux核心之IO2:EXT檔案系統詳解(案例解析)

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)函數

Linux核心之IO2:EXT檔案系統詳解(案例解析)

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)。

Linux核心之IO2:EXT檔案系統詳解(案例解析)

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 >>      

7 fuse