[回主選單]


Linux 虛擬檔案系統概述


介紹
====


虛擬檔案系統(也稱為虛擬檔案 switch)是核心中的一個軟體層,該層向用戶空間程式提供的檔案系統的界面。同時,也在核心內部提供了一個抽象,使得核心允許不同的檔案系統實現並存。


VFS 系統呼叫,比如 open(2),stat(2),read(2),write(2),chmod(2)等等,都是在進程上下文中呼叫的。檔案系統的鎖在文檔 Documentation/filesystems/Locking 中描述。


Directory Entry Cache (dcache)
------------------------------


VFS 實現了 open(2),stat(2),chmod(2)和類似的系統呼叫。VFS 使用傳給它們的路徑參數在 directory entry cache(也可稱為 dentry cache 或者 dcache) 中查找。這樣就提供一種非常快速的將路徑名(檔案名)轉換成特定 dentry 的機製。dentry 儲存在內存中,並且永遠不會儲存到磁片上︰它們的存在就是為了提升性能。


dentry cache 應該覆蓋你的整個檔案空間。因為大多數計算機是不能在同一刻把所有的dentry 都放到內存中,所以有些路徑不被包含在 cache 中。為了將你的路徑解析成一個dentry,VFS 可能要採取先創建 dentry,然後讀取相應的 inode 的方法。解析的工作透過對 inode 的解析完成。


Inode 對象
----------


一個 dentry 個體一般都有一個指向 inode 的指標。inode是檔案系統對象,比如普通文件,目錄,FIFO檔案等等。它們即可以儲存在磁片中(針對塊設備檔案系統),也可以儲存在內存中(針對偽檔案系統)。當訪問在磁片上的 inode 時,要將其拷貝到內存中,對這樣的 inode 的任何更改都要最終寫回到磁片中。多個 dentry 可以指向同一個 inode (比如,硬聯接,就是這樣作的)。


查詢一 inode ,就要求 VFS 在所要查詢的 inode 所在目錄的 inode 上呼叫 lookup()。該方法由 inode 所在的檔案系統實現並註冊。一旦 VFS 中包含所請求 dentry(也就包括相應的 inode),我們就能作那些煩人的事情了,比如用 open(2) 打開檔案,或stat(2) 來看看相應的 inode 數據。stat(2) 操作相當的簡單︰一旦 VFS 中該 dentry,VFS 就會檢視相應 inode 的數據,然後把其中的一些傳回用戶空間。


File 對象
---------


打開一個檔案還要求另一個操作︰分發一個 file 架構(這是檔案描述符的核心實現)。新分發的 file 架構用一個指向相應 dentry 的指標和一個檔案操作成員函數集合初始化,而這些都是從 inode 的數據中得到的。然後,呼叫 open(),這樣特定檔案系統的實現就可以工作了。你可以看到這是 VFS 所作的其一個轉換。file 架構放在相應進程的檔案描述符表中。


完成讀,寫和關閉檔案(和其它類型的 VFS 操作),都是透過使用用戶空間的檔案描述符,得到相應的 file 架構,然後呼叫相應的 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 *);


傳給函數的 file_system_type 架構描述了你的檔案系統。當要將一個設備掛載到在你的
檔案空間中的某個目錄上時,VFS 會為具體的檔案系統呼叫相應的 get_sb() 函數。然後,
該掛載點的 dentry 就會被更新,從而指向新檔案系統的根 inode 。


你可以在 /proc/filesystems 檔案中看到當前所有註冊在核心中的檔案系統。


架構 file_system_type
---------------------


該架構是用來描述檔案系統的。到 2.6.13 為止,該架構的定義如下︰


struct file_system_type{
const char *name;
int fs_flags;
int (*get_sb) (struct file_system_type *,int,
const char *,void *,struct vfsmount *);
void (*kll_sb) )struct super_block *);
struct module *owner;
struct file_system_type *next;
struct list_head fs_supers;
};


name
檔案類型的名字,比如 "ext2","iso9660","msdos"等等


fs_flags
各種標記(比如, FS_REQUIRES_DEV,FS_NO_DCACHE,等等)

get_sb
當該檔案系統一新的實體要被掛載的時候,呼叫該函數


kill_sb
當該檔案系統一實體要被卸載的時候,呼叫該函數


owner
供 VFS 內部使用︰在大多數情況下,你應該將其初始化為 THIS_MODULE


next
供 VFS 內部使用︰你應用將其初始化為 NULL


get_sb() 函數有如下參數

struct super_block *sb
架構 superblock。其一部分是由 VFS 初始化的,而剩下的要由 get_sb() 初始化


int flags
掛載標識


const char *dev_name
我們要掛載的設備的名字。


void *data
任意的掛載選項,一般是個 ASCII 字元串


int silent
發生錯誤的時候是否提示


從 superblock 上得到塊設備後,get_sb() 必須判斷該函數是否支援該塊設備上的檔案
系統類型。如果成功,返回該超級塊的指標,失敗返回 NULL 。


由 get_sb() 填充的架構 superblock 中,最吸引人的是 s_op 。這是一個指向架構
super_operation 的指標,該指標描述了下層的檔案系統實現。


一個檔案系統經常會使用幾個已經實現的通用 get_sb()中的一個,而提供一個自己的
file_super() 函數。這些通用函數是︰


get_sb_bdev
將檔案系統掛載到一塊設備上


get_sb_nodev
掛載一無須設備支援的檔案系統


get_sb_single
掛載一在所有掛載點共享內容的檔案系統


fill_super() 函數的實現需要以下參數︰


struct super_block *sb
超級塊架構。函數 fill_super() 必須將其正確的初
始化。


void *data
任意的掛載選項,一般是個 ASCII 字元串


int silent
出現錯誤的時候是否提示


超級塊對象
==========


一個超級塊對象就代表了一個已經掛載的檔案系統。


架構 super_operations
---------------------


該架構描述了 VFS 是如何操縱你檔案系統的超級塊的。在版本為2.6.13的核心中,定義了
如下成員︰


struct super_operations {
struct inode *(*alloc_inode)(struct super_block *sb);
void (*destroy_inode)(struct inode *);


void (*read_inode) (struct inode *);


void (*dirty_inode) (struct inode *);
int (*write_inode) (struct inode *, int);
void (*put_inode) (struct inode *);
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);
void (*write_super_lockfs) (struct super_block *);
void (*unlockfs) (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 *);


void (*sync_inodes) (struct super_block *sb,
struct writeback_control *wbc);
int (*show_options)(struct seq_file *, struct vfsmount *);


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);
};


除非注明,否則所有的方法呼叫都不用先上鎖。這也就是說大多數的方法都可以安全的阻塞。所有的方法都是在進程上下文中呼叫。(比如,不能從中斷或下半部中呼叫)。


alloc_inode
該方法由 inode_alloc()呼叫,用來為架構 inode 分發內存空間並將其初始化。如果該函數沒有定義,那就分發一個簡單的 inode 架構。alloc_inode 經常用來分發一個更大的架構,而在該架構中包含一個 inode 結構。


destroy_inode:
該方法由 destroy_inode()呼叫,用來釋放 inode 架構所占內存。當實現了 alloc_inode 時,就必須實現該函數,並簡單的回收 alloc_inode 所分發的一切資料。


read_inode
呼叫該方法是用來從一已掛載的檔案系統中讀取特定的 inode 。架構inode 中的成員 i_ino 由 VFS 初始化以說明所要讀取的是哪個 inode 。架構中的其它成員由該函數填充。


你可以將 read_inode 設定為 NULL,並使用 iget5_locked()而不是 iget() 去讀 inode 。這對於那些單憑 inode 號是無法區分節點的檔案系統來說,是必要的。


dirty_inode
VFS 在將一 inode 標識為臟的時候,呼叫該函數。


write_inode
VFS 在將一 inode 寫到磁片上時,呼叫該函數。該函數的第二的參數表明是否為同步寫,當然並不是所有的檔案系統都檢查該標識。


put_inode
VFS 在將一 inode 從 inode cache 中移出的時候,呼叫該函數。


drop_inode
當最後一次訪問被刪除的 inode 的時候,呼叫該函數。呼叫的時候要鎖上 inode_lock spinlock。


該方法可以為 NULL (普通的 UNIX 檔案系統語義)或 "generic_delete_inode"(對於那些不緩存 inode 的檔案系統 - 不管 i_nlink 的值是多少,要刪掉inode 的時候就呼叫 "delete_inode")


"genreic_delete_inode()" 所完成的工作和在 put_inode()中使用"force_delete"的舊實現是一樣的,只是其在執行時不存在競爭,而force_delete 有。


delete_inode
VFS 刪除一 inode 時,呼叫該函數。


put_super
VFS 想要釋放一超級塊的時候(比如,卸載),呼叫該函數。呼叫時,要鎖上該superblock。


write_super
VFS 的超級塊需要寫到磁片時,呼叫該函數。該函數是可選項。


sync_fs
VFS 將與一超級塊相關的所有臟數據寫出的時候,呼叫該函數。該函數的第二個參數說明該方法是否要等到所有的寫操作完成之後再返回。可選項。


write_super_lockfs
VFS 對一檔案系統上鎖並強迫其保持一致時,呼叫該函數。該方法當前正在被 Logical Volume Manager (LVM) 使用。


unlockfs
VFS 對一檔案系統解鎖並將其再次置為可寫狀態時,呼叫該函數。


statfs
VFS 要得到檔案系統的統計訊息的時候,呼叫該函數。呼叫時要鎖上核心相應的鎖。


remount_fs
當檔案系統被重新掛載的時候,呼叫該函數。呼叫時要鎖上核心相應的鎖。


clear_inode
VFS clear(我不清楚該怎么翻譯) inode 時呼叫。可選項。


umount_begin
VFS 卸載一檔案系統時,呼叫該函數。


sync_inodes
VFS 寫出與一超級塊相關的臟數據時,呼叫該函數。


show_options
由 VFS 呼叫,為 /proc/<pid>/mounts 顯示掛載選項。


quota_read
VFS 要讀檔案系統的配額檔案時,呼叫。


quota_write:VFS 要向檔案系統的配額檔案中寫東西的時候,呼叫。


read_inode() 方法用來填充 "i_op" 選項。該選項是一個指向 "struct inode_operations"的指標,而該架構描述了可以在單個 inode所能進行的操作。


inode 對象
==========


一個 inode 對象代表了檔案系統中的一個對象。


架構 inode_operations
---------------------


該架構描述了 VFS 是如何操縱你的檔案系統的一個 inode。在版本為2.6.13的核心中,
定義了如下成員︰


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, struct nameidata *);
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 *);
};
除非注明,否則所有的方法呼叫都不用先上鎖。


create
由 open(2) 和 create(2) 系統呼叫呼叫。只能當你要支援普通檔案的時候,要求實現。你所得到的 dentry 可以沒有 inode(比如,它可以是個 negativedentry)。此處你可能要呼叫 d_instatntiate(),參數為該 dentry 和新建的inode。


lookup
VFS 要在父目錄中查找一 inode 時,呼叫該函數。所要查找的 inode 的名字可以在相應的 dentry 中找到。該方法必須呼叫d_add(),將所找到的 inode插入到相應的 dentry hash表中。在 inode 架構中的 i_count 應該增加。如果該名字所對應的 inode 不存在,那一個 NULL inode 就會被插入到相應的dentry hash表中(這就是所謂的一個 negative dentry)。此函數必須只有在發生真正的錯誤的時候才返回一個錯誤碼,否則就像 create(2),mknod(2),mkdir(2) 等等會失敗的系統呼叫一樣建一個新的 inode。如果你相重載 dentry的方法,那你就應初始化該 dentry 的 "d_dop";就是一個指向架構"dentry_operations" 的指標。在得到目錄的 inode 的信號量之後才能呼叫該方法。


link
由系統呼叫 link(2) 呼叫。用來實現硬聯接。你可能要像在 create() 方法那樣,呼叫 d_instantiate() 。


unlink
由系統呼叫 unlink(2) 呼叫。用來實現對 inode 刪除操作。


symlink
由系統呼叫 symlink(2) 呼叫。用來實現符號聯接。你可能要像在 create()
方法那樣,呼叫 d_instantiate() 。


mkdir
由系統呼叫 mkdir(2) 呼叫。用來實現新建次目錄。你可能要像在 create()
方法那樣,呼叫 d_instantiate() 。


rmdir︰由系統呼叫 rmdir(2) 呼叫。用來實現對次目錄的刪除。


mknod
由系統呼叫 mknod(2) 呼叫,用來新建一個設備(字符,塊) inode 或一個named pipe(FIFO) 或 socket。只在要支援新建這些類型的 inode 時需要。你可能要像在 create() 方法那樣,呼叫 d_instantiate() 。

rename
由系統呼叫 rename(2) 呼叫,用來對對象重命名,將其所在目錄與其自身的名字修改成第二個 inode 和 dentry 所提供的目錄和名字。


readlink
由系統呼叫 readlink(2) 呼叫。用來實現對符號聯接的讀操作。


follow_link
VFS 操作符號聯接所指的 inode 時呼叫該方法。用來支援符號聯接。該方法返回一個 put_link() 需要的 void 指標。


put_link
VFS 要釋放由 follow_link() 所定位的資源時,呼叫該方法。該方法的第三個參數是 follow_link() 返回的指標。被那些 page cache 不穩定的檔案系統(比如 NFS) 使用。(比如,當沿著符號聯接開始訪問時,把頁放到 cache中,但在訪問結束時,這個頁已經不在 cache 中了)


truncate
VFS 要改變檔案的大小的時候,呼叫該方法。呼叫之前,VFS 將相應的
inode 的 i_size 改成新值。該方法由系統呼叫 truncate(2) 以及相關功能呼叫。


permission
VFS 在一POSIX類檔案系統上檢查訪問權限時,呼叫該方法。


setattr
VFS 設定一檔案的屬性時,呼叫該方法。該方法由系統呼叫 chmod(2) 和相關係統呼叫呼叫。


getattr
VFS 訪問一檔案的屬性時,呼叫該方法。該方法由系統呼叫 stat(2) 和相關係統呼叫呼叫。


setxattr
VFS 為一檔案設定附加屬性時,呼叫該方法。附加屬性是與一 inode 相關聯的 name:value 對。


getxattr
VFS 訪問一特定附加屬性的值的,呼叫該方法。該方法由函數 getxattr(2)呼叫。


listxattr
VFS 列出一檔案所有的附加屬性,呼叫該方法。該方法由系統呼叫listxattr(2) 呼叫。


removexattr
VFS 刪除檔案的一個附加屬性時,呼叫該方法。該方法由系統呼叫removexattr(2) 呼叫。


address space 對象
==================


address space 對象是用來對 page cache 中的頁進行分類和管理的。它可以用來跟蹤一個檔案(或其它什麼)的特定頁,也可以用來跟蹤檔案中映射到進程位址空間的映射。


一個 address space 可以提供一系列完全不同但又相關的功能。這就包括了體現內存的壓力,按位址找相應的頁,以及跟蹤那些被打上 dirty 或 writeback 標記的頁。


第一個功能可能獨立使用。VM 可以透過在臟頁上呼叫其 writepage 方法將臟頁變成乾淨頁,也可以對乾淨頁設定 PagePrivate 然後呼叫 releasepage 將其釋放,從而可以重新使用他們。address space 是不會注意到釋放那些沒有設定 PagePrivate 並沒有外部引用計數的乾淨頁的。


要實現此功能,頁要由 lru_cache_add 掛到一 LRU 上去,在使用時呼叫mark_page_active 。


頁一般是儲存在一 radix 樹中,根據 page->index 索引。該樹為每一個頁維護著關於PG_Dirty 和 PG_Writeback 的訊息,這樣設定了這些標誌的頁會被很快的找到。


臟標識主要是由 mpage_writepages 使用 - ->writepages 方法的預設值。在那些用標識找到的臟頁上呼叫 ->writepages。如果不是使用 mpage_writepages(比如,address space提供自己的 ->writepages),那麼基本上就不使用標識 PAGECACHE_TAG_DIRTY 了。當write_inode_now 和 sync_inode 使用該標識(透過 __sync_single_inode) 檢查->writepages 是否已經成功的將整個 address_space 寫出了。


Writeback 標識是用來等待所有的寫回操作完成,是由 filemap*wait* 和 sync_page* 函數透過 wait_on_page_writeback_range 使用的。在等待的期間, ->sync_page(如果定義的話)將會在每一個被發現要求寫回的頁上呼叫。


address_space 處理函數可以對頁添加附加訊息,典型的是就使用 'struct page' 中的'private'。在添加附加訊息的同時,要設定 PG_Private 。這會讓其它的 VM 函數為處理該訊息而呼叫相關的處理函數。


address space 就好像是存儲設備與應用程式之間的中間層一樣。數據是以整個頁的模式讀到 address space 中的,然後以拷貝整個頁或內存映射該頁的模式提供給應用程式。應用程式在把數據寫入 address space 後,一般是再以頁為單位寫回到存儲設備中,只是
address space 對寫多少數據量有最好的控制。


讀操作只要求 'readpage'。寫操作就比讀操作更複雜一些,使用prepare_write/commit_write 或 set_page_dirty 將數據寫入 address space 中,使用writepage,sync_page 和 writepages 將數據寫回到存儲設備上。


在 address space 中增加或刪除頁的操作要由其所屬的 inode 的 i_mutex 保護。


當數據寫入一個頁之後,該頁的 PG_Dirty 就要被設定。一般該標誌會一直保持不變,直到對該頁進行 writepages 呼叫。此時,要取消 PG_Dirty 並設定 PG_Writeback 。在PG_Dirty 取消之後,才能開始真正的寫操作。一旦操作完成,就取消 PG_Writeback。


寫回利用了一 writeback_control 架構...(類似於pagevec,也是一種集中操作的模式@lb)


架構 address_space_operations
-----------------------------


該架構描述了 VFS 是怎樣在你的檔案系統上實現一檔案到 page cache 的映射的。在版本為2.6.16的核心中,定義了如下成員︰


struct address_space_operations {
int (*writepage)(struct page *page, struct writeback_control *wbc);
int (*readpage)(struct file *, struct page *);
int (*sync_page)(struct page *);
int (*writepages)(struct address_space *, struct writeback_control *);
int (*set_page_dirty)(struct page *page);
int (*readpages)(struct file *filp, struct address_space *mapping,
struct list_head *pages, unsigned nr_pages);
int (*prepare_write)(struct file *, struct page *, unsigned, unsigned);
int (*commit_write)(struct file *, struct page *, unsigned, unsigned);
sector_t (*bmap)(struct address_space *, sector_t);
int (*invalidatepage) (struct page *, unsigned long);
int (*releasepage) (struct page *, int);
ssize_t (*direct_IO)(int, struct kiocb *, const struct iovec *iov,
loff_t offset, unsigned long nr_segs);
struct page* (*get_xip_page)(struct address_space *, sector_t,
int);
/* migrate the contents of a page to the specified target */
int (*migratepage) (struct page *, struct page *);
};


writepage
VM 要將一臟頁上的數據寫回到存儲設備時,呼叫。該函數也可以在保持頁數據的一致性(比如,'sync'),或是釋放內存(flush)呼叫。它們之間的可以用wbc->sync_mode 區分。
標誌 PG_Dirty 已經被取消,而 PageLocked 的返回值為 true 。writepage 將開始寫操作,並設定 PG_Writeback,並且在寫操作完成之後,無論是否同步確保不再鎖住該頁。
如果 wbc->sync_mode 是 WB_SYNC_NONE,->writepage 在寫操作出現問題時,不會作過多的嘗試,並且還會寫出那些映射中相對容易一些的頁(比如,由於內部的倚賴關係)。如果函數沒有選擇開始寫出,就會返回 AOP_WRITEPAGE_ACTIVATE,這樣VM就不會在該頁上再呼叫 ->writepage。


更多細節請看文檔 "Locking"。


readpage
VM 在從存儲設備中讀取一個頁時,呼叫。該頁在 readpage 呼叫時應該被鎖住,並在讀操作完成之後解鎖並標識為 uptodate 。如果 ->readpage 發現因為某種原因,它需要把頁解鎖,它可以對其解鎖,然後就返回AOP_TRUNCATED_PAGE。此時,該頁將會被重新定位,重新鎖住,如果都成功的話,->readpage 將被再次呼叫。


sync_page
VM 在通知存儲設備執行某頁的所有已提交的I/O 操作時,呼叫。和該address_space 對象有關,但是其它頁的I/O操作此時也可能被執行。


該函數是個可選函數,雖然PG_Writeback 設定說明該頁在等待寫回操作的完成,但該函數只對那些該標誌設定的頁呼叫,


writepages
VM 在寫出多個與某個 address_space 對象有關的頁時,呼叫。如果wbc->sync_mode 是 WBC_SYNC_ALL,writeback_control 架構中將是必須要寫出的頁的範圍。如果是WBC_SYNC_NONE,那麼就要給出要寫出頁的數量,並且儘可能的將這些頁
寫出。
如果 ->writepages 沒有給出,就用 mpage_writepages 代替。該函數要把address space 對象中的臟頁傳給->writepage。


set_page_dirty
VM 把頁標識為臟時,呼叫。
只是在 address space 中的頁有私有數據時使用,並在該頁被標識為臟時,更新私有數據。比如,當一個內存映射頁被更改時,呼叫。如果定義,其應該設定 PageDirty 標誌 和 radix tree 中的PAGECACHE_TAG_DIRTY 標識。


readpages
VM 讀多個與 address space 相關聯的頁時,呼叫。這基本上就是一個向量版的 readpage。此時不再是請求一個頁,而是請求多個頁。readpages 只是用來 read-ahead,所以忽略讀時發生的錯誤。如果全部都沒有成功,就簡單的放棄操作。


prepare_write
VM 中的一般寫操作方法在設定對頁的寫操作時,呼叫。對於 address space來說,就是要寫出的範圍(以位元組為單位)。如果必要, address space 會分發一定的空間和作一些內部的操作來檢查寫操作是否會完成。如果寫操作將更新存儲設備上的任意的一小部分(
甚至是一個基本塊中的一小部分),那麼這些塊將會被優先讀(如果它們還沒有被讀進來的話),這樣被更新的塊就正確的寫出了。
該頁應該被鎖住。如果 prepare_write 要像 readpage 那樣解頁的鎖的話,是可以的,只是要返回 AOP_TRUNCATED_PAGE。
此時,prepare_write 將會在一重新得到鎖的頁上再次嘗試執行。


commit_write
如果 prepare_write 執行成功,新的數據就將被拷貝到頁中,然後呼叫commit_write 。一般該函數會更新所屬檔案的大小(如果正確的話)並且將相應的 inode 標識為臟,然後再執行一些相關的操作。此函數會儘可能的避免返回一個錯誤 - 錯誤應該被
prepare_write 處理完了。


bmap
VFS 將一對象中的邏輯塊偏移映射到一物理塊號上時,呼叫。該方法由 FIBMAP ioctl 使用在交換檔案上。檔案必須有一個對塊設備的穩定的映射,這樣才能交換內存到該檔案。交換系統不使用檔案系統,而是使用 bmap 來查找塊在檔案中的位置並直接使用這些位址。


invalidatepage
如果頁的 PagePrivate 設定了,當該頁的部分或所有的內容要從 address space 中刪除時,呼叫。一般來說,這要么是一次修改,要么是使 address space 完全變成無效。(後者的 'offset' 將是0)。任何的與該頁相關聯的的私有數據都要被更新,這樣才能反映出此次修改。因為該頁必須能完全忽略,所以如果 offset 為0,私有數據就會被釋放。可以透過呼叫 ->releasepage 函數 完成釋放,只是此時的釋放必須成功。


releasepage:
在 PagePrivate 頁上呼叫,以表示如果可以,就釋放該頁。 ->releasepage 將會從頁中刪除所有的私有數據,並清除設定的 PagePrivate 標誌。同時,也可能將頁從address space 中刪除。如果因為什麼原因函數執行失敗,可以透過返回0表示失敗。
該函數用在兩個完全不同但又有關係的情況中。一個是當 VM 在找一個沒有 active users 的乾淨頁,並要將其回收時呼叫。如果 ->releasepage 成功,該頁將會從 address space 中刪除,並被回收。


另一個是,在一個要將 address space 中部分或全部頁都變成無效的時候。當呼叫系統調用fadvice(POSIX_FADV_DONTNEED),或像 nfs 或 9fs 呼叫invalidate_inode_pages2() 那樣由檔案系統明確的要求的時候。
如果檔案系統呼叫了這樣的一個系統呼叫,並需要確認所有頁都變成了無效的了,就由releasepage 來保證現實。但有可能清除了 PageUptodate 標誌,但沒有釋放其私有數據。


direct_IO
當 generic read/wirte 要執行直接IO操作時,呼叫。直接IO就是忽略page cache,直接在存儲設備與應用程式的位址空間傳遞數據。


get_xip_page
VM要根據塊號找到在 address space 中的相應的頁時,呼叫。除非相應的檔案系統被卸載,否則該頁一直有效。需要使用 execute-in-place(XIP) 的檔案系統需要現實此函數。在 fs/ext2/xip.c 中有一個實現的例子。


migrate_page
這只要是用來壓縮物理內存的使用的。如果VM要想重新定位一個頁(可能關閉一內存卡幾乎就是個錯誤)它會傳給該函數舊頁和一個新的頁。 migrate_page 會傳輸舊頁上所有的私有數據並更新其所有的引用。


檔案對象
========


一個檔案對象代表了一個由一個進程打開的檔案。


架構 file_operations
--------------------


該架構描述了 VFS 是怎樣處理一個打開的檔案的。在版本為2.6.17的核心中,定義了如下成員︰


struct file_operations {
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 *);
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 (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);
ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);
ssize_t (*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void *);
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 (*dir_notify)(struct file *filp, unsigned long arg);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, struct pipe_inode_info *,size_t, unsigned int);
};


除非注明,否則所有的方法呼叫都不用先上鎖。


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) 呼叫。


ioclt
由系統呼叫 ioctl(2) 呼叫。


unlocked_ioctl
由系統呼叫 ioctl(2) 呼叫。不需要 BKL(Big Kernel lock,lb注)的文件系統使用該函數代替 ioctl()。


compat_ioctl
當在64位的核心上使用32位的系統呼叫時,由 ioctl() 呼叫。


mmap
由系統呼叫 mmap(2) 呼叫。


open
當 VFS 打開一 inode 時,呼叫。當 VFS 打開一檔案時,會新建一新的 "struct file"。然後為這個新建的檔案架構呼叫 open 函數。你可能認為該 open 函數真正是屬於 "struct inode_operation",你是對的。我認為用這種模式是因為這樣作可以使檔案系統實現起來更加的簡單。如果 file structure 中的 "private_data" 是指向一 device structure,open() 就是個初始化該私有數據的好地方。


flush
系統呼叫 close(2) 在清理一個檔案時,呼叫。


release
當關閉被打開的最後一次引用時,呼叫。


fsync
由系統呼叫 fsync(2) 呼叫。


fasync
當在一打開異步模式的檔案上呼叫系統呼叫 fcntl(2) 時,呼叫。


lock
當 fcntl(2) 執行 F_GETLK,F_SETLK 和 F_SETLKW 時呼叫。


readv
由系統呼叫 readv(2) 呼叫。


writev
由系統呼叫 writev(2) 呼叫。


sendfile
由系統呼叫 sendfile(2) 呼叫。


get_unmapped_area
由系統呼叫 mmap(2) 呼叫。


check_flags
當 fcntl(2) 執行 F_SETFL 時,呼叫。


dir_notify
當 fcntl(2) 執行 F_NOTIFY 時,呼叫。


flock
由系統呼叫 flock(2) 呼叫。


splice_write
當 VFS 將一 pipe 和一 file 的數據聯接起來的時候,呼叫。由系統呼叫splice(2) 使用。


splice_read
當 VFS 將一 pipe 和一 file 的數據聯接起來的時候,呼叫。由系統呼叫splice(2) 使用。
(恐譯有誤,原文︰ called by the VFS to splice data from a pipe to a file.
This method is used by the splice(2) system call)


注意,檔案操作是由 inode 所處的檔案系統實現的。當打開一設備節點(字符或塊設備),大多數的檔案系
統都會呼叫 VFS 中特殊的函數,以定位所請求設備的訊息。這些函數為設備驅動代替了檔案系統的
file operations ,然後為檔案呼叫新的 open()。這就是如果在檔案系統中打開一設備檔案,最後是
呼叫的設備驅動的 open() 。


 


Directory Entry CAche (dcache)
==============================


架構 dentry_operations
----------------------


該架構描述了一檔案系統是如何重載標準的 dentry 操作的。dentries 和 dcache 是在 VFS 內
部的,與檔案系統的實現無關。設備驅動不涉及這部分內容。這些方法可以設定為 NULL,因為它們要么使
用實現了的(可選),要么 VFS 使用預設的函數。在版本為 2.6.13 的核心中,定義了下列成員函數︰


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 *);
};


d_revalidate
當 VFS 使一 dentry 重新有效時,呼叫。當在 dcache 中使用名字查找一dentry 中,呼叫。大多數的檔案系統置該項為 NULL ,因為所有在 dcache 中的 dentry都是有效的。


d_hash
當 VFS 要向 hash 表中加入一 dentry 中,呼叫。


d_compare
比較 dentry 時,呼叫。


d_delete
當刪除只有一個引用的 dentry 時,呼叫。這也就是說儘管 dentry 是有效的,並還在 dcache 中,但該 dentry 已經沒有用了。


d_release
回收一 dentry 時,呼叫。


d_input
當一 dentry 相應的 inode 在其之前就被回收的時候,呼叫。當 d_input 為 NULL時,VFS 預設呼叫 iput()。如果你定義了該方法,那你就要自己呼叫 iput() 。


每一個 dentry 都有一個指向其父 dentry 的指標,就像是一個子 dentry 的 hash 清單。子
dentry 基本上就像是目錄中的檔案一樣。


Directory Entry Cache API
-------------------------


這裡有一系列的函數幫助檔案系統處理 dentry︰


dget
為一已存在的 dentry 打開一新的引用(只是增加使用計數)


dput
關閉一 dentry 的引用計數(減少使用計數)。如果使用計數為0,就呼叫 'd_delete‘,並且如果該 dentry 仍在父 dentry 的hash 表上,就把它移到未使用清單上。把 dentry放到未使用清單上就意味著如果系統需要內存,就可以把未使用清單上的 dentry 回收。如果
dentry 已經不在 hash 表上了並且其使用計數為0,此時該 dentry 在呼叫 d_delete之後被回收。


d_drop
把一 dentry 從父 hash 表上移除。其後的 dput() 呼叫將會回收那些使用計數為0的dentry。


d_delete
刪除一 dentry 。如果 dentry 沒有其它引用,就把該 dentry 變成一negative dentry(呼叫d_iput())。如果還有其它引用,就呼叫 d_drop()。


d_add
增加一 dentry 到其父 hash 清單,然後呼叫 d_instantiate()。


d_instantiate
為 inode 增加一 dentry 到一 alias hash 清單,然後更新 d_inode 。該 inode 中的 i_count 要被設定或增加。如果 inode 為NULL,該 dentry就被稱為negative dentry。此函數一般是在為一已存在的 negative dentry 新建一 inode 時呼叫。


d_lookup
根據指定的路徑和父 dentry,查找一 dentry。從 dcache hash 表中查找指定名字的子 dentry。如果找到,增加其引用計數,並返回該 dentry。呼叫者必須在結束對該dentry 的使用時,呼叫 d_put()。


如想進一步了解 dentry 鎖, 請看文檔 Documentation/filesystems/dentry-locking.txt。


資料
====


(注意,有些資料已經不是針對於最新的核心版本了。)


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>




[回主選單]

arrow
arrow
    全站熱搜

    立你斯 發表在 痞客邦 留言(0) 人氣()