VFS檔案系統結構分析,vfs系統結構分析
本文乃fireaxe原創,使用GPL發布,可以自由拷貝,轉載。但轉載請保持文檔的完整性,並註明原作者及原連結。內容可任意使用,但對因使用該內容引起的後果不做任何保證。
作者:fireaxe_hq@hotmail.com部落格:fireaxe.blog.chinaunix.net
VFS是Linux非常核心的一個概念,linux下的大部分操作都要用到VFS的相關功能。這裡從使用者的角度,對VFS進行了簡單說明。使用者不但需要知道Linux下有哪些檔案操作的函數,還需要對VFS的結構有一個比較清晰的瞭解,才能更好的使用它。例如hard link 與symbolic,如果沒有VFS結構的相瞭解,就無法搞清楚如何使用它們。
本文首先是建立了一個簡單的目錄模型,然後介紹該目錄在VFS的結構,最終總結出如何使用各個檔案操作函數。
本著簡單使用的原則,主要使用了分析加猜測的方法。鑒於本人水平有限,文中不免會有些錯誤。歡迎各位讀者理性閱讀,大膽批判。您的批判是我進步的動力。
1 目錄模型
以下面的目錄為例。
dir為第一級目錄,dir中有subdir0與subdir1兩個子目錄與一個檔案file0。“subdir0”中有兩個檔案file1與file0。subdir1中有一個檔案file3。
2 VFS的概念
VFS是Linux中的一個虛擬檔案檔案系統,也稱為虛擬檔案系統交換層(Virtual Filesystem Switch)。它為應用程式員提供一層抽象,屏蔽底層各種檔案系統的差異。如所示:
不同的檔案系統,如Ext2/3、XFS、FAT32等,具有不同的結構,假如使用者調用open等檔案IO函數去開啟檔案,具體的實現會非常不同。為了屏蔽這種差異,Linux引入了VFS的概念。相當於是Linux自建了一個新的貯存在記憶體中的檔案系統。所有其他檔案系統都需要先轉換成VFS的結構才能為使用者所調用。
3 VFS的構建
所謂VFS的構建就是載入實際檔案系統的過程,也就是mount被調用的過程。如所示,以mount一個ext2的檔案系統為例。
這是一個經過簡化的Ext2磁碟結構,只是用於說明用它構建VFS的基本過程。
mount命令的一般形式為:mount /dev/sdb1 /mnt/mysdb1
/dev/sdb1是裝置名稱,/mnt/mysdb1是掛載點。
VFS檔案系統的基本結構是dentry結構體與inode結構體。
Dentry代表一個檔案目錄中的一個點,可以是目錄也可以是檔案。
Inode代表一個在磁碟上的檔案,它與磁碟檔案一一對應。
Inode與dentry不一定一一對應,一個inode可能會對應多個dentry項。(hard link)
Mount時,linux首先找到磁碟分割的super block,然後通過解析磁碟的inode table與file data,構建出自己的dentry列表與indoe列表。
需要注意的是,VFS實際上是按照Ext的方式進行構建的,所以兩者非常相似(畢竟Ext是Linux的原生檔案系統)。
比如inode節點,Ext與VFS中都把檔案管理結構稱為inode,但實際上它們是不一樣的。Ext的inode節點在磁碟上;VFS的inode節點在記憶體裡。Ext-inode中的一些成員變數其實是沒有用的,如引用計數等。保留它們的目的是為了與vfs-node保持一致。這樣在用ext-inode節點構造vfs-inode節點時,就不需要一個一個賦值,只需一次記憶體拷貝即可。
如果是非EXT格式的磁碟,就沒有這麼幸運了,所以mount非EXT磁碟會慢一些。
4 VFS的結構
構建出VFS檔案系統後,下一步是把第一節中提到的目錄模型映射到VFS結構體系中。
上文提到了VFS主要由denty與inode構成。Dentry用於維護VFS的目錄結構,每個dentry項就代表著我們用ls時看的的一項(每個目錄和每個檔案都對應著一個dentry項)。Inode為檔案節點,它與檔案一一對應。Linux中,目錄也是一種檔案,所以dentry也會對應一個inode節點。
是第一節中的目錄模型在VFS中的結構。
5 Dentry cache
每個檔案都要對應一個inode節點與至少一個dentry項。假設我們有一個100G的硬碟,上面寫滿了空檔案,那個需要多少記憶體才能重建VFS呢?
檔案最少要佔用1個block(一般是4K)。假一個dentry與一個inode需要100byte,則dentry與inode需要佔用1/40的空間。100G硬碟則需要2.5G空間。最近都開始換裝1T硬碟了,需要 25G的記憶體才能放下inode與dentry,相信沒有幾台電腦可以承受。
為了避免資源浪費,VFS採用了dentry cache的設計。
當有使用者用ls命令查看某一個目錄或用open命令開啟一個檔案時,VFS會為這裡用的每個目錄項與檔案建立dentry項與inode,即“按需建立”。然後維護一個LRU(Least Recently Used)列表,當Linux認為VFS佔用太多資源時,VFS會釋放掉長時間沒有被使用的dentry項與inode項。
需要注意的是:這裡的建立於釋放是從記憶體佔用的角度看。從Linux角度看,dentry與inode是VFS中固有的東西。所不同的只是VFS是否把dentry與inode讀到了記憶體中。對於Ext2/3檔案系統,構建dentry與inode的過程非常簡單,但對於其他檔案系統,則會慢得多。
瞭解了Dentry cache的概念,才能明白為何下面會有兩種定位檔案的方式。
6 無dentry時定位檔案
因為上面提到的Dentry Cache,VFS並不能保證隨時都有dentry項與inode項可用。下面是無dentry項與inode項時的定位方式。
為了簡化問題,這裡假設已經找到了dir的dentry項(找到dentry的過程會在後面講解)。
首先,通過dir對應的dentry0找到inode0節點,有了inode節點就可以讀取目錄中的資訊。其中包含了該目錄包含的下一級目錄與檔案檔案清單,包括name與inode號。實際上用ls命令查看的就是這些資訊。“ls -i”會顯示出檔案的inode號。
|
> ls -i
975248 subdir0 975247 subdir1 975251 file0 |
然後,根據通過根據subdir0對應的inode號重建inode2,並通過檔案資料(目錄也是檔案)與inode2重建subdir0的dentry節點:dentry1。
|
> ls -i
975311 file1 975312 file2 |
接著,根據file1對應的inode號重建inode4,並通過檔案資料與inode4重建file1的dentry節點。
最後,就可以通過inode4節點訪問檔案了。
注意:檔案對應的inode號是確定的,只是inode結構體需要重新構造。
7 有dentry時定位檔案
一旦在Dentry cache中建立了dentry項,下次訪問就很方便了。
Dentry中的一個關鍵變數是d_subdirs,它儲存了下一級目錄的列表,用於快速定位檔案。
首先,在代表dir目錄的dentry0的d_subdirs中尋找名字為“subdir0”的dentry項,找到了dentry1。
然後在dentry1中尋找名字為“file1”的dentry項,然後找到了file1對應的dentry項,
最後通過file1對應的dentry項獲得file1對應的inode4。
與無dentry項時比較,有dentry項時的操作精簡了許多。
8 Symbolic link
建立symboliclink的命令為 :ln -s 源檔案 目標檔案
Linux中的symbolic link類似於Windows系統中的捷徑。如所示,symlink1是指向file1的symbolic link。symlink1本身也是檔案,因此有自己獨立的inode節點。symlink中實際儲存的是源檔案的相對路徑。
大部分檔案操作會直接對symbolic link指向的目標進行操作,比如open(“symlikn1”),實際上開啟的是file3。
如果file3不在會發生什麼事情呢?open函數照樣會按照symlink1中的檔案路徑開啟檔案。但file3不存在,因此會報錯說檔案不存在。
9 hard link
Linux除了symbolic link,還有hard link的概念。
Hard link建立實際上是dentry項的一個拷貝,它們都指向同一個inode節點。當我們使用write改寫file1的內容時,hardlink1的內容也會被改寫,因為所以實際上它們是同一個檔案。
如所示,hardlink1是file1的一個hard link。它們都指向同一個inode1節點。Inode1中有一個計數器,用於記錄有幾個dentry項指向它。刪除任意一個dentry項都不會導致inode1的刪除。只有所有指向inode1的dentry都被刪除了,inode1才會被刪除。
他們實際
從某種意義上講,所有dentry項都是hard link。
10 進程對檔案的管理
進程式控制制塊task_struct中有兩個變數與檔案有關:fs與files。
files中儲存著root與pwd兩個指向dentry項的指標。使用者定路徑時,絕對路徑會通過root進行定位;相對路徑會的通過pwd進行定位。(一個進程的root不一定是檔案系統的根目錄。比如ftp進程的根目錄不是檔案系統的根目錄,這樣才能保證使用者只能訪問ftp目錄下的內容)
fs是一個file object列表,其中每一個節點對應著一個被開啟了的檔案。當進程定位到檔案時,會構造一個file object,並通過f_inode 關聯到inode節點。檔案關閉時(close),進程會釋放對應對應file object。File object中的f_mode是開啟時選擇的許可權,f_pos為讀寫位置。當開啟同一個檔案多次時,每次都會構造一個新的file object。每個file object中有獨立的f_mode與f_pos。
11 open的過程
開啟檔案涉及到裡一系列的結構調整,這裡分步驟進行說明:
首先建立一個檔案管理結構,如所示,該進程已經開啟了兩個檔案,接下來我們再開啟一個新檔案。
第一步:找到檔案;
從上文中能定位到我們檔案的inode節點,找到了inode節點也就找到了檔案。
第二步:建立file object;
建立一個新的file object對象,放入file object對象列表,並把它指向inode節點。
第三步:建立file descriptor
file descriptor就是進程式控制制塊task_struct中files中維護的fd_array。因為是數組,所以file descriptor實際上已經預先分配好空間了,這裡這是需要把某個閒置file descriptor與file object關聯起來。這個file descriptor在數組中的索引號就是open檔案時得到的檔案fd。
12 open與dup
同一個檔案是可以open多次的,結構如所示。每次open都會建立一個新的file descriptor與file object。然後指向同一個檔案的inode節點。中,假設open的檔案與fd1指向的是同一個檔案,則新建立的file object 2與fd1的file object 2會指向同一個inode2節點。
Linux還提供了dup功能,用於複製file descriptor。使用dup不會建立新的非file object,所以建立立的file descriptor會與原filedescriptor同時指向同一個file object。中,我們通過dup(fd1)得到了fd2,則fd2與fd1指向了同一個file object2。
兩次open後由於會產生新的object,所以檔案讀寫屬性、檔案讀寫位置(f_pos)等資訊都是獨立的。使用dup複製file descriptor後,由於沒有獨立的object,所以修改某個fd的屬性或檔案讀寫位置後,另一個fd也會隨之變化。
13 Fork對開啟檔案的影響
Dup的操作與fork一個子進程時的操作類似。
是已有父進程的檔案結構:
使用fork後的結構如下。同樣是沒有建立新的file object,因此當對parent process中的fd1進行檔案指標的移動時(如讀寫),child process中的fd1也會受影響。也即是說opened files list不是進程的一部分,因此不會被複製。Opened files list應該是一個全域性的資源鏈表,進程維護的是一個指標列表fd table,所以被複製的只是指標列表fd table,而不是opened files list。
14 檔案操作函數解析
通過上面的分析,可以對各個函數的範圍與使用方式有更清晰的瞭解。下面列出了常用的檔案操作:
|
函數名 |
作用對象 |
說明 |
|
creat |
dentry, inode |
建立檔案時會建立新的dentry與inode |
|
open |
file object |
如果檔案不存在,且有O_CREAT參數,則會先調用creat |
|
close |
file object |
刪除file object,但不會刪除檔案。 |
|
state/lstate |
inode |
讀取inode的內容。如果目標是symbolic link,stat會讀取symbolic link指向的內容;lstat則會讀取symbolic link檔案本身。 |
|
chmod |
file object |
改變file object中的f_mode |
|
chown/lchown |
file object |
改變file object中的f_uid與f_gid |
|
truncate |
inode |
改變檔案長度。 |
|
read |
file object |
讀檔案會改變file object中的f_pos |
|
write |
file object,inode |
寫檔案改變file object中的f_pos的同時也會改變檔案內容與更新修改時間。 |
|
dup |
file object |
建立一個新的file object項 |
|
seek/lseek |
file object |
改變file object中的f_pos |
|
link |
dentry |
建立新的dentry項,指向同一個inode節點。 |
|
unlink |
dentry |
刪除一個dentry項。如果該dentry指向的inode節點沒有被其他dentry項使用,則刪除inode節點與磁碟檔案。 |
|
rename |
dentry |
修改dentry相中的d_name |
|
readlink |
----------- |
read無法讀取symbolic link 檔案的內容,需要使用readlink讀取 |
|
symlink |
dentry, inode |
作用與creat類似,但建立的檔案屬性為symbolic link。 |
註:磁碟檔案與inode節點一一對應,所以在表中不再單獨列出磁碟檔案。
參考檔案:
Advanced Programming in the UNIX Environment (3rd) W. Richard Stevens & Stephen A. Rago
Understanding the Linux Kernel (3rd) Daniel P. Bovet & Marco Cesati
什是虛擬檔案系統?windows常使用的檔案系統有什?Linux使用的檔案系統有什?
虛擬檔案系統 Virtual File Systems
虛擬檔案系統(VFS)是由Sun icrosystems公司在定義網路檔案系統(NFS)時創造的。它是一種用於網路環境的Distributed File System,是允許和作業系統使用不同的檔案系統實現的介面。
虛擬檔案系統(VFS)是物理檔案系統與服務之間的一個介面層,它對Linux的每個檔案系統的所有細節進行抽象,使得不同的檔案系統在Linux核心以及系統中啟動並執行其他進程看來,都是相同的。嚴格說來,VFS並不是一種實際的檔案系統。它只存在於記憶體中,不存在於任何外存空間。VFS在系統啟動時建立,在系統關閉時消亡。
VFS使Linux同時安裝、支援許多不同類型的檔案系統成為可能。VFS擁有關於各種特殊檔案系統的公用介面,如超級塊、inode、檔案操作函數入口等。實際檔案系統的細節,統一由VFS的公用介面來索引,它們對系統核心和使用者進程來說是透明的。
VFS的功能包括:紀錄可用的檔案系統的類型;將裝置同對應的檔案系統聯絡起來;處理一些面向檔案的通用操作;涉及到針對檔案系統的操作時,VFS把它們影射到與控制檔案、目錄以及inode相關的物理檔案系統。
當某個進程發布了一個面向檔案的系統調用時,核心將調用VFS中相應的函數,這個函數處理一些與物理結構無關的操作,並且把它重新導向為真實檔案系統中相應的函數調用,後者則用來處理那些與物理結構相關的操作
典型嵌入式linux軟體部分由什模組組成?他們的功可以及相互聯絡? Bootloader分為哪兩階段?分
從軟體硬體設計特點簡單描述嵌入式產品開發設計流程。
項目論證階段:項目的可行性分析並形成可行性研究報告。
系統方案階段:對產品需求加以分析、細化,並抽象出需要完成的功能列表,明確定義所要完成的任務。
系統設計階段:軟體開發部分完成軟體需求分析,形成軟體總體設計方案,軟體開發介面規範等;硬體部分完成硬體總體設計方案,介面定義及說明等。
產品詳細設計階段:完成軟/硬體的詳細設計,編製代碼,形成軟體各模組的設計說明;硬體部分各單板的原理圖,PCB和料單,同時完成產品的結構設計。
製造聯試階段:完成產品的系統調試和可靠性測試,並形成相應的系統調試報告和可靠性測試報告。
典型嵌入式Linux軟體部分由哪些模組組成?它的功能和相互關係是什嗎?
Bootloader、嵌入式Linux核心、嵌入式檔案系統組成。Bootloader完成硬體裝置的初始化以及引導核心載入,核心通過檔案系統來管理對整個系統中的所有的資料和檔案。
BootLoader分為哪兩個階段?分別實現了哪些功能?
stage1和stage2兩個階段。
完成的工作:
硬體裝置初始化。
為載入Bootloader的stage2準備RAM空間。
拷貝Bootloader的stage2到RAM空間中。
設定堆棧。
跳轉到stage2的C進入點。
stage2完成的工作:
初始化本階段要使用到的硬體裝置。
監測系統記憶體映射。
將核心映像和根檔案系統映像從Flash裝置上複製到RAM空間中。
設定核心啟動參數。
調用啟動核心。
簡述嵌入式檔案系統的種類和管理機制。
Ext2fs檔案系統 2.基於Flash的檔案系統 3.基於RAM的檔案系統 4.網路檔案系統。
Linux引入了虛擬檔案系統vfs(virtual file system),為各類檔案系統提供一個統一的應用編程介面。
如何理解消費類電子產品開發的可裁剪性和可移植性,並以Linux系統為例進行說明。
Linux來說,假如我們用不到乙太網路裝置,我們可以將該裝置的驅動程式以及相關庫檔案等都去掉以縮小體積。
Linux可以在不同架構的CPU平台上運行。
詳細描述嵌入式Linux軟體開發的編譯開發環境和編譯開發工具。
開發環境:首先宿主機上需要安裝Linux作業系統。需要為這個Linux系統安裝以下三個部分:
函數庫(glibc):是Linux下C語言的主要函數庫。
編譯器(gcc):可以將C,C++,彙編來源程式和目標程式編譯、連結成可執行檔。
系統標頭檔(glibc_header):系統相關功能的標頭檔集合。
編譯開發工具:編輯器有Vi和Emacs;編譯器為GCC,是GUN推出的功能強大、效能優越的多平台編譯器;調試器為GDB,可以方便的設定斷點、單步跟蹤等調試功能;專案管理器“make”,用來控制編譯或者重複編譯,自動管理軟體編譯內容、方式和時機。
基於S3C2410嵌入式Linux的開發的邏輯空間和物理空間如何對應?詳細描述之。
在支援MMU的32位處理器平台上,Linux系統中的實體儲存體空間和虛擬儲存空間的位址範圍分別都是從0x00000000到0xFFFFFFFF,共4GB,但實體儲存體空間與虛擬儲存空間布局完全不同。Linux運行在虛擬儲存空間,並負責把系統中實際存在的遠小於4GB的實體記憶體根據不同需求映射到整個4GB的虛擬儲存空間中。
n 實體儲存體空間布局
Linux的實體儲存體空間布局與處理器相關,詳細情況可以從處理器使用者手冊的儲存空間分布表(memory map)相關章節中查到,我們這裡只列出嵌入式處理器平台Linux實體記憶體空......餘下全文>>