Linux支持各種各樣的文件系統(tǒng)格式,如ext2、ext3、reiserfs、FAT、NTFS、iso9660等等,不同的磁盤分區(qū)、光盤或其它存儲設(shè)備都有不同的文件系統(tǒng)格式,然而這些文件系統(tǒng)都可以mount
到某個目錄下,使我們看到一個統(tǒng)一的目錄樹,各種文件系統(tǒng)上的目錄和文件我們用ls
命令看起來是一樣的,讀寫操作用起來也都是一樣的,這是怎么做到的呢?Linux內(nèi)核在各種不同的文件系統(tǒng)格式之上做了一個抽象層,使得文件、目錄、讀寫訪問等概念成為抽象層的概念,因此各種文件系統(tǒng)看起來用起來都一樣,這個抽象層稱為虛擬文件系統(tǒng)(VFS,Virtual Filesystem)。上一節(jié)我們介紹了一種典型的文件系統(tǒng)在磁盤上的存儲布局,這一節(jié)我們介紹運(yùn)行時文件系統(tǒng)在內(nèi)核中的表示。
Linux內(nèi)核的VFS子系統(tǒng)可以圖示如下:
在第 28 章 文件與I/O中講過,每個進(jìn)程在PCB(Process Control Block)中都保存著一份文件描述符表,文件描述符就是這個表的索引,每個表項都有一個指向已打開文件的指針,現(xiàn)在我們明確一下:已打開的文件在內(nèi)核中用file
結(jié)構(gòu)體表示,文件描述符表中的指針指向file
結(jié)構(gòu)體。
在file
結(jié)構(gòu)體中維護(hù)File Status Flag(file
結(jié)構(gòu)體的成員f_flags
)和當(dāng)前讀寫位置(file
結(jié)構(gòu)體的成員f_pos
)。在上圖中,進(jìn)程1和進(jìn)程2都打開同一文件,但是對應(yīng)不同的file
結(jié)構(gòu)體,因此可以有不同的File Status Flag和讀寫位置。file
結(jié)構(gòu)體中比較重要的成員還有f_count
,表示引用計數(shù)(Reference Count),后面我們會講到,dup
、fork
等系統(tǒng)調(diào)用會導(dǎo)致多個文件描述符指向同一個file
結(jié)構(gòu)體,例如有fd1
和fd2
都引用同一個file
結(jié)構(gòu)體,那么它的引用計數(shù)就是2,當(dāng)close(fd1)
時并不會釋放file
結(jié)構(gòu)體,而只是把引用計數(shù)減到1,如果再close(fd2)
,引用計數(shù)就會減到0同時釋放file
結(jié)構(gòu)體,這才真的關(guān)閉了文件。
每個file
結(jié)構(gòu)體都指向一個file_operations
結(jié)構(gòu)體,這個結(jié)構(gòu)體的成員都是函數(shù)指針,指向?qū)崿F(xiàn)各種文件操作的內(nèi)核函數(shù)。比如在用戶程序中read
一個文件描述符,read
通過系統(tǒng)調(diào)用進(jìn)入內(nèi)核,然后找到這個文件描述符所指向的file
結(jié)構(gòu)體,找到file
結(jié)構(gòu)體所指向的file_operations
結(jié)構(gòu)體,調(diào)用它的read
成員所指向的內(nèi)核函數(shù)以完成用戶請求。在用戶程序中調(diào)用lseek
、read
、write
、ioctl
、open
等函數(shù),最終都由內(nèi)核調(diào)用file_operations
的各成員所指向的內(nèi)核函數(shù)完成用戶請求。file_operations
結(jié)構(gòu)體中的release
成員用于完成用戶程序的close
請求,之所以叫release
而不叫close
是因?yàn)樗灰欢ㄕ娴年P(guān)閉文件,而是減少引用計數(shù),只有引用計數(shù)減到0才關(guān)閉文件。對于同一個文件系統(tǒng)上打開的常規(guī)文件來說,read
、write
等文件操作的步驟和方法應(yīng)該是一樣的,調(diào)用的函數(shù)應(yīng)該是相同的,所以圖中的三個打開文件的file
結(jié)構(gòu)體指向同一個file_operations
結(jié)構(gòu)體。如果打開一個字符設(shè)備文件,那么它的read
、write
操作肯定和常規(guī)文件不一樣,不是讀寫磁盤的數(shù)據(jù)塊而是讀寫硬件設(shè)備,所以file
結(jié)構(gòu)體應(yīng)該指向不同的file_operations
結(jié)構(gòu)體,其中的各種文件操作函數(shù)由該設(shè)備的驅(qū)動程序?qū)崿F(xiàn)。
每個file
結(jié)構(gòu)體都有一個指向dentry
結(jié)構(gòu)體的指針,“dentry”是directory entry(目錄項)的縮寫。我們傳給open
、stat
等函數(shù)的參數(shù)的是一個路徑,例如/home/akaedu/a
,需要根據(jù)路徑找到文件的inode。為了減少讀盤次數(shù),內(nèi)核緩存了目錄的樹狀結(jié)構(gòu),稱為dentry cache,其中每個節(jié)點(diǎn)是一個dentry
結(jié)構(gòu)體,只要沿著路徑各部分的dentry搜索即可,從根目錄/
找到home
目錄,然后找到akaedu
目錄,然后找到文件a
。dentry cache只保存最近訪問過的目錄項,如果要找的目錄項在cache中沒有,就要從磁盤讀到內(nèi)存中。
每個dentry
結(jié)構(gòu)體都有一個指針指向inode
結(jié)構(gòu)體。inode
結(jié)構(gòu)體保存著從磁盤inode讀上來的信息。在上圖的例子中,有兩個dentry,分別表示/home/akaedu/a
和/home/akaedu/b
,它們都指向同一個inode,說明這兩個文件互為硬鏈接。inode
結(jié)構(gòu)體中保存著從磁盤分區(qū)的inode讀上來信息,例如所有者、文件大小、文件類型和權(quán)限位等。每個inode
結(jié)構(gòu)體都有一個指向inode_operations
結(jié)構(gòu)體的指針,后者也是一組函數(shù)指針指向一些完成文件目錄操作的內(nèi)核函數(shù)。和file_operations
不同,inode_operations
所指向的不是針對某一個文件進(jìn)行操作的函數(shù),而是影響文件和目錄布局的函數(shù),例如添加刪除文件和目錄、跟蹤符號鏈接等等,屬于同一文件系統(tǒng)的各inode
結(jié)構(gòu)體可以指向同一個inode_operations
結(jié)構(gòu)體。
inode
結(jié)構(gòu)體有一個指向super_block
結(jié)構(gòu)體的指針。super_block
結(jié)構(gòu)體保存著從磁盤分區(qū)的超級塊讀上來的信息,例如文件系統(tǒng)類型、塊大小等。super_block
結(jié)構(gòu)體的s_root
成員是一個指向dentry
的指針,表示這個文件系統(tǒng)的根目錄被mount
到哪里,在上圖的例子中這個分區(qū)被mount
到/home
目錄下。
file
、dentry
、inode
、super_block
這幾個結(jié)構(gòu)體組成了VFS的核心概念。對于ext2文件系統(tǒng)來說,在磁盤存儲布局上也有inode和超級塊的概念,所以很容易和VFS中的概念建立對應(yīng)關(guān)系。而另外一些文件系統(tǒng)格式來自非UNIX系統(tǒng)(例如Windows的FAT32、NTFS),可能沒有inode或超級塊這樣的概念,但為了能mount
到Linux系統(tǒng),也只好在驅(qū)動程序中硬湊一下,在Linux下看FAT32和NTFS分區(qū)會發(fā)現(xiàn)權(quán)限位是錯的,所有文件都是rwxrwxrwx
,因?yàn)樗鼈儽緛砭蜎]有inode和權(quán)限位的概念,這是硬湊出來的。
dup
和dup2
都可用來復(fù)制一個現(xiàn)存的文件描述符,使兩個文件描述符指向同一個file
結(jié)構(gòu)體。如果兩個文件描述符指向同一個file
結(jié)構(gòu)體,F(xiàn)ile Status Flag和讀寫位置只保存一份在file
結(jié)構(gòu)體中,并且file
結(jié)構(gòu)體的引用計數(shù)是2。如果兩次open
同一文件得到兩個文件描述符,則每個描述符對應(yīng)一個不同的file
結(jié)構(gòu)體,可以有不同的File Status Flag和讀寫位置。請注意區(qū)分這兩種情況。
#include <unistd.h>int dup(int oldfd);int dup2(int oldfd, int newfd);
如果調(diào)用成功,這兩個函數(shù)都返回新分配或指定的文件描述符,如果出錯則返回-1。dup
返回的新文件描述符一定是該進(jìn)程未使用的最小文件描述符,這一點(diǎn)和open
類似。dup2
可以用newfd
參數(shù)指定新描述符的數(shù)值。如果newfd
當(dāng)前已經(jīng)打開,則先將其關(guān)閉再做dup2
操作,如果oldfd
等于newfd
,則dup2
直接返回newfd
而不用先關(guān)閉newfd
再復(fù)制。
下面這個例子演示了dup
和dup2
函數(shù)的用法,請結(jié)合后面的連環(huán)畫理解程序的執(zhí)行過程。
例 29.2. dup和dup2示例程序
#include <unistd.h>#include <sys/stat.h>#include <fcntl.h>#include <stdio.h>#include <stdlib.h>#include <string.h>int main(void){ int fd, save_fd; char msg[] = "This is a test\n"; fd = open("somefile", O_RDWR|O_CREAT, S_IRUSR|S_IWUSR); if(fd<0) { perror("open"); exit(1); } save_fd = dup(STDOUT_FILENO); dup2(fd, STDOUT_FILENO); close(fd); write(STDOUT_FILENO, msg, strlen(msg)); dup2(save_fd, STDOUT_FILENO); write(STDOUT_FILENO, msg, strlen(msg)); close(save_fd); return 0;}
重點(diǎn)解釋兩個地方:
第3幅圖,要執(zhí)行dup2(fd, 1);
,文件描述符1原本指向tty
,現(xiàn)在要指向新的文件somefile
,就把原來的關(guān)閉了,但是tty
這個文件原本有兩個引用計數(shù),還有文件描述符save_fd
也指向它,所以只是將引用計數(shù)減1,并不真的關(guān)閉文件。
第5幅圖,要執(zhí)行dup2(save_fd, 1);
,文件描述符1原本指向somefile
,現(xiàn)在要指向新的文件tty
,就把原來的關(guān)閉了,somefile
原本只有一個引用計數(shù),所以這次減到0,是真的關(guān)閉了。
聯(lián)系客服