文件系統
概述
提到文件系統,Linux的老江湖們對這個概念當然不會陌生,然而剛接觸Linux的新手們就會被文件系統這個概念弄得暈頭轉向,恰好我當年正好屬于后者。
從windows下轉到Linux的童鞋聽到最多的應該是fat32和ntfs(在windows 2000之后所出現的一種新型的日志文件系統),那個年代經常聽到說“我要把C盤格式化成ntfs格式,D盤格式化成fat32格式”。
一到Linux下,很多入門Linux的書籍中當牽扯到文件系統這個術語時,二話不說,不管三七二十一就給出了下面這個圖,然后逐一解釋一下每個目錄是拿來干啥的、里面會放什么類型的文件就完事兒了,弄得初學者經常“丈二和尚摸不著頭腦”。
本文的目的就是和大家分享一下我當初是如何學習Linux的文件系統的,也算是一個“老”油條的一些心得吧。
“文件系統”的主語是“文件”,那么文件系統的意思就是“用于管理文件的(管理)系統”,在大多數操作系統教材里,“文件是數據的集合”這個基本點是一致的,而這些數據最終都是存儲在存儲介質里,如硬盤、光盤、U盤等。
另一方面,用戶在管理數據時也是文件為基本單位,他們所關心的問題是:
-
? 1.我的文件在什么地方放著?
-
? 2.我如何將數據存入某個文件?
-
? 3.如何從文件里將數據讀出來?
-
? 4.不再需要的文件怎么將其刪除?
簡而言之,文件系統就是一套用于定義文件的命名和組織數據的規范,其根本目的是便對文件進行查詢和存取。
虛擬文件系統VFS
在Linux早期設計階段,文件系統與內核代碼是整合在一起的,這樣做的缺點是顯而易見的。假如,我的系統只能識別ext3格式的文件系統,我的U盤是fat32格式,那么很不幸的是我的U盤將不會被我的系統所識別,
為了支持不同種類的文件系統,Linux采用了在Unix系統中已經廣泛采用的設計思想,通過虛擬文件系統VFS來屏蔽下層各種不同類型文件系統的實現細節和差異。
其實VFS最早是由Sun公司提出的,其基本思想是將各種文件系統的公共部分抽取出來,形成一個抽象層。對用戶的應用程序而言,VFS提供了文件系統的系統調用接口。而對具體的文件系統來說,VFS通過一系列統一的外部接口屏蔽了實現細節,使得對文件的操作不再關心下層文件系統的類型,更不用關心具體的存儲介質,這一切都是透明的。
ext2文件系統
虛擬文件系統VFS是對各種文件系統的一個抽象層,抽取其共性,以便對外提供統一管理接口,便于內核對不同種類的文件系統進行管理。那么首先我們得看一下對于一個具體的文件系統,我們該關注重點在哪里。
對于存儲設備(以硬盤為例)上的數據,可分為兩部分:
-
?用戶數據:存儲用戶實際數據的部分;
-
?管理數據:用于管理這些數據的部分,這部分我們通常叫它元數據(metadata)。
我們今天要討論的就是這些元數據。這里有個概念首先需要明確一下:塊設備。所謂塊設備就是以塊為基本讀寫單位的設備,支持緩沖和隨機訪問。每個文件系統提供的mk2fs.xx工具都支持在構建文件系統時由用戶指定塊大小,當然用戶不指定時會有一個缺省值。
我們知道一般硬盤的每個扇區512字節,而多個相鄰的若干扇區就構成了一個簇,從文件系統的角度看這個簇對應的就是我們這里所說塊。用戶從上層下發的數據首先被緩存在塊設備的緩存里,當寫滿一個塊時數據才會被發給硬盤驅動程序將數據最終寫到存儲介質上。如果想將設備緩存中數據立即寫到存儲介質上可以通過sync命令來完成。
塊越大存儲性能越好,但浪費比較嚴重;塊越小空間利用率較高,但性能相對較低。如果你不是專業的“骨灰級”玩兒家,在存儲設備上構建文件系統時,塊大小就用默認值。通過命令“tune2fs -l /dev/sda1”可以查看該存儲設備上文件系統所使用的塊大?。?/p>
[root@localhost~]#
tune2fs-l/dev/sda1
tune2fs1.39(29-May-2006) Filesystemvolumename:/boot Lastmountedon: FilesystemUUID:6ade5e49-ddab-4bf1-9a45-a0a742995775 Filesystemmagicnumber:0xEF53 Filesystemrevision#:1(dynamic) Filesystemfeatures:has_journalext_attrresize_inodedir_indexfiletypeneeds_recoverysparse_super Defaultmountoptions:user_xattracl Filesystemstate:clean Errorsbehavior:Continue FilesystemOStype:Linux Inodecount:38152 Blockcount:152584 Reservedblockcount:7629 Freeblocks:130852 Freeinodes:38111 Firstblock:1 Blocksize:1024 Fragmentsize:1024 ReservedGDTblocks:256 Blockspergroup:8192 Fragmentspergroup:8192 Inodespergroup:2008 Inodeblockspergroup:251 Filesystemcreated:ThuDec1300:42:522012 Lastmounttime:TueNov2010:35:282012 Lastwritetime:TueNov2010:35:282012 Mountcount:12 Maximummountcount:-1 Lastchecked:ThuDec1300:42:522012 Checkinterval:0() Reservedblocksuid:0(userroot) Reservedblocksgid:0(grouproot) Firstinode:11 Inodesize:128 Journalinode:8 Defaultdirectoryhash:tea DirectoryHashSeed:72070587-1b60-42de-bd8b-a7b7eb7cbe63 Journalbackup:inodeblocks
該命令已經暴露了文件系統的很多信息,接下我們將詳細分析它們。
下圖是我的虛擬機的情況,三塊IDE的硬盤。容量分別是:
hda: 37580963840/(102410241024)=35GB
hdb: 8589934592/(102410241024)=8GB
hdd: 8589934592/(102410241024)=8GB
如果這是三塊實際的物理硬盤的話,廠家所標稱的容量就分別是37.5GB、8.5GB和8.5GB??赡苡行┩X得虛擬機有點“假”,那么我就來看看實際硬盤到底是個啥樣子。
主角1:西部數據 500G SATA接口 CentOS 5.5
實際容量:500107862016B = 465.7GB
主角2:希捷 160G SCSI接口 CentOS 5.5
實際容量:160041885696B=149GB
大家可以看到,VMware公司的水平還是相當不錯的,虛擬硬盤和物理硬盤“根本”看不出差別,畢竟屬于云平臺基礎架構支撐者的風云人物嘛。
以硬盤/dev/hdd1為例,它是我新增的一塊新盤,格式化成ext2后,根目錄下只有一個lost+found目錄,讓我們來看一下它的布局情況,以此來開始我們的文件系統之旅。
對于使用了ext2文件系統的分區來說,有一個叫superblock的結構,superblock的大小為1024字節,其實ext3的superblock也是1024字節。下面的小程序可以證明這一點:
#include
#include
#include
intmain(intargc,char**argv){
printf("sizeofofext2superblock=%d
",sizeof(structext2_super_block));
printf("sizeofofext3superblock=%d
",sizeof(structext3_super_block));
return0;
}
******************【運行結果】******************
sizeofofext2superblock=1024
sizeofofext3superblock=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
intmain(intargc,char**argv){
printf("sizeofofext2superblock=%d
",sizeof(structext2_super_block));
printf("sizeofofext3superblock=%d
",sizeof(structext3_super_block));
charbuf[1024]={0};
intfd=-1;
structext2_super_blockhdd1sb;
memset(&hdd1sb,0,1024);
if(-1==(fd=open("./hdd1sb",O_RDONLY,0777))){
printf("openfileerror!
");
return1;
}
if(-1==read(fd,buf,1024)){
printf("readerror!
");
close(fd);
return1;
}
memcpy((char*)&hdd1sb,buf,1024);
printf("inodecount:%ld
",hdd1sb.s_inodes_count);
printf("blockcount:%ld
",hdd1sb.s_blocks_count);
printf("Reservedblockscount:%ld
",hdd1sb.s_r_blocks_count);
printf("Freeblockscount:%ld
",hdd1sb.s_free_blocks_count);
printf("Freeinodescount:%ld
",hdd1sb.s_free_inodes_count);
printf("FirstDataBlock:%ld
",hdd1sb.s_first_data_block);
printf("Blocksize:%ld
",1<<(hdd1sb.s_log_block_size+10));
printf("Fragmentsize:%ld
",1<<(hdd1sb.s_log_frag_size+10));
printf("Blockspergroup:%ld
",hdd1sb.s_blocks_per_group);
printf("Fragmentspergroup:%ld
",hdd1sb.s_frags_per_group);
printf("Inodespergroup:%ld
",hdd1sb.s_inodes_per_group);
printf("Magicsignature:0x%x
",hdd1sb.s_magic);
printf("sizeofinodestructure:%d
",hdd1sb.s_inode_size);
close(fd);
return0;
}
******************【運行結果】******************
inodecount:1048576
blockcount:2097065
Reservedblockscount:104853
Freeblockscount:2059546
Freeinodescount:1048565
FirstDataBlock:0
Blocksize:4096
Fragmentsize:4096
Blockspergroup:32768
Fragmentspergroup:32768
Inodespergroup:16384
Magicsignature:0xef53
sizeofinodestructure: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向下擴展。
【底層原理】徹底理解Linux文件系統(二)
superblock,inode,block,group,group descriptor,block bitmap,inode table
下面我們來認識一下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中:
//Structureofablocksgroupdescriptor
structext2_group_desc
{
__le32bg_block_bitmap;/*group中blockbitmap所在的第一個block號*/
__le32bg_inode_bitmap;/*group中inodebitmap所在的第一個block號*/
__le32bg_inode_table;/*group中inodestable所在的第一個block號*/
__le16bg_free_blocks_count;/*group中空閑的block總數*/
__le16bg_free_inodes_count;/*group中空閑的inode總數*/
__le16bg_used_dirs_count;/*目錄數*/
__le16bg_pad;
__le32bg_reserved[3];
};
下面的程序可以幫助了解一下/dev/hdd1中所有group的descriptor的詳情:
#defineB_LEN32//一個struct ext2_group_desc{}占固定32字節
intmain(intargc,char**argv){
charbuf[B_LEN]={0};
inti=0,fd=-1;
structext2_group_descgd;
memset(&gd,0,B_LEN);
if(-1==(fd=open(argv[1],O_RDONLY,0777))){
printf("openfileerror!
");
return1;
}
while(i<64){//因為我已經知道了/dev/hdd1中只有64個group
if(-1==read(fd,buf,B_LEN)){
printf("readerror!
");
close(fd);
return1;
}
memcpy((char*)&gd,buf,B_LEN);
printf("==========Group%d:==========
",i);
printf("Blocksbitmapblock%ld
",gd.bg_block_bitmap);
printf("Inodesbitmapblock%ld
",gd.bg_inode_bitmap);
printf("Inodestableblock%ld
",gd.bg_inode_table);
printf("Freeblockscount%d
",gd.bg_free_blocks_count);
printf("Freeinodescount%d
",gd.bg_free_inodes_count);
printf("Directoriescount%d
",gd.bg_used_dirs_count);
memset(buf,0,B_LEN);
i++;
}
close(fd);
return0;
}
運行結果和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的一些基本信息如下:
Group0:(Blocks0-32767)
Primarysuperblockat0,Groupdescriptorsat1-1
ReservedGDTblocksat2-512
Blockbitmapat513(+513),Inodebitmapat514(+514)
Inodetableat515-1026(+515)
31739freeblocks,16374freeinodes,1directories#包含一個目錄
Freeblocks:1028-1031,1033-32767#一共有31739個空閑block
Freeinodes: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) |
最后來一張全家福:
審核編輯 :李倩
-
Linux
+關注
關注
87文章
11292瀏覽量
209332 -
文件系統
+關注
關注
0文章
284瀏覽量
19904
原文標題:【底層原理】徹底理解Linux文件系統(二)
文章出處:【微信號:yikoulinux,微信公眾號:一口Linux】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論