轉自:http://www.cnblogs.com/Jezze/archive/2011/12/23/2299861.html
簡單歸納: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

1 struct file { 2 union { 3 struct llist_node fu_llist; 4 struct rcu_head fu_rcuhead; 5 } f_u; 6 struct path f_path; 7 #define f_dentry f_path.dentry 8 struct inode *f_inode; /* cached value */ 9 const struct file_operations *f_op; 10 11 /* 12 * Protects f_ep_links, f_flags. 13 * Must not be taken from IRQ context. 14 */ 15 spinlock_t f_lock; 16 atomic_long_t f_count; 17 unsigned int f_flags; 18 fmode_t f_mode; 19 struct mutex f_pos_lock; 20 loff_t f_pos; 21 struct fown_struct f_owner; 22 const struct cred *f_cred; 23 struct file_ra_state f_ra; 24 25 u64 f_version; 26 #ifdef CONFIG_SECURITY 27 void *f_security; 28 #endif 29 /* needed for tty driver, and maybe others */ 30 void *private_data; 31 32 #ifdef CONFIG_EPOLL 33 /* Used by fs/eventpoll.c to link all the hooks to this file */ 34 struct list_head f_ep_links; 35 struct list_head f_tfile_llink; 36 #endif /* #ifdef CONFIG_EPOLL */ 37 struct address_space *f_mapping; 38 #ifdef CONFIG_DEBUG_WRITECOUNT 39 unsigned long f_mnt_write_state; 40 #endif 41 } __attribute__((aligned(4))); /* lest something weird decides that 2 is OK */

1 struct file_operations { 2 struct module *owner; 3 loff_t (*llseek) (struct file *, loff_t, int); 4 ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); 5 ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); 6 ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t); 7 ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t); 8 int (*iterate) (struct file *, struct dir_context *); 9 unsigned int (*poll) (struct file *, struct poll_table_struct *); 10 long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); 11 long (*compat_ioctl) (struct file *, unsigned int, unsigned long); 12 int (*mmap) (struct file *, struct vm_area_struct *); 13 int (*open) (struct inode *, struct file *); 14 int (*flush) (struct file *, fl_owner_t id); 15 int (*release) (struct inode *, struct file *); 16 int (*fsync) (struct file *, loff_t, loff_t, int datasync); 17 int (*aio_fsync) (struct kiocb *, int datasync); 18 int (*fasync) (int, struct file *, int); 19 int (*lock) (struct file *, int, struct file_lock *); 20 ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int); 21 unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long); 22 int (*check_flags)(int); 23 int (*flock) (struct file *, int, struct file_lock *); 24 ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int); 25 ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int); 26 int (*setlease)(struct file *, long, struct file_lock **); 27 long (*fallocate)(struct file *file, int mode, loff_t offset, 28 loff_t len); 29 int (*show_fdinfo)(struct seq_file *m, struct file *f); 30 };
1.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和權限位的概念,這是硬湊出來的。