linux虛擬文件系統概述

本文轉載自:http://blog.chinaunix.net/uid-12567959-id-160983.html

原文是 Linux/Documentation/filesystems/vfs.txt

=========================================

Overview of the Linux Virtual File System

 

    Original author: Richard Gooch <[email protected]>

 

         Last updated on June 24, 2007.

 

  Copyright (C) 1999 Richard Gooch

  Copyright (C) 2005 Pekka Enberg

 

  This file is released under the GPLv2.

 

簡介

============

 

虛擬文件系統(或稱爲虛擬文件系統轉換層)是一個內核中的軟件層,它給用戶空間程序提供文件系統接口。同時在內核中提供一種抽象以允許不同文件系統的實現的共存。

 

VFS系統調用open(2), stat(2), read(2), write(2), chmod(2)等從一個進程上下文調用。文件系統鎖在文檔Documentation/filesystems/Locking描述。

 

目錄項緩存(dcache)

------------------

 

VFS實現open(2), stat(2), chmod(2)等一些系統調用。路徑名參數被傳遞給它們,VFS使用路徑名參數通過目錄項緩存(也被稱爲dentry cache或dcache)來查找。這提供了一種非常快速的查詢機制來將路徑名(文件名)轉換爲一個特定的dentry。Dentries存在於內存中,並且從來不會被保存到磁盤:它們的存在僅僅是爲了性能。

 

dentry緩存是作爲一個通向你的整個文件空間的視圖而存在的。由於大多數計算機都不能在同一時間將所有的dentries都裝入到內存中,所以一些位的緩存是不命中的。爲了將你的路徑名解析爲一個dentry,VFS可能不得不去創建dentries,並且加載inode。這通過查找inode來完成。

 

inode 對象

----------------

 

一個獨立的dentry通常有一個指向一個inode的指針。Inodes是文件系統對象,比如常規文件,目錄,FIFOs和其他的beasts。它們或者存在於磁盤上(對於塊設備文件系統)或者存在於內存中(對於僞文件系統)。存在於磁盤上的Inodes將在請求的時候被拷貝進內存,而對inode的修改將被寫回到磁盤。可以有多個dentries指向同一個inode(比如硬鏈接,hard links)。

 

爲了查找一個請求的inode,VFS會調用父目錄inode的lookup()方法。這個方法由inode所在的特定的文件系統實現安裝。一旦VFS有了請求的dentry(並因此有了inode),那麼我們就可以做所有的令人討厭的如open(2) 文件, 或者stat(2) 它來查看inode數據等的事情。stat(2)操作相當的簡單:VFS有了dentry,它查看inode數據並且將其中的一些傳回給用戶空間。

 

文件對象(The File Object)

--------------------------

 

打開一個文件需要另外一個操作:分配一個file結構(這是內核邊(kernel-side,相對於進程的文件描述符)實現的文件描述符)。新分配的file結構總是用一個指向dentry的指針和一系列文件操作成員函數來初始化。這些都取自inode數據。然後,open()文件方法被調用,以使特定的文件系統實現可以做它的工作。你可以看到,這是由VFS執行另一個轉換。file結構被放在進程的文件描述符表中。

 

讀、寫和關閉文件(還有其他的各種各樣的VFS操作)通過使用文件描述符來獲得合適的file結構,然後調用請求的文件結構方法來做請求的動作來完成。文件被打開後,它將保持dentry可用,這反過來意味着VFS inode是可用的。

 

註冊和掛載一個文件系統

======================

 

註冊或註銷一個文件愛你係統,可以使用下面的API函數:

 

#include <linux/fs.h>

 

extern int register_filesystem(struct file_system_type *);

extern int unregister_filesystem(struct file_system_type *);

 

傳遞的struct file_system_type描述了你的文件系統。當有一個掛載一個設備到一個你文件空間的一個目錄的請求產生時,VFS將會爲特定的文件系統調用合適的get_sb()方法。掛載點的dentry將會被更新以指向新文件系統的根inode。

 

你可以在文件/proc/filesystems中查看所有的已註冊的文件系統。

 

struct file_system_type

-----------------------

 

這個結構體用於描述一個文件系統。這個結構體有如下定義:

 

struct file_system_type {

    const char *name;

    int fs_flags;

    int (*get_sb) (struct file_system_type *, int,

              const char *, void *, struct vfsmount *);

    void (*kill_sb) (struct super_block *);

    struct module *owner;

    struct file_system_type * next;

    struct list_head fs_supers;

 

    struct lock_class_key s_lock_key;

    struct lock_class_key s_umount_key;

 

    struct lock_class_key i_lock_key;

    struct lock_class_key i_mutex_key;

    struct lock_class_key i_mutex_dir_key;

    struct lock_class_key i_alloc_sem_key;

};

 

  name: 文件系統類型的名字, 比如 "ext2", "iso9660",  "msdos" 等等

 

  fs_flags: 各種標誌 (i.e. FS_REQUIRES_DEV, FS_NO_DCACHE, etc.)

 

  get_sb: 當有一個該文件系統的新的實例被掛載的時候調用的方法

 

  kill_sb: 當該文件系統的一個實例被卸載的時候調用的方法

 

  owner: 給VFS內部使用: 在大多數情況下你應該將它初始化爲THIS_MODULE。

 

  next: 給VFS內部使用: 你應該將它初始化爲NULL

 

  s_lock_key, s_umount_key: lockdep-specific

 

get_sb() 方法有下列參數:

 

  struct file_system_type *fs_type: 描述文件系統, 被特定文件系統碼備份的初始化

 

  int flags: 掛載標誌

 

  const char *dev_name: 我們掛載的設備的設備名

 

  void *data: 任意的掛載選項, 通常是一個ASCII字符串(參考 "掛載選項" 部分)

 

  struct vfsmount *mnt: 一個掛載點vfs內部的代表

get_sb()方法必須判斷用dev_name和fs_type指定的特定設備是否包含一個該方法支持的文件系統類型。如果它成功的打開了用名字指定的塊設備,則爲塊設備包含的文件系統初始化一個struct super_block描述符。出錯時它返回錯誤。

 

get_sb()方法最多的是填充超級塊結構的成員"s_op"。這是一個指向一個"struct super_operations"的指針,而"struct super_operations"則描述了文件系統實現的下一個層次。

 

通常一個文件系統使用某個通用的get_sb()實現並提供fill_super()方法來代替。通用的方法有:

 

  get_sb_bdev: 掛載一個位於一個塊設備上的文件系

 

  get_sb_nodev: 掛載一個沒有被一個設備支持的文件系統

 

  get_sb_single: 掛載一個mount a filesystem which shares the instance between all mounts

 

一個fill_super()方法實現具有如下的參數:

struct super_block *sb: 超級塊結構。fill_super()方法必須適當的初始化它

 

  void *data: 任意的掛載選項,通常是一個ASCII字符串(參考 "掛載選項" 部分)

 

  int silent: whether or not to be silent on error

 

超級快對象(The Superblock Object)

===================================

 

一個超級快對象代表一個掛載的文件系統。

 

struct super_operations

-----------------------

 

他描述了VFS能如何管理你的文件系統的超級塊。其定義如下:

 

struct super_operations {

   struct inode *(*alloc_inode)(struct super_block *sb);

    void (*destroy_inode)(struct inode *);

 

   void (*dirty_inode) (struct inode *);

    int (*write_inode) (struct inode *, int);

    void (*drop_inode) (struct inode *);

    void (*delete_inode) (struct inode *);

    void (*put_super) (struct super_block *);

    void (*write_super) (struct super_block *);

    int (*sync_fs)(struct super_block *sb, int wait);

    int (*freeze_fs) (struct super_block *);

    int (*unfreeze_fs) (struct super_block *);

    int (*statfs) (struct dentry *, struct kstatfs *);

    int (*remount_fs) (struct super_block *, int *, char *);

    void (*clear_inode) (struct inode *);

    void (*umount_begin) (struct super_block *);

 

    int (*show_options)(struct seq_file *, struct vfsmount *);

    int (*show_stats)(struct seq_file *, struct vfsmount *);

#ifdef CONFIG_QUOTA

    ssize_t (*quota_read)(struct super_block *, int, char *, size_t, loff_t);

    ssize_t (*quota_write)(struct super_block *, int, const char *, size_t, loff_t);

#endif

    int (*bdev_try_to_free_page)(struct super_block*, struct page*, gfp_t);

};

 

所有的方法都在不持有任何鎖的情況下調用,除非另有說明。這意味着大多說方法可以安全的阻塞。所有的方法都只從進程上下文調用(比如,不會從一箇中斷處理程序或下半部)。

 

  alloc_inode: 這個方法由inode_alloc()調用來爲struct inode 分配內存並初始化它。如果這個函數沒有定義,則一個簡單的'struct inode'被分配。通常alloc_inode 將被用來分配一個更大的內嵌有'struct inode'的結構體。

 

  destroy_inode: 這個方法由destroy_inode()調用來釋放爲struct inode 分配的資源。只有當->alloc_inode 有定義的時候才需要它,它只是簡單的撤銷->alloc_inode 所做的一切。

 

  dirty_inode: 這個方法被VFS調用來標記一個inode 爲dirty。

 

  write_inode: 當VFS需要將一個inode 寫回磁盤的時候調用這個方法。第二個參數用以說明寫是否爲同步的,並不是所有的文件系統都會檢查這個標誌。

 

  drop_inode: 當對inode 的最後的訪問被丟棄的時候調用,調用時要持有inode_lock自旋鎖。

 

  這個方法應該爲NULL (普通的 UNIX 文件系統語義) 或者"generic_delete_inode" (對於那些不需要緩存inodes的文件系統 – 導致"delete_inode" 總是被調用而不管i_nlink的值是多少。

 

   generic_delete_inode()的行爲和以前在put_inode()情形中使用的"force_delete"是一樣的,只是它沒有"force_delete()"方法中出現的競爭顯現。

 

  delete_inode: 當VFS想要刪除(delete)一個inode 時調用

 

  put_super: 當VFS想要釋放superblock 是調用(比如unmount)。在持有superblock 鎖時調用。

 

  write_super: 當VFS superblock 需要被寫回磁盤時調用。這個方法是可選的。

 

  sync_fs: 當VFS寫完所有的與superblock 相關的“髒”的數據之後調用。第二個參數用以說明這個方法是否需要等待知道寫出操作完成。可選的。

 

  freeze_fs: 當VFS鎖定一個文件系統並強制它進入一致性狀態時調用。這個方法現在爲Logical Volume Manager (LVM)所用。

 

  unfreeze_fs: 當VFS解除鎖定一個文件系統並再次使它可寫是調用。

 

  statfs: 當VFS需要獲得文件系統統計量時調用。

 

  remount_fs: 當文件系統被重新掛載時調用。持有內核鎖時調用。

 

  clear_inode: 當VFS清除(clear)inode 時調用。可選。

 

  umount_begin: 當VFS卸載一個文件系統時調用。

 

  show_options:被VFS調用來爲/proc/<pid>/mounts顯示掛載選項 。 (參考 "掛載選項" 部分)

 

  quota_read: VFS調用以從文件系統讀取配額文件。

 

  quota_write: VFS調用以將配額文件寫回文件系統。

 

設置inode者有責任填充"i_op"成員。這是一個指向一個"struct inode_operations"的指針,而struct inode_operations則描述可以操作單個inodes的方法。

 

inode 對象

================

 

一個inode代表一個文件系統內的對象。

 

struct inode_operations

-----------------------

 

它描述了VFS是如何管理你文件系統中的inode的。在kernel 2.6.32.7中它有如下的定義:

 

struct inode_operations {

    int (*create) (struct inode *,struct dentry *,int, struct nameidata *);

    struct dentry * (*lookup) (struct inode *,struct dentry *, struct nameidata *);

    int (*link) (struct dentry *,struct inode *,struct dentry *);

    int (*unlink) (struct inode *,struct dentry *);

    int (*symlink) (struct inode *,struct dentry *,const char *);

    int (*mkdir) (struct inode *,struct dentry *,int);

    int (*rmdir) (struct inode *,struct dentry *);

    int (*mknod) (struct inode *,struct dentry *,int,dev_t);

    int (*rename) (struct inode *, struct dentry *,

           struct inode *, struct dentry *);

    int (*readlink) (struct dentry *, char __user *,int);

    void * (*follow_link) (struct dentry *, struct nameidata *);

    void (*put_link) (struct dentry *, struct nameidata *, void *);

    void (*truncate) (struct inode *);

    int (*permission) (struct inode *, int);

    int (*check_acl)(struct inode *, int);

    int (*setattr) (struct dentry *, struct iattr *);

    int (*getattr) (struct vfsmount *mnt, struct dentry *, struct kstat *);

    int (*setxattr) (struct dentry *, const char *,const void *,size_t,int);

    ssize_t (*getxattr) (struct dentry *, const char *, void *, size_t);

    ssize_t (*listxattr) (struct dentry *, char *, size_t);

    int (*removexattr) (struct dentry *, const char *);

    void (*truncate_range)(struct inode *, loff_t, loff_t);

    long (*fallocate)(struct inode *inode, int mode, loff_t offset,

             loff_t len);

    int (*fiemap)(struct inode *, struct fiemap_extent_info *, u64 start,

             u64 len);

};

 

再次,除非另有說明,所有的方法都在不持有鎖的情況下調用。

 

  create: 被open(2) and creat(2)系統調用所調用。只要你想要支持普通(regular )文件時才需要。你獲得的dentry應該還沒有inode(比如,它應該是一個negative dentry)。這裏你可能要以dentry和新建的inode爲參數來調用d_instantiate()。

 

  lookup: 當VFS需要在一個父目錄中查詢一個inode時調用。需要查詢的名字在dentry中。這個方法必須調用d_add()來將找到的inode插入到dentry中。inode 結構中的"i_count"成員應該被增加一。如果所要查找的特定名字的inode沒要找到,NULL inode應該被插入到dentry中。(這就是所謂的 negative

dentry)。必須只有當一個實際的錯誤時這個例程才返回一個錯誤,否則使用如create(2), mknod(2), mkdir(2)等系統調用來創建inodes將會失敗。如果你想要重載dentry方法,你應該初始化dentry的"d_dop"成員,這是一個指向一個"dentry_operations"結構體的指針。這個方法在持有目錄inode信號量的的情況下調用。

 

  link: 被link(2)系統調用所調用。只有當你想要支持硬鏈接的時候才需要。你可能需要調用。正像在create()方法中一樣,你將可能需要調用d_instantiate()。

 

  unlink: unlink(2)系統調用掉喲個。只要當你想要支持刪除(deleting )inodes時才需要。

 

  symlink: symlink(2)系統調用調用。只要當你想要支持符號鏈接時才需要。正像在create()方法中一樣,你將可能需要調用d_instantiate()。

 

  mkdir:系統調用 mkdir(2)調用。只有當你想要支持創建子目錄時才需要。正像在create()方法中一樣,你將可能需要調用d_instantiate()。

 

  rmdir: 系統調用rmdir(2)調用。只有當你想要支持刪除(deleting)子目錄時才需要。

 

  mknod: 系統調用mknod(2)調用來創建一個設備(字符設備,塊設備)inode或一個命名管道 (FIFO)或socket。只有當你想要支持這些類型的inodes的創建時才需要。正像在create()方法中一樣,你將可能需要調用d_instantiate()。

 

  rename: 系統調用rename(2)調用來重命名對象,以第二個inode和dentry爲父目錄和名字。

 

  readlink: 系統調用readlink(2)調用。只有當你想要支持讀取符號鏈接時才需要。

 

  follow_link: 由VFS調用來使一個符號鏈接跟隨它所指向的inode。只有當你想要支持符號鏈接時才需要它。此方法返回一個將會被傳遞給put_link()的無類型指針cookie 。

 

  put_link: 由VFS調用來釋放由follow_link()分配的資源。由follow_link()返回的cookie 被傳遞給它作爲最後一個參數。它被NFS等頁緩存不穩定的文件系統使用。    (比如,page that was installed when the symbolic link walk started might not be in the page cache at the end of the walk).

 

  truncate: 由VFS調用來改變一個文件的大小。在調用這個方法之前,inode結構的i_size成員被VFS設置爲需要的大小。這個方法由truncate(2)方法及相關的函數調用。

 

  permission: 在一個POSIX-like 文件系統上由VFS調用來檢查訪問權限。

 

  setattr: 由VFS調用來爲一個文件設置屬性。這個方法由chmod(2)及相關的系統調用來調用。

 

  getattr: 由VFS調用來獲得一個文件的屬性。這個方法由stat(2)及相關的系統調用來調用。

 

  setxattr: 由VFS調用來設置一個文件的擴展屬性。擴展屬性是一個與一個inode關聯的name:value對。這個方法由系統調用setxattr(2)調用。

 

  getxattr: 由VFS調用來檢索一個擴展屬性名的值。這個方法由getxattr(2)函數掉喲個。

 

  listxattr: 由VFS調用來列出給定文件的所有的擴展屬性。這個方法由系統調用listxattr(2)調用。

 

  removexattr: 由VFS調用來從一個文件移除(remove)。這個方法由系統調用removexattr(2)調用。

 

  truncate_range: 一個由底層文件系統提供來截斷塊的範圍的方法,比如在一個文件的某些地方打洞。

 

地址空間對象(The Address Space Object)

=======================================

 

地址空間對象用來分類並管理頁緩存中的頁。它可以被用來跟蹤一個文件(或其他)的頁,也可以被用來跟蹤一個文件映射到進程地址空間的段。

 

地址空間(address-space)可以提供許多不同但相關的服務。這包括communicating memory pressure, 根據地址查詢頁,和追蹤標記爲髒(Dirty)或寫回(Writeback)的頁。

 

其他人第一個可以獨立的使用(The first can be used independently to the others.)VM可以試着寫回“髒”頁以清理(clean)它們,或者釋放清理的頁以重新使用它們。爲了做到這些可以在“髒”頁上調用->writepage,和在設置了PagePrivate 的條件下調用 ->releasepage 。沒有PagePrivate也沒有外部引用的的清理過的頁將會在不給address_space 通知的情況下釋放。

 

爲了完成這些功能,無論何時頁被使用,則都需要使用lru_cache_add 和 mark_page_active 來將之放入一個LRU。

 

也通常被保存在一個由->index 索引的基數樹裏。這個數維護每一個頁關於PG_Dirty和 PG_Writeback 的狀態信息,以使具有這些標誌的頁可以被快速找到。

 

“髒”(Dirty)標記主要由mpage_writepages - 默認的->writepages 方法來使用。它使用標記來查找“髒”(dirty)頁並在其上調用->writepage 。如果不使用mpage_writepages(比如地址(address)提供了它自己的->writepages),則PAGECACHE_TAG_DIRTY標記已經未使用。write_inode_now 和 sync_inode使用它(通過__sync_single_inode)來檢查->writepages已經成功寫出整個的address_space。

 

Writeback標記由filemap*wait* 和 sync_page*函數使用,通過filemap_fdatawait_range ,來等待所有的寫回完成。等待的同時,將會在每一個請求寫回的頁上調用 ->sync_page (如果定義了)。

 

一個address_space handler 可能爲page附加了額外信息,典型的是使用'struct page'的'private'成員。如果附加了這樣的信息,則應該設置PG_Private 標記。這將導致各種VM例程做一些進入address_space  處理程序的額外的調用來處理那些數據。

 

一個地址空間就像存儲和應用程序之間的一箇中層。數據以頁爲單位在某一時刻被讀入到地址空間,並通過對頁的複製或者通過頁的內存映射來提供給應用程序。應用程序將數據寫回到地址空間,然後典型的以頁爲單位寫回到存儲器,然而address_space 可以對寫大小進行更好的控制。

 

讀進程需要僅僅請求'readpage'。寫進程要更復雜一些,它要使用write_begin/write_end 或 set_page_dirty來將數據寫回到address_space,並使用writepage,sync_page,和 writepages來將數據寫回存儲器。

 

向address_space 添加(add)和移除(removing)頁有inode結構的i_mutex 來保護。

 

將數據寫入頁的時候,應該設置PG_Dirty標誌。這個標誌要一直處於設置狀態,直到writepage請求將它寫回。而此時則要清除(clear)PG_Dirty並設置(PG_Writeback)。它可能在PG_Dirty被清除(clear)之後的任何時間點上被實際寫回。在安全的時候,則可以清除PG_Writeback。

 

寫回充分利用了writeback_control 結構……

 

struct address_space_operations

-------------------------------

 

它描述了VFS是如何管理你的文件系統中的一個文件到頁緩存中的映射映射的。在kernel 2.6.32.7中,這個結構體有如下定義:

 

struct address_space_operations {

    int (*writepage)(struct page *page, struct writeback_control *wbc);

    int (*readpage)(struct file *, struct page *);

    void (*sync_page)(struct page *);

 

    /* Write back some dirty pages from this mapping. */

    int (*writepages)(struct address_space *, struct writeback_control *);

 

    /* Set a page dirty.  Return true if this dirtied it */

    int (*set_page_dirty)(struct page *page);

 

    int (*readpages)(struct file *filp, struct address_space *mapping,

           struct list_head *pages, unsigned nr_pages);

 

    int (*write_begin)(struct file *, struct address_space *mapping,

              loff_t pos, unsigned len, unsigned flags,

              struct page **pagep, void **fsdata);

    int (*write_end)(struct file *, struct address_space *mapping,

              loff_t pos, unsigned len, unsigned copied,

              struct page *page, void *fsdata);

 

    /* Unfortunately this kludge is needed for FIBMAP. Don't use it */

    sector_t (*bmap)(struct address_space *, sector_t);

    void (*invalidatepage) (struct page *, unsigned long);

    int (*releasepage) (struct page *, gfp_t);

    ssize_t (*direct_IO)(int, struct kiocb *, const struct iovec *iov,

           loff_t offset, unsigned long nr_segs);

    int (*get_xip_mem)(struct address_space *, pgoff_t, int,

                     void **, unsigned long *);

    /* migrate the contents of a page to the specified target */

    int (*migratepage) (struct address_space *,

           struct page *, struct page *);

    int (*launder_page) (struct page *);

    int (*is_partially_uptodate) (struct page *, read_descriptor_t *,

                  unsigned long);

    int (*error_remove_page)(struct address_space *, struct page *);

};

 

  writepage: 由VM調用來將一個“髒”頁寫回到輔存中。這可能是爲了數據完整性(如'sync'),或者是爲了釋放內存(flush)。兩者的區別可以由wbc->sync_mode 來判斷。PG_Dirty被清除並且PageLocked返回true。同步或異步寫操作完成時,writepage應該啓動writeout,設置PG_Writeback,並且應該確保頁處於解鎖狀態。

 

      如果wbc->sync_mode 是WB_SYNC_NONE ,則->writepage 在出錯的時候不需要一直努力地嘗試,如果寫回其他頁更容易的話(比如,由於內部依賴),它也可以從mapping中選擇其他的頁來寫回。如果他選擇不啓動writeout,則它應該返回AOP_WRITEPAGE_ACTIVATE ,以使VM沒有不停地在那個頁上調用->writepage 。

 

      更多詳情請參考"Locking"文件。

 

  readpage: 由VM調用來從輔存中讀取一個頁。當readpage調用時,頁將被鎖定,並且應該在讀取結束時,立刻將其解除鎖定並標記爲uptodate。如果->readpage發現由於某些原因它需要解除鎖定頁,那麼它可以這樣做,並在之後返回AOP_TRUNCATED_PAGE。這種情況下,頁將會被重新定位、重新鎖定,如果都能正確完成,則將會再次調用->readpag。

 

  sync_page: 由VM調用來通知輔存,以爲一個頁執行所有排隊的I/O 操作。與該address_space 對象相關其他的頁的I/O 操作也可能被執行。

 

    這個函數是可選的,它僅僅在等待writeback 完成時對設置了PG_Writeback 標記的頁調用。

 

  writepages: 由VM調用來將與address_space 對象相關的頁寫出。如果wbc->sync_mode 是 WBC_SYNC_ALL ,那麼writeback_control將會指定一個必須寫出的頁的範圍。而如果是WBC_SYNC_NONE ,則一個nr_to_write 將會被傳遞給它,並且,如果可能的話,應該有許多的頁被寫出。如果沒有->writepages方法,那麼將會用mpage_writepages來代替。這將會從地址空間中選擇標記爲DIRTY的頁,並將它們傳遞給->writepage。

 

  set_page_dirty: 由VM調用來設置一個頁爲“髒”。這主要在一個address space向一個頁加入了私有數據時使用,並且那些數據需要在頁被標記爲“髒”時更新。比如,它會在一個內存映射的頁被改變時調用。

    如果定義,則它應該設置基數樹結構中的PageDirty 標誌(flag)和PAGECACHE_TAG_DIRTY 標記(tag)。

 

  readpages: 由VM調用來讀取與address_space 對象相關的頁。它本質上只是readpage 的一個向量版本。不同於只有一個頁,它用於請求多個頁的情況。Readpages僅僅被用於預讀。因此忽略讀錯誤。如果有任何錯誤,則可以隨意放棄。

 

  write_begin: 由通用緩存寫代碼調用,以使文件系統 準備 在文件給定的偏 移處寫入 len 字節。該address_space應檢查寫可以完成,通過分配空間,如果需要的話,並做其它的內部處理。如果寫將更新存儲器上的任何基本塊(basic-blocks),那麼那些塊應給被預讀(如果他們還沒有被讀入),以使那些更新的塊可以被適當的寫出。

 

    文件系統必須在*pagep 中給調用者返回鎖定了指定偏移量的頁緩存的頁來寫入。

 

    它必須能夠應付short writes(即傳遞給write_begin的要寫入的長度大於複製到頁裏的字節數的情況)。

 

    flags是一個AOP_FLAG_xxx型標誌,在include/linux/fs.h描述。

 

   可能在fsdata 返回一個無類型指針,這個指針隨後將會被傳入write_end。

 

   成功則放回0,錯誤在返回負值(錯誤碼),則將不調用write_end 。

 

  write_end: 成功地調用write_begin ,並完成了數據複製之後,則必須調用write_end 。len是最初傳遞給write_begin 的len,copied 參數是可以被複制的數量(如果write_begin在設置了AOP_FLAG_UNINTERRUPTIBLE標誌的情況下被調用,則 copied == len總爲真)。

 

    文件系統必須注意對頁解除鎖定,釋放它的refcount並更新i_size。

 

    出錯時返回負值,否則返回可以被複制進pagecache的字節數(<= 'copied')。

 

  bmap: 由VFS調用來將一個對象內的邏輯塊偏移量映射爲物理塊號。這個方法由FIBMAP ioctl使用,並且和swap-files一起工作。爲了能夠交換文件,文件必須具有到一個塊設備的穩定的影視。swap系統不是瀏覽整個文件系統,而是使用bmap 來查找文件所在的塊並直接使用那些地址。

 

 

  invalidatepage: 如果一個頁設置了PagePrivate ,則在部分或全部的頁將被移除出(remove)地址空間時調用invalidatepage。這通常對應於一個截斷或一個完全無效的地址空間。(後一種情況下則'offset'總爲0)。任何與頁關聯的私有數據應該被更新以反映這種截斷。如果offset爲0,由於頁必須能夠被完全丟棄,則私有數據應該被釋放。這可能通過調用->releasepage 函數來完成,但是在這種情況下釋放必須成功。

 

  releasepage: releasepage 在PagePrivate頁上調用以標示如果可能就應該釋放的頁。->releasepage 應該移除頁的所有的數據(remove) 並清除PagePrivate 標誌。它也可能會從address_space 中移除(remove)頁。如果它由於某些原因而失敗,則它可能用0返回值來標示失敗。它被用於兩種不同但相關的情況中。第一,是當VM發現一個沒有活躍用戶的頁而想要使其成爲一個空閒的頁時。如果 ->releasepage 成功,則頁將會被從address_space中移除(remove)併成爲空閒的。

 

    第二種情形是, 當 要使address_space 裏的一些或全部的頁無效的請求發出時。這可以通過fadvice(POSIX_FADV_DONTNEED)系統調用或者文件系統作爲NFS和9fs通過調用invalidate_inode_pages2()來明確的請求(當它們認爲緩存與存儲器相比可能過期了)。

 

    如果文件系統做了這個調用,則它的releasepage需要確保所有的頁是無效的。如果它還不能釋放私有數據,可能它可以清除PageUptodate 位。

 

  direct_IO: 由通用read/write 例程調用來執行直接IO – 即繞過頁緩存而直接在存儲器和應用程序的地址空間傳遞數據的IO請求。

 

  get_xip_page: 由VM調用來將一個塊號轉化爲一個頁。頁將保持有效直到相應的文件系統被卸載。想要使用片內執行(execute-in-place, XIP)的文件系統需要實現它。在fs/ext2/xip.c中可以找到一個實現的實例。

 

  migrate_page:  它被用來壓縮對物理內存的使用。如果VM想要重新定位一個頁 (maybe off a memory card that is signalling imminent failure),則它將會傳遞一個新頁和一箇舊頁給這個函數。migrate_page 應該爲這個頁傳輸所有的私有數據,並更新對這個頁的飲用。

 

  launder_page: 在釋放一個頁之前調用- 它寫回標記爲“髒”的頁。爲了防止該頁被重新標記爲“髒”,則它將在整個操作期間持有鎖。

 

  error_remove_page: 如果這個地址空間的truncation is ok ,那麼它通常設置爲generic_error_remove_page 。用於內存失敗處理。除非你鎖定它們或者增加它們的引用計數,則設置它意味着你將親自處理頁

    Setting this implies you deal with pages going away under you,

    unless you have them locked or reference counts increased.

 

 

文件對象(The File Object)

==========================

 

一個文件對象代表進程打開的一個文件。

 

struct file_operations

----------------------

 

它描述了VFS是如何管理一個打開的文件的。在 kernel 2.6.32.7中,有如下定義:

 

/*

 * NOTE:

 * read, write, poll, fsync, readv, writev, unlocked_ioctl and compat_ioctl

 * can be called without the big kernel lock held in all filesystems.

 */

struct file_operations {

    struct module *owner;

    loff_t (*llseek) (struct file *, loff_t, int);

    ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);

    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);

    ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);

    ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);

    int (*readdir) (struct file *, void *, filldir_t);

    unsigned int (*poll) (struct file *, struct poll_table_struct *);

    int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);

    long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);

    long (*compat_ioctl) (struct file *, unsigned int, unsigned long);

    int (*mmap) (struct file *, struct vm_area_struct *);

    int (*open) (struct inode *, struct file *);

    int (*flush) (struct file *, fl_owner_t id);

    int (*release) (struct inode *, struct file *);

    int (*fsync) (struct file *, struct dentry *, int datasync);

    int (*aio_fsync) (struct kiocb *, int datasync);

    int (*fasync) (int, struct file *, int);

    int (*lock) (struct file *, int, struct file_lock *);

    ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);

    unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);

    int (*check_flags)(int);

    int (*flock) (struct file *, int, struct file_lock *);

    ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);

    ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);

    int (*setlease)(struct file *, long, struct file_lock **);

};

 

再次,除非另有說明,所有的方法均在不持有任何鎖的情況下調用。

 

  llseek: 當VFS需要移動文件位置索引(文件指針)時調用。

 

  read: 由read(2)及相關的系統調用調用

 

  aio_read: 由io_submit(2)及其他的異步I/O 操作調用。

 

  write: 由write(2)及相關的系統調用調用

 

  aio_write: 由io_submit(2)及其他的異步I/O 操作調用。

 

  readdir: 當VFS需要讀取目錄內容時調用。

 

  poll: 當一個進程想要檢查該文件是否活躍並(可選地)進入睡眠狀態直到文件活躍 時由VFS調用。由select(2) 及poll(2)系統調用調用。

 

  ioctl: 由系統調用ioctl(2)調用

 

  unlocked_ioctl: 由系統調用ioctl(2)調用沒有請求BKL 的文件系統應該使用這個方法來代替上面的ioctl()。

 

  compat_ioctl: 32位系統調用運行在64位內核上時由ioctl(2)系統調用調用。

 

  mmap: 由系統調用mmap(2)調用。

 

  open: 當需要打開一個inode時由VFS調用。 VFS打開一個文件時,它創建一個新的"struct file",然後爲新分配的file 結構調用open方法。你可能認爲open方法實際上應該放在"struct inode_operations"裏,也許你是對的。我認爲它之所以如現在這樣工作,是爲了使文件系統更容易實現。如果你想要指向一個設備結構體,那麼open()方法是一個初始化file 結構體中"private_data"成員的好地方。

 

  flush: 由系統調用close(2)來刷新(flush)一個文件

 

  release: 當對一個打開文件的最後一個引用關閉時調用。

 

  fsync: 由系統調用fsync(2)調用

 

  fasync: 當一個文件的異步(非阻塞)模式使能時,由系統調用fcntl(2)調用。

 

  lock: 爲系統調用fcntl(2)的F_GETLK, F_SETLK, 和 F_SETLKW 命令調用

 

  readv: called by the readv(2) system call

 

  writev: called by the writev(2) system call

 

  sendfile: called by the sendfile(2) system call

 

  get_unmapped_area: 有系統調用mmap(2)調用

 

  check_flags: 爲系統調用fcntl(2)的F_SETFL命令調用。

 

  flock: 由系統調用flock(2)調用

 

  splice_write: VFS調用來從一個管道接合數據到一個文件,這個方法由系統調用splice(2)使用。

 

  splice_read: VFS調用來從一個文件中接合數據到一個管道。它由系統調用splice(2)使用。

 

注意,文件操作由inode所在的特定的文件系統實現。當打開一個設備文件(字符設備或塊設備特殊文件)時,大多數文件系統將調用VFS的特殊支持例程,這些例程將定位請求設備的驅動程序信息。這些支持例程也將用設備驅動程序的file operations來代替文件系統的file operations,併爲文件調用新的open()方法。這就是文件系統如何打開一個設備文件,它最終以調用設備驅動程序的open()方法結束。

 

 

目錄項緩存(dcache)

==================

 

 

struct dentry_operations

------------------------

 

它描述了一個文件系統是如何重載一個標準的dentry operations 。Dentries and 和 dcache 是VFS和獨立的文件系統實現的域。設備驅動與此無關。由於他們是可選的或者VFS使用默認的例程,這些方法可能被設爲NULL。在kernel2.6.32.7中有如下定義:

 

struct dentry_operations {

    int (*d_revalidate)(struct dentry *, struct nameidata *);

    int (*d_hash) (struct dentry *, struct qstr *);

    int (*d_compare) (struct dentry *, struct qstr *, struct qstr *);

    int (*d_delete)(struct dentry *);

    void (*d_release)(struct dentry *);

    void (*d_iput)(struct dentry *, struct inode *);

    char *(*d_dname)(struct dentry *, char *, int);

};

 

  d_revalidate: 當VFS需要使一個dentry重新生效時調用。用名字來在dcache 中查詢一個dentry時調用。由於它們在dcache中的所有的dentries 是有效的,大多數文件系統將它設置爲NULL。

 

  d_hash: 當VFS向哈希表中添加一個dentry是調用。

 

  d_compare: 當一個dentry要和另一個比較時調用

 

  d_delete: 對於一個dentry的最後的引用解除(delete)時調用。這意味着沒有人正在使用這個dentry,但依然是有效的,並依然在dcache中。

 

  d_release: 當dentry被真正的解除分配時調用。

 

  d_iput: 當一個dentry丟失了他的inode(在它被釋放之前)調用。如果它是NULL,則默認地VFS會調用iput()。如果你定義了這個方法,則你必須自己調用iput()。I

 

  d_dname: 當需要產生一個dentry的路徑名的時候調用。對於某些想要延遲路徑名的產生的僞文件系統(sockfs, pipefs, ...)很有用。(不是在dentry創建的時候,而是在需要路徑名的時候才產生)。真實的文件系統可能不會使用它,因爲它們的dentries 出現在全局的dcache哈希表中,它們的哈希應該是不變量。除非使用適當的SMP安全措施,否則由於沒有持有鎖,則d_dname()不應該試着自己去修改dentry 。注意:d_path()邏輯是相當複雜的。正確的返回 比如"Hello"的方法是將其放在緩衝區的結尾處,然後返回指向第一個字符的指針。dynamic_dname()輔助函數可被用來處理這一點。Real filesystems probably

 

例如 :

 

static char *pipefs_dname(struct dentry *dent, char *buffer, int buflen)

{

    return dynamic_dname(dentry, buffer, buflen, "pipe:[%lu]",

              dentry->d_inode->i_ino);

}

 

每一個dentry有一個指向它的父dentry的指針,並有一個它的子dentries的列表。子dentries 基本上就像一個目錄中的文件。

 

 

目錄項緩存API(Directory Entry Cache API)

------------------------------------------

 

內核定義了許多函數可以用來管理dentries:

 

  dget: 爲一個現有的dentry打開一個新的句柄(它僅僅增加使用計數)。

 

  dput: 爲一個dentry 關閉一個句柄(將使用計數減一)。如果使用計數減到0,則調用"d_delete"方法,並且,如果dentry仍然在它的父哈希列表中時,將dentry放入未使用列表。將dentry放入未使用列表僅僅意味着如果系統需要RAM,它瀏覽dentries的未使用列表並對dentries解除分配。如果dentry已經unhashed ,並且使用計數減爲了0,則在調用了"d_delete"方法之後要解除分配dentry。

 

  d_drop: 該函數從dentry的父哈希表中unhashes 一個dentry。如果它的使用計數減爲了0,則隨後調用的dput()解除分配該dentry。

 

  d_delete: 刪除(delete)一個dentry。如果沒有其他的對於該dentry的打開引用,則該dentry將被轉換爲一個negative  dentry(調用d_iput()方法)。如果還有其他的引用,則調用d_drop()來代替。

 

  d_add: 將一個dentry添加進它的父哈希表中,並調用d_instantiate()。

 

  d_instantiate: 爲inode將一個dentry添加進別名哈希表,並更新"d_inode"成員。Inode結構體的"i_count"成員應該被設置/增加計數。如果inode指針爲NULL,則dentry被稱爲"negative    dentry"。這個函數通常在 爲一個現有的negative dentry 創建一個inode時調用。

 

  d_lookup: 根據父dentry和路徑名來查找一個dentry。它從dcache哈希表中查找與給定名字相同的子dentry。如果找到了,則增加引用計數,並返回dentry。調用者必須在結束使用dentry時使用dput()釋放它。

 

更多關於dentry鎖的信息,請參考文檔Documentation/ filesystems/ dentry-locking.txt.

 

掛載選項(Mount Options)

========================

 

解析參數(Parsing options)

---------------------------

 

在掛載和重掛載(remount)文件系統時,會傳遞一個含有由逗號分隔的掛載選項的字符串。選項可以具有下列各式之一:

 

  option

  option=value

 

<linux/parser.h>頭文件定義一個API以幫助解析這些選項。有許多關於如何在一個現有的文件系統使用它的例子。

 

Showing options

---------------

 

如果一個文件系統接受掛載選項,那麼它必須定義show_options()來顯示當前可用的所有的選項。規則是:

 

  - 非默認的及與默認值不相同的選項必須顯示。

 

  - 默認代開或使用默認值的選項可以顯示。

 

選項僅僅在內部使用,在一個掛載輔助者和內核(比如文件描述符)之間使用,或者只在掛載時(比如控制日誌的創建)服從上面的規則。

 

上述規則的根本原因是爲了確保,根據/proc/mounts的信息,可以對一個掛載進行精確的複製(比如,卸載然後重新掛載)。

 

一個在掛載/重掛載時保存選項並顯示他們的方法(method)是提供save_mount_options() 和generic_show_options()輔助函數。請注意,使用這些可能有不足之處。更多信息,可以參考這些函數在文件fs/namespace.c 中的註釋信息。

 

資源(Resources)

================

 

(注意,有些資源沒有隨着最新版本的內核而更新。)

 

Creating Linux virtual filesystems. 2002

    <http://lwn.net/Articles/13325/>

 

The Linux Virtual File-system Layer by Neil Brown. 1999

    <http://www.cse.unsw.edu.au/~neilb/oss/linux-commentary/vfs.html>

 

A tour of the Linux VFS by Michael K. Johnson. 1996

    <http://www.tldp.org/LDP/khg/HyperNews/get/fs/vfstour.html>

 

A small trail through the Linux kernel by Andries Brouwer. 2001

    <http://www.win.tue.nl/~aeb/linux/vfs/trail.html>



後記:

這是linux內核文檔中的 Documentation/filesystems/vfs.txt文件的翻譯,很好的一個specification,對linux虛擬文件系統中的許多對象都有比較清晰的描述。這個文件最後一次更新是在2007年了,有點老了,自那之後linux虛擬文件系統有變動。說明仍然根據內核文檔,但各結構體的定義取的都是較新linux 2.6.32.7版。實在是有好多看不太懂的地方,還請各位多多指教了。



發佈了90 篇原創文章 · 獲贊 434 · 訪問量 106萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章