提到文件系統,linux的老江湖們對這個概念當然不會陌生,然而剛接觸Linux的新手們就會被文件系統這個概念弄得暈頭轉向,恰好我當年正好屬于後者。
從windows下轉到Linux的童鞋聽到最多的應該是fat32和ntfs(在windows 2000之後所出現的一種新型的日志文件系統),那個年代經常聽到說“我要把C盤格式化成ntfs格式,D盤格式化成fat32格式”。
一到Linux下,很多入門Linux的書籍中當牽扯到文件系統這個術語時,二話不說,不管三七二十一就給出了下面這個圖,然後逐一解釋一下每個目錄是拿來幹啥的、裡面會放什麼類型的文件就完事兒了,弄得初學者經常“丈二和尚摸不着頭腦”。
本文的目的就是和大家分享一下我當初是如何學習Linux的文件系統的,也算是一個“老”油條的一些心得吧。
“文件系統”的主語是“文件”,那麼文件系統的意思就是“用于管理文件的(管理)系統”,在大多數操作系統教材裡,“文件是數據的集合”這個基本點是一緻的,而這些數據最終都是存儲在存儲介質裡,如硬盤、光盤、U盤等。
另一方面,用戶在管理數據時也是文件為基本單位,他們所關心的問題是:
簡而言之,文件系統就是一套用于定義文件的命名和組織數據的規範,其根本目的是便對文件進行查詢和存取。
虛拟文件系統VFS在Linux早期設計階段,文件系統與内核代碼是整合在一起的,這樣做的缺點是顯而易見的。假如,我的系統隻能識别ext3格式的文件系統,我的U盤是fat32格式,那麼很不幸的是我的U盤将不會被我的系統所識别,
為了支持不同種類的文件系統,Linux采用了在Unix系統中已經廣泛采用的設計思想,通過虛拟文件系統VFS來屏蔽下層各種不同類型文件系統的實現細節和差異。
其實VFS最早是由Sun公司提出的,其基本思想是将各種文件系統的公共部分抽取出來,形成一個抽象層。對用戶的應用程序而言,VFS提供了文件系統的系統調用接口。而對具體的文件系統來說,VFS通過一系列統一的外部接口屏蔽了實現細節,使得對文件的操作不再關心下層文件系統的類型,更不用關心具體的存儲介質,這一切都是透明的。
ext2文件系統虛拟文件系統VFS是對各種文件系統的一個抽象層,抽取其共性,以便對外提供統一管理接口,便于内核對不同種類的文件系統進行管理。那麼首先我們得看一下對于一個具體的文件系統,我們該關注重點在哪裡。
對于存儲設備(以硬盤為例)上的數據,可分為兩部分:
我們今天要讨論的就是這些元數據。這裡有個概念首先需要明确一下:塊設備。所謂塊設備就是以塊為基本讀寫單位的設備,支持緩沖和随機訪問。每個文件系統提供的mk2fs.xx工具都支持在構建文件系統時由用戶指定塊大小,當然用戶不指定時會有一個缺省值。
我們知道一般硬盤的每個扇區512字節,而多個相鄰的若幹扇區就構成了一個簇,從文件系統的角度看這個簇對應的就是我們這裡所說塊。用戶從上層下發的數據首先被緩存在塊設備的緩存裡,當寫滿一個塊時數據才會被發給硬盤驅動程序将數據最終寫到存儲介質上。如果想将設備緩存中數據立即寫到存儲介質上可以通過sync命令來完成。
塊越大存儲性能越好,但浪費比較嚴重;塊越小空間利用率較高,但性能相對較低。如果你不是專業的“骨灰級”玩兒家,在存儲設備上構建文件系統時,塊大小就用默認值。通過命令“tune2fs -l /dev/sda1”可以查看該存儲設備上文件系統所使用的塊大小:
[root@localhost ~]#
該命令已經暴露了文件系統的很多信息,接下我們将詳細分析它們。
下圖是我的虛拟機的情況,三塊IDE的硬盤。容量分别是:
hda: 37580963840/(102410241024)=35GB hdb: 8589934592/(102410241024)=8GB hdd: 8589934592/(102410241024)=8GB
如果這是三塊實際的物理硬盤的話,廠家所标稱的容量就分别是37.5GB、8.5GB和8.5GB。可能有些童鞋覺得虛拟機有點“假”,那麼我就來看看實際硬盤到底是個啥樣子。
主角1:西部數據 500G SATA接口 CentOS 5.5
實際容量:500107862016B = 465.7GB
主角2:希捷 160G SCSI接口 CentOS 5.5
實際容量:160041885696B=149GB
大家可以看到,VMware公司的水平還是相當不錯的,虛拟硬盤和物理硬盤“根本”看不出差别,畢竟屬于雲平台基礎架構支撐者的風雲人物嘛。
相關視頻推薦
3個linux内核的秘密,讓你徹底搞懂文件系統
剖析Linux内核虛拟文件系統(VFS)架構
學習地址:C/C Linux服務器開發/後台架構師【零聲教育】-學習視頻教程-騰訊課堂
需要C/C Linux服務器架構師學習資料加qun812855908(資料包括C/C ,Linux,golang技術,内核,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒體,CDN,P2P,K8S,Docker,TCP/IP,協程,DPDK,ffmpeg,大廠面試題 等)
以硬盤/dev/hdd1為例,它是我新增的一塊新盤,格式化成ext2後,根目錄下隻有一個lost found目錄,讓我們來看一下它的布局情況,以此來開始我們的文件系統之旅。
對于使用了ext2文件系統的分區來說,有一個叫superblock的結構 ,superblock的大小為1024字節,其實ext3的superblock也是1024字節。下面的小程序可以證明這一點:
#include <stdio.h>
#include <linux/ext2_fs.h>
#include <linux/ext3_fs.h>
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=1024
sizeof 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 <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <linux/ext2_fs.h>
#include <linux/ext3_fs.h>
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 : 1048576
block count : 2097065
Reserved blocks count : 104853
Free blocks count : 2059546
Free inodes count : 1048565
First Data Block : 0
Block size : 4096
Fragment size : 4096
Blocks per group : 32768
Fragments per group : 32768
inodes per group : 16384
Magic signature : 0xef53
size 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,詳情如下:
用命令tune2fs可以驗證我們之前的分析:
再通過命令dumpe2fs /dev/hdd1的輸出,可以得到我們關注如下部分:
接下來以Group0為例,主superblock在Group0的block0裡,根據前面的分析,我們可以畫出主superblock在block0中的位置如下:
因為superblock是如此之重要,一旦它出錯你的整個系統就玩兒完了,所以文件系統中會存在磁盤的多個不同位置會存在主superblock的備份副本,一旦系統出問題後還可以通過備份的superblock對文件系統進行修複。
第一版ext2文件系統的實現裡,每個Group裡都存在一份superblock的副本,然而這樣做的負面效果也是相當明顯,那就是嚴重降低了磁盤的空間利用率。所以在後續ext2的實現代碼中,選擇用于備份superblock的Group組号的原則是3的N次方、5的N次方、7的N次方其中N=0,1,2,3…。根據這個公式我們來計算一下/dev/hdd1中備份有supeblock的Group号:
也就是說Group1、3、5、7、9、25、27、49裡都保存有superblock的拷貝,如下:
用block号分别除以32768就得到了備份superblock的Group号,和我們在上面看到的結果一緻。我們來看一下/dev/hdd1中Group和block的關系:
從上圖中我們可以清晰地看出在使用了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 descriptor
struct 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的輸出對比如下:
其中,文件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,如下:
發現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如下:
/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目錄,如下:
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的結構如下:
這裡我們主要關注的其中的數據block指針部分。前12個block指針直接指向了存有數據的block号;第13個block指針所指向的block中存儲的并不是數據而是由其他block号,這些block号所對應的block裡存儲的才是真正的數據,即所謂的兩級block指針;第14個block為三級block指針;第15個block為四級block指針。最後效果圖如下:
一個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字節,則:
如下表所示:
block大小(字節) |
單個文件容量(字節) |
1024 |
17247240192字節(16GB) |
2048 |
275415826432字節(256GB) |
4096 |
4402345672704字節(4TB) |
最後來一張全家福:
,更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!