首頁/ 汽車/ 正文

「底層原理」一層層剝開檔案系統的面紗,徹底理解Linux檔案系統

概述

提到檔案系統,Linux的老江湖們對這個概念當然不會陌生,然而剛接觸Linux的新手們就會被檔案系統這個概念弄得暈頭轉向,恰好我當年正好屬於後者。

從windows下轉到Linux的童鞋聽到最多的應該是fat32和ntfs(在windows 2000之後所出現的一種新型的日誌檔案系統),那個年代經常聽到說“我要把C盤格式化成ntfs格式,D盤格式化成fat32格式”。

一到Linux下,很多入門Linux的書籍中當牽扯到檔案系統這個術語時,二話不說,不管三七二十一就給出了下面這個圖,然後逐一解釋一下每個目錄是拿來幹啥的、裡面會放什麼型別的檔案就完事兒了,弄得初學者經常“丈二和尚摸不著頭腦”。

「底層原理」一層層剝開檔案系統的面紗,徹底理解Linux檔案系統

本文的目的就是和大家分享一下我當初是如何學習Linux的檔案系統的,也算是一個“老”油條的一些心得吧。

“檔案系統”的主語是“檔案”,那麼檔案系統的意思就是“用於管理檔案的(管理)系統”,在大多數作業系統教材裡,“檔案是資料的集合”這個基本點是一致的,而這些資料最終都是儲存在儲存介質裡,如硬碟、光碟、隨身碟等。

另一方面,使用者在管理資料時也是檔案為基本單位,他們所關心的問題是:

• 1。我的檔案在什麼地方放著?

• 2。我如何將資料存入某個檔案?

• 3。如何從檔案裡將資料讀出來?

• 4。不再需要的檔案怎麼將其刪除?

簡而言之,檔案系統就是一套用於定義檔案的命名和組織資料的規範,其根本目的是便對檔案進行查詢和存取。

虛擬檔案系統VFS

在Linux早期設計階段,檔案系統與核心程式碼是整合在一起的,這樣做的缺點是顯而易見的。假如,我的系統只能識別ext3格式的檔案系統,我的隨身碟是fat32格式,那麼很不幸的是我的隨身碟將不會被我的系統所識別,

為了支援不同種類的檔案系統,Linux採用了在Unix系統中已經廣泛採用的設計思想,透過虛擬檔案系統VFS來遮蔽下層各種不同型別檔案系統的實現細節和差異。

其實VFS最早是由Sun公司提出的,其基本思想是將各種檔案系統的公共部分抽取出來,形成一個抽象層。對使用者的應用程式而言,VFS提供了檔案系統的系統呼叫介面。而對具體的檔案系統來說,VFS透過一系列統一的外部介面遮蔽了實現細節,使得對檔案的操作不再關心下層檔案系統的型別,更不用關心具體的儲存介質,這一切都是透明的。

ext2檔案系統

虛擬檔案系統VFS是對各種檔案系統的一個抽象層,抽取其共性,以便對外提供統一管理介面,便於核心對不同種類的檔案系統進行管理。那麼首先我們得看一下對於一個具體的檔案系統,我們該關注重點在哪裡。

對於儲存裝置(以硬碟為例)上的資料,可分為兩部分:

• 使用者資料:儲存使用者實際資料的部分;

• 管理資料:用於管理這些資料的部分,這部分我們通常叫它元資料(metadata)。

我們今天要討論的就是這些元資料。這裡有個概念首先需要明確一下:塊裝置。所謂塊裝置就是以塊為基本讀寫單位的裝置,支援緩衝和隨機訪問。每個檔案系統提供的mk2fs。xx工具都支援在構建檔案系統時由使用者指定塊大小,當然使用者不指定時會有一個預設值。

我們知道一般硬碟的每個扇區512位元組,而多個相鄰的若干扇區就構成了一個簇,從檔案系統的角度看這個簇對應的就是我們這裡所說塊。使用者從上層下發的資料首先被快取在塊裝置的快取裡,當寫滿一個塊時資料才會被髮給硬碟驅動程式將資料最終寫到儲存介質上。如果想將裝置快取中資料立即寫到儲存介質上可以透過sync命令來完成。

塊越大儲存效能越好,但浪費比較嚴重;塊越小空間利用率較高,但效能相對較低。如果你不是專業的“骨灰級”玩兒家,在儲存裝置上構建檔案系統時,塊大小就用預設值。透過命令“tune2fs -l /dev/sda1”可以檢視該儲存裝置上檔案系統所使用的塊大小:

[root@localhost ~]#

該命令已經暴露了檔案系統的很多資訊,接下我們將詳細分析它們。

下圖是我的虛擬機器的情況,三塊IDE的硬碟。容量分別是:

hda: 37580963840/(1024

1024

1024)=35GB hdb: 8589934592/(1024

1024

1024)=8GB hdd: 8589934592/(1024

1024

1024)=8GB

「底層原理」一層層剝開檔案系統的面紗,徹底理解Linux檔案系統

如果這是三塊實際的物理硬碟的話,廠家所標稱的容量就分別是37。5GB、8。5GB和8。5GB。可能有些童鞋覺得虛擬機器有點“假”,那麼我就來看看實際硬碟到底是個啥樣子。

主角1

:西部資料 500G SATA介面 CentOS 5。5

實際容量:500107862016B = 465。7GB

「底層原理」一層層剝開檔案系統的面紗,徹底理解Linux檔案系統

主角2

:希捷 160G SCSI介面 CentOS 5。5

實際容量:160041885696B=149GB

「底層原理」一層層剝開檔案系統的面紗,徹底理解Linux檔案系統

大家可以看到,VMware公司的水平還是相當不錯的,虛擬硬碟和物理硬碟“根本”看不出差別,畢竟屬於雲平臺基礎架構支撐者的風雲人物嘛。

相關影片推薦

3個linux核心的秘密,讓你徹底搞懂檔案系統

剖析Linux核心虛擬檔案系統(VFS)架構

學習地址:C/C++Linux伺服器開發/後臺架構師【零聲教育】-學習影片教程-騰訊課堂

需要C/C++ Linux伺服器架構師學習資料加qun

812855908

(資料包括C/C++,Linux,golang技術,核心,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒體,CDN,P2P,K8S,Docker,TCP/IP,協程,DPDK,ffmpeg,大廠面試題 等)

「底層原理」一層層剝開檔案系統的面紗,徹底理解Linux檔案系統

以硬碟/dev/hdd1為例,它是我新增的一塊新盤,格式化成ext2後,根目錄下只有一個lost+found目錄,讓我們來看一下它的佈局情況,以此來開始我們的檔案系統之旅。

「底層原理」一層層剝開檔案系統的面紗,徹底理解Linux檔案系統

對於使用了ext2檔案系統的分割槽來說,有一個叫superblock的結構 ,superblock的大小為1024位元組,其實ext3的superblock也是1024位元組。下面的小程式可以證明這一點:

#include #include #include int main(int argc,char** argv){ printf(“sizeof of ext2 superblock=%d\n”,sizeof(struct ext2_super_block)); printf(“sizeof of ext3 superblock=%d\n”,sizeof(struct ext3_super_block)); return 0;}******************【執行結果】******************sizeof of ext2 superblock=1024sizeof of ext3 superblock=1024

硬碟的第一個位元組是從0開始編號,所以第一個位元組是byte0,以此類推。/dev/hdd1分割槽頭部的1024個位元組(從byte0~byte1023)都用0填充,因為/dev/hdd1不是主引導盤。superblock是從byte1024開始,佔1024B儲存空間。我們用dd命令把superblock的資訊提取出來:

dd if=/dev/hdd1 of=。/hdd1sb bs=1024 skip=1 count=1

上述命令將從/dev/hdd1分割槽的byte1024處開始,提取1024個位元組的資料儲存到當前目錄下的hdd1sb檔案裡,該檔案裡就儲存了我們superblock的所有資訊,上面的程式稍加改造,我們就可以以更直觀的方式看到superblock的輸出瞭如下:

#include #include #include #include #include #include #include #include int main(int argc,char** argv){ printf(“sizeof of ext2 superblock=%d\n”,sizeof(struct ext2_super_block)); printf(“sizeof of ext3 superblock=%d\n”,sizeof(struct ext3_super_block)); char buf[1024] = {0}; int fd = -1; struct ext2_super_block hdd1sb; memset(&hdd1sb,0,1024); if(-1 == (fd=open(“。/hdd1sb”,O_RDONLY,0777))){ printf(“open file error!\n”); return 1; } if(-1 == read(fd,buf,1024)){ printf(“read error!\n”); close(fd); return 1; } memcpy((char*)&hdd1sb,buf,1024); printf(“inode count : %ld\n”,hdd1sb。s_inodes_count); printf(“block count : %ld\n”,hdd1sb。s_blocks_count); printf(“Reserved blocks count : %ld\n”,hdd1sb。s_r_blocks_count); printf(“Free blocks count : %ld\n”,hdd1sb。s_free_blocks_count); printf(“Free inodes count : %ld\n”,hdd1sb。s_free_inodes_count); printf(“First Data Block : %ld\n”,hdd1sb。s_first_data_block); printf(“Block size : %ld\n”,1<<(hdd1sb。s_log_block_size+10)); printf(“Fragment size : %ld\n”,1<<(hdd1sb。s_log_frag_size+10)); printf(“Blocks per group : %ld\n”,hdd1sb。s_blocks_per_group); printf(“Fragments per group : %ld\n”,hdd1sb。s_frags_per_group); printf(“Inodes per group : %ld\n”,hdd1sb。s_inodes_per_group); printf(“Magic signature : 0x%x\n”,hdd1sb。s_magic); printf(“size of inode structure : %d\n”,hdd1sb。s_inode_size); close(fd); return 0;}******************【執行結果】******************inode count : 1048576block count : 2097065Reserved blocks count : 104853Free blocks count : 2059546Free inodes count : 1048565First Data Block : 0Block size : 4096Fragment size : 4096Blocks per group : 32768Fragments per group : 32768Inodes per group : 16384Magic signature : 0xef53size of inode structure : 128

可以看出,superblock 的作用就是記錄檔案系統的型別、block大小、block總數、inode大小、inode總數、group的總數等資訊。

對於ext2/ext3檔案系統來說數字簽名Magic signature都是0xef53,如果不是那麼它一定不是ext2/ext3檔案系統。這裡我們可以看到,我們的/dev/hdd1確實是ext2檔案系統型別。hdd1中一共包含1048576個inode節點(inode編號從1開始),每個inode節點大小為128位元組,所有inode消耗的儲存空間是1048576×128=128MB;總共包含2097065個block,每個block大小為4096位元組,每32768個block組成一個group,所以一共有2097065/32768=63。99,即64個group(group編號從0開始,即Group0~Group63)。所以整個/dev/hdd1被劃分成了64個group,詳情如下:

「底層原理」一層層剝開檔案系統的面紗,徹底理解Linux檔案系統

用命令tune2fs可以驗證我們之前的分析:

「底層原理」一層層剝開檔案系統的面紗,徹底理解Linux檔案系統

再透過命令dumpe2fs /dev/hdd1的輸出,可以得到我們關注如下部分:

「底層原理」一層層剝開檔案系統的面紗,徹底理解Linux檔案系統

接下來以Group0為例,主superblock在Group0的block0裡,根據前面的分析,我們可以畫出主superblock在block0中的位置如下:

「底層原理」一層層剝開檔案系統的面紗,徹底理解Linux檔案系統

因為superblock是如此之重要,一旦它出錯你的整個系統就玩兒完了,所以檔案系統中會存在磁碟的多個不同位置會存在主superblock的備份副本,一旦系統出問題後還可以透過備份的superblock對檔案系統進行修復。

第一版ext2檔案系統的實現裡,每個Group裡都存在一份superblock的副本,然而這樣做的負面效果也是相當明顯,那就是嚴重降低了磁碟的空間利用率。所以在後續ext2的實現程式碼中,選擇用於備份superblock的Group組號的原則是3的N次方、5的N次方、7的N次方其中N=0,1,2,3…。根據這個公式我們來計算一下/dev/hdd1中備份有supeblock的Group號:

「底層原理」一層層剝開檔案系統的面紗,徹底理解Linux檔案系統

也就是說Group1、3、5、7、9、25、27、49裡都儲存有superblock的複製,如下:

「底層原理」一層層剝開檔案系統的面紗,徹底理解Linux檔案系統

用block號分別除以32768就得到了備份superblock的Group號,和我們在上面看到的結果一致。我們來看一下/dev/hdd1中Group和block的關係:

「底層原理」一層層剝開檔案系統的面紗,徹底理解Linux檔案系統

從上圖中我們可以清晰地看出在使用了ext2檔案系統的分割槽上,包含有主superblock的Group、備份superblock的Group以及沒有備份superblock的Group的佈局情況。儲存了superblock的Group中有一個組描述符(Group descriptors)緊跟在superblock所在的block後面,佔一個block大小;同時還有個Reserved GDT跟在組描述符的後面。

Reserved GDT的存在主要是支援ext2檔案系統的resize功能,它有自己的inode和data block,這樣一來如果檔案系統動態增大,Reserved GDT就正好可以騰出一部分空間讓Group descriptor向下擴充套件。

接下來我們來認識一下superblock,inode,block,group,group descriptor,block bitmap,inode table這些傢伙。

superblock

這個東西確實很重要,前面我們已經見識過。為此,檔案系統還特意精挑細選的找了N多後備Group,在這些Group中都存有superblock的副本,你就知道它有多重要了。

說白了,superblock 的作用就是記錄檔案系統的型別、block大小、block總數、inode大小、inode總數、group的總數等等。

group descriptors

千萬不要以為這就是一個組描述符,看到descriptor後面加了個s就知道這是N多描述符的集合。確實,這是檔案系統中所有group的描述符所構成的一個數組,它的結構定義在include/linux/ext2_fs。h中:

//Structure of a blocks group descriptorstruct ext2_group_desc{ __le32 bg_block_bitmap; /* group中block bitmap所在的第一個block號 */ __le32 bg_inode_bitmap; /* group中inode bitmap 所在的第一個block號 */ __le32 bg_inode_table; /* group中inodes table 所在的第一個block號 */ __le16 bg_free_blocks_count; /* group中空閒的block總數 */ __le16 bg_free_inodes_count; /* group中空閒的inode總數*/ __le16 bg_used_dirs_count; /* 目錄數 */ __le16 bg_pad; __le32 bg_reserved[3];};

下面的程式可以幫助瞭解一下/dev/hdd1中所有group的descriptor的詳情:

#define B_LEN 32 //一個struct ext2_group_desc{}佔固定32位元組int main(int argc,char** argv){ char buf[B_LEN] = {0}; int i=0,fd = -1; struct ext2_group_desc gd; memset(&gd,0,B_LEN); if(-1 == (fd=open(argv[1],O_RDONLY,0777))){ printf(“open file error!\n”); return 1; } while(i<64){ //因為我已經知道了/dev/hdd1中只有64個group if(-1 == read(fd,buf,B_LEN)){ printf(“read error!\n”); close(fd); return 1; } memcpy((char*)&gd,buf,B_LEN); printf(“========== Group %d: ==========\n”,i); printf(“Blocks bitmap block %ld \n”,gd。bg_block_bitmap); printf(“Inodes bitmap block %ld \n”,gd。bg_inode_bitmap); printf(“Inodes table block %ld \n”,gd。bg_inode_table); printf(“Free blocks count %d \n”,gd。bg_free_blocks_count); printf(“Free inodes count %d \n”,gd。bg_free_inodes_count); printf(“Directories count %d \n”,gd。bg_used_dirs_count); memset(buf,0,B_LEN); i++; } close(fd); return 0;}

執行結果和dumpe2fs /dev/hdd1的輸出對比如下:

「底層原理」一層層剝開檔案系統的面紗,徹底理解Linux檔案系統

其中,檔案gp0decp是由命令“dd if=/dev/hdd1 of=。/gp0decp bs=4096 skip=1 count=1”生成。每個group descriptor裡記錄了該group中的inode table的起始block號,因為inode table有可能會佔用連續的多個block;空閒的block、inode數等等。

block bitmap:

在檔案系統中每個物件都有一個對應的inode節點(這句話有些不太準確,因為符號連結和它的目標檔案共用一個inode),裡儲存了一個物件(檔案或目錄)的資訊有許可權、所佔位元組數、建立時間、修改時間、連結數、屬主ID、組ID,如果是檔案的話還會包含檔案內容佔用的block總數以及block號。inode是從1編號,這一點不同於block。

需要格外注意。另外,/dev/hdd1是新掛載的硬碟,格式化成ext2後並沒有任何資料,只有一個lost+found目錄。接下來我們用命令“dd if=/dev/hdd1 of=。/gp0 bs=4096 count=32768”將Group0裡的所有資料提取出來。

前面已經瞭解了Group0的一些基本資訊如下:

Group 0: (Blocks 0-32767) Primary superblock at 0, Group descriptors at 1-1 Reserved GDT blocks at 2-512 Block bitmap at 513 (+513), Inode bitmap at 514 (+514) Inode table at 515-1026 (+515) 31739 free blocks, 16374 free inodes, 1 directories #包含一個目錄 Free blocks: 1028-1031, 1033-32767 #一共有31739個空閒block Free inodes: 11-16384 #一共有16374個空閒inode

一個block bitmap佔用一個block大小,而block bitmap中每個bit表示一個對應block的佔用情況,0表示對應的block為空,為1表示相應的block中存有資料。在/dev/hdd1中,一個group裡最多隻能包含8×4096=32768個block,這一點我們已經清楚了。接下來我們來看一下Group0的block bitmap,如下:

「底層原理」一層層剝開檔案系統的面紗,徹底理解Linux檔案系統

發現block bitmap的前128位元組和第129位元組的低4位都為1,說明發現Group0中前128×8+4=1028個block,即block0block1027都已被使用了。第129位元組的高4位為0,表示block1028block1031四個block是空閒的;第130位元組的最低位是1,說明block1032被佔用了;從block1033~block32767的block bitmap都是0,所以這些block都是空閒的,和上表輸出的結果一致。

inode bitmap

和block bitmap類似,innode bitmap的每個位元表示相應的inode是否被使用。Group0的inode bitmap如下:

「底層原理」一層層剝開檔案系統的面紗,徹底理解Linux檔案系統

/dev/hdd1裡inode總數為1048576,要被均分到64個Group裡,所以每個Group中都包含了16384個inode。要表示每個Group中16384個inode,inode bitmap總共需要使用2048(16384/8)位元組。inode bitmap本身就佔據了一個block,所以它只用到了該block中的前2048個位元組,剩下的2048位元組都被填充成1,如上圖所示。

我們可以看到Group0中的inode bitmap前兩個位元組分別是ff和03,說明Group0裡的前11個inode已經被使用了。其中前10個inode被ext2預留起來,第11個inode就是lost+found目錄,如下:

「底層原理」一層層剝開檔案系統的面紗,徹底理解Linux檔案系統

inode table

那麼每個Group中的所有inode到底存放在哪裡呢?答案就是inode table。它是每個Group中所有inode的聚合地。

因為一個inode佔128位元組,所以每個Group裡的所有inode共佔16384×128=2097152位元組,總共消耗了512個block。Group的group descriptor裡記錄了inode table的所佔block的起始號,所以就可以唯一確定每個Group裡inode table所在的block的起始號和結束號了。inode的結構如下:

「底層原理」一層層剝開檔案系統的面紗,徹底理解Linux檔案系統

這裡我們主要關注的其中的資料block指標部分。前12個block指標直接指向了存有資料的block號;第13個block指標所指向的block中儲存的並不是資料而是由其他block號,這些block號所對應的block裡儲存的才是真正的資料,即所謂的兩級block指標;第14個block為三級block指標;第15個block為四級block指標。最後效果圖如下:

「底層原理」一層層剝開檔案系統的面紗,徹底理解Linux檔案系統

一個block為4096位元組,每個塊指標4位元組,所以一個block裡最多可以容納4096/4=1024個block指標,我們可以計算出一個inode最大能表示的單個檔案的最大容量如下:

直接block指標(位元組)

兩級block指標(位元組)

三 級block指標(位元組)

四 級block指標(位元組)

單個檔案的最大容量(位元組)

12×409

4096/4×4096

40962/4×4096

40963/4×4096

4TB

所以,我們可以得出不同block大小,對單個檔案最大容量的影響。假設block大小為X位元組,則:

「底層原理」一層層剝開檔案系統的面紗,徹底理解Linux檔案系統

如下表所示:

block大小(位元組)

單個檔案容量(位元組)

1024

17247240192位元組(16GB)

2048

275415826432位元組(256GB)

4096

4402345672704位元組(4TB)

最後來一張全家福:

「底層原理」一層層剝開檔案系統的面紗,徹底理解Linux檔案系統

相關文章

頂部