Linux中檔案描述符fd和檔案指標flip
簡單歸納:fd只是一個整數,在open時產生。起到一個索引的作用,程序通過PCB中的檔案描述符表找到該fd所指向的檔案指標filp。
open:檔案描述符的操作(如: open)返回的是一個檔案描述符(int fd),核心會在每個程序空間中維護一個檔案描述符表, 所有開啟的檔案都將通過此表中的檔案描述符來引用(fd1,fd2,fd3...);
fopen:而流(如: fopen)返回的是一個FILE結構指標, FILE結構是包含有檔案描述符的,FILE結構函式可以看作是對fd直接操作的系統呼叫的封裝, 它的優點是帶有I/O快取.
Linux支援各種各樣的檔案系統格式,如ext2、ext3、reiserfs、FAT、NTFS、iso9660等等,不同的磁碟分割槽、光碟或其它儲存裝置都有不同的檔案系統格式,然而這些檔案系統都可以mount
ls
命令看起來是一樣的,讀寫操作用起來也都是一樣的,這是怎麼做到的呢?Linux核心在各種不同的檔案系統格式之上做了一個抽象層,使得檔案、目錄、讀寫訪問等概念成為抽象層的概念,因此各種檔案系統看起來用起來都一樣(VFS作用),這個抽象層稱為虛擬檔案系統(VFS,Virtual Filesystem),這一節我們介紹執行時檔案系統在核心中的表示。
Linux核心的VFS子系統可以圖示如下:
每個程序在PCB(Process Control Block)即程序控制塊中都儲存著一份檔案描述符表,檔案描述符就是這個表的索引,檔案描述表中每個表項都有一個指向已開啟檔案的指標,現在我們明確一下:已開啟的檔案在核心中用file
file
結構體(理解:fd為開啟檔案的檔案描述符,而每個程序都有一張檔案描述表,fd檔案描述符就是這張表的索引,同樣這張表中有一表項,該表項又是指向前面提到開啟檔案的file結構體,file結構體才是核心中用於描述檔案屬性的結構體)。
struct file-----define in inlcude/linux/fs.h
struct file_operations------define in include/linux/fs.h
struct file struct file_operations1.file.File Status Flag和file.f_count
在file
結構體中維護File Status Flag(file
結構體的成員f_flags
)和當前讀寫位置(file
結構體的成員f_pos
)。在上圖中,程序1和程序2都開啟同一檔案,但是對應不同的file
結構體,因此可以有不同的File Status Flag和讀寫位置。file
結構體中比較重要的成員還有f_count
,表示引用計數(Reference Count),後面我們會講到,dup
、fork
等系統呼叫會導致多個檔案描述符指向同一個file
結構體,例如有fd1
和fd2
都引用同一個file
結構體,那麼它的引用計數就是2,當close(fd1)
時並不會釋放file
結構體,而只是把引用計數減到1,如果再close(fd2)
,引用計數就會減到0同時釋放file
結構體,這才真的關閉了檔案。
2.file.file_operations
每個file
結構體都指向一個file_operations
結構體,這個結構體的成員都是函式指標,指向實現各種檔案操作的核心函式。比如在使用者程式中read
一個檔案描述符,read
通過系統呼叫進入核心,然後找到這個檔案描述符所指向的file
結構體,找到file
結構體所指向的file_operations
結構體,呼叫它的read
成員所指向的核心函式以完成使用者請求(應用層到核心層的呼叫流程)。在使用者程式中呼叫lseek
、read
、write
、ioctl
、open
等函式,最終都由核心呼叫file_operations
的各成員所指向的核心函式完成使用者請求。file_operations
結構體中的release
成員用於完成使用者程式的close
請求,之所以叫release
而不叫close
是因為它不一定真的關閉檔案,而是減少引用計數,只有引用計數減到0才關閉檔案。對於同一個檔案系統上開啟的常規檔案來說,read
、write
等檔案操作的步驟和方法應該是一樣的,呼叫的函式應該是相同的,所以圖中的三個開啟檔案的file
結構體指向同一個file_operations
結構體。如果開啟一個字元裝置檔案,那麼它的read
、write
操作肯定和常規檔案不一樣,不是讀寫磁碟的資料塊而是讀寫硬體裝置,所以file
結構體應該指向不同的file_operations
結構體(也就有了使用者自定義結構體物件或者核心自定義結構體物件),其中的各種檔案操作函式由該裝置的驅動程式實現。
3.file.dentry
每個file
結構體都有一個指向dentry
結構體的指標,“dentry”是directory entry(目錄項)的縮寫。我們傳給open
、stat
等函式的引數的是一個路徑,例如/home/akaedu/a
,需要根據路徑找到檔案的inode。為了減少讀盤次數,核心快取了目錄的樹狀結構,稱為dentry cache(作用),其中每個節點是一個dentry
結構體,只要沿著路徑各部分的dentry搜尋即可,從根目錄/
找到home
目錄,然後找到akaedu
目錄,然後找到檔案a
。dentry cache只儲存最近訪問過的目錄項,如果要找的目錄項在cache中沒有,就要從磁碟讀到記憶體中。
4.dentry.inode
每個dentry
結構體都有一個指標指向inode
結構體。inode
結構體儲存著從磁碟inode讀上來的資訊。在上圖的例子中,有兩個dentry,分別表示/home/akaedu/a
和/home/akaedu/b
,它們都指向同一個inode,說明這兩個檔案互為硬連結。inode
結構體中儲存著從磁碟分割槽的inode讀上來資訊,例如所有者、檔案大小、檔案型別和許可權位等(inode有哪些引數,正常理解file結構體可能包含這些資訊,其實是file.inode成員管理這些資訊)。每個inode
結構體都有一個指向inode_operations
結構體的指標,後者也是一組函式指標指向一些完成檔案目錄操作的核心函式。和file_operations
不同,inode_operations
所指向的不是針對某一個檔案進行操作的函式,而是影響檔案和目錄佈局的函式,例如新增刪除檔案和目錄、跟蹤符號連結等等,屬於同一檔案系統的各inode
結構體可以指向同一個inode_operations
結構體。
5.inod.super_block
inode
結構體有一個指向super_block
結構體的指標。super_block
結構體儲存著從磁碟分割槽的超級塊讀上來的資訊,例如檔案系統型別、塊大小等。super_block
結構體的s_root
成員是一個指向dentry
的指標,表示這個檔案系統的根目錄被mount
到哪裡,在上圖的例子中這個分割槽被mount
到/home
目錄下。
file
、dentry
、inode
、super_block
這幾個結構體組成了VFS的核心概念。對於ext2檔案系統來說,在磁碟儲存佈局上也有inode和超級塊的概念,所以很容易和VFS中的概念建立對應關係。而另外一些檔案系統格式來自非UNIX系統(例如Windows的FAT32、NTFS),可能沒有inode或超級塊這樣的概念,但為了能mount
到Linux系統,也只好在驅動程式中硬湊一下,在Linux下看FAT32和NTFS分割槽會發現許可權位是錯的,所有檔案都是rwxrwxrwx
,因為它們本來就沒有inode和許可權位的概念,這是硬湊出來的。