翻譯自Linux文檔中的vfs.txtnode
VFS(Virtual File System)是內核提供的文件系統抽象層,其提供了文件系統的操做接口,能夠隱藏底層不一樣文件系統的實現。linux
VFS經過open()
、stat()
這些接口傳入的文件名搜索dcache,快速找到文件名對應的dentry。dentry的結構是struct dentry
,這只是一個內存結構,不會持久化到磁盤中,僅僅是爲了提高性能而已。api
dentry cache是整個文件空間的視圖,可是大部分狀況下並不能同時將全部dentry都加載到內存。VFS會在執行lookup()
時,建立還不在內存中的dentry。數組
inode表明真實存儲在文件系統中的對象,好比文件、目錄、FIFO等等。inode的結構是struct inode
。一個dentry只能指向一個inode,但一個inode能夠被多個dentry指向。緩存
對於塊設備的文件系統,inode存儲在磁盤上,並在須要的時候拷貝到內存中,修改inode後又會寫回磁盤進行持久化。對於僞文件系統,inode保存在內存中。網絡
VFS調用lookup()
從指定路徑的第一級目錄的dentry開始查找對應的inode。lookup()
的真實實現是由inode所在的底層文件系統提供的。併發
打開一個文件實際就是建立一個file對象。file的結構是struct file
,其有指向一個dentry的指針,和一組操做file的函數指針。這些信息是從inode中獲取的。在打開文件的最後,file會被加入當前進程的文件描述符表中。app
用戶的讀、寫、關閉文件都經過fd
進行,內核會能夠根據fd
獲取到對應的file對象,這些操做最終都會調用到底層文件系統提供的函數。框架
註冊和註銷文件系統的函數以下socket
#include <linux/fs.h> extern int register_filesystem(struct file_system_type *); extern int unregister_filesystem(struct file_system_type *);
當掛載文件系統時,VFS會調用底層文件系統指定的mount()
函數,mount()
會返回一個dentry做爲文件系統的根目錄。全部已掛載的文件系統,能夠在/proc/filesystems中看到。
該結構用於描述文件系統,自內核2.6.30,有以下定義
struct file_system_type { const char *name; int fs_flags; struct dentry *(*mount) (struct file_system_type *, int, const char *, void *); 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; };
mount()
的參數以下
struct file_system_type* fs_type
:描述文件系統,其中部分底層文件系統初始化。int flags
:掛載的標誌,如FS_REQUIRES_DEV
,FS_NO_DCACHE
等。const char *dev_name
:掛載的設備名。void *data
:任意的選項,一般是ASCII字符串。mount()
成功時,要求加鎖並得到superblock的活動引用,返回dentry(能夠是子目錄的dentry),失敗時返回ERR_PTR(error)
。
grab_super()
是獲取活動引用的函數,即讓spuer_block::a_active
加1
mount()
會建立一個superblock,其結構是struct superblock
。struct superblock
中有一個指向struct super_operations
的指針s_op
,其由一系列操做文件系統的函數指針組成。
VFS提供了以下方法掛載不一樣類型的文件系統
mount_bdev()
:掛載基於塊設備的文件系統。mount_nodev()
:掛載不基於設備的文件系統。mount_single()
:掛載共享實例的文件系統。這些函數都有一個入參int (*fill_super)(struct super_block *, void *, int)
,其用於初始化struct superblock
的部分字段。
fill_super的參數以下
struct super_block *sb
:指向superblock。void *data
:mount的參數,一般是一個ASCII字符串,須要自行解析。int silent
:肯定是否輸出錯誤信息。一個superblock表明了一個已掛載的文件系統。
VFS經過struct super_operations
操做superblock
struct super_operations { struct inode *(*alloc_inode)(struct super_block *sb); void (*destroy_inode)(struct inode *); void (*dirty_inode) (struct inode *, int flags); int (*write_inode) (struct inode *, int); void (*drop_inode) (struct inode *); void (*delete_inode) (struct inode *); void (*put_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 dentry *); 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); int (*nr_cached_objects)(struct super_block *); void (*free_cached_objects)(struct super_block *, int); };
除非有額外的說明,不然VFS不會加鎖調用這些函數。這意味着這些函數阻塞時,不會影響到其餘線程。全部的函數只會在進程上下文調用,不會在中斷上下文中調用。
struct inode
。一般狀況下,文件系統會自定義本身的inode結構,除了包含struct inode
外,還會包含和底層文件系統相關的字段。inode->i_lock
。drop_inode應該是NULL(正常UNIX文件系統語義)或者是generic_delete_inode()
(不考慮引用計數,強制清除inode,適用於不想緩存inode的文件系統)。struct super_operations
就沒有這個字段,取而代之的是evict_inodesuperblock
,調用前鎖定superblock lock
。kernel lock
。struct super_operations
就沒有這個字段若是這些函數內會執行批量任務,那麼必須包含支持從新調度的函數,這使得VFS無需擔憂這些函數長時間處理的問題。
設置inode時必須初始化struct inode
的i_op
字段,其指向struct inode_operations
,該結構包含了一系列操做inode
的函數。
當文件系統須要支持擴展屬性時,能夠指定superblock
的s_xattr
字段,其指向一個一NULL結尾的struct xattr_handler
數組。擴展屬性是一個name-value對。
struct xattr_handler { const char *name; const char *prefix; int flags; /* fs private flags */ bool (*list)(struct dentry *dentry); int (*get)(const struct xattr_handler *, struct dentry *dentry, struct inode *inode, const char *name, void *buffer, size_t size); int (*set)(const struct xattr_handler *, struct dentry *dentry, struct inode *inode, const char *name, const void *buffer, size_t size, int flags); };
getxattr(2)
流程中調用setxattr(2)
和removexattr(2)
流程中調用當文件系統沒有xattr的處理方法或者沒有匹配的屬性時,會返回-EOPNOTSUPP
。
一個inode表明了一個文件系統對象
struct inode_operations
struct inode_operations
描述了VFS如何操做inode
struct inode_operations { int (*create) (struct inode *,struct dentry *, umode_t, bool); struct dentry * (*lookup) (struct inode *,struct dentry *, unsigned int); 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 *,umode_t); int (*rmdir) (struct inode *,struct dentry *); int (*mknod) (struct inode *,struct dentry *,umode_t,dev_t); int (*rename) (struct inode *, struct dentry *, struct inode *, struct dentry *, unsigned int); int (*readlink) (struct dentry *, char __user *,int); const char *(*get_link) (struct dentry *, struct inode *, struct delayed_call *); int (*permission) (struct inode *, int); int (*get_acl)(struct inode *, int); int (*setattr) (struct dentry *, struct iattr *); int (*getattr) (const struct path *, struct kstat *, u32, unsigned int); ssize_t (*listxattr) (struct dentry *, char *, size_t); void (*update_time)(struct inode *, struct timespec *, int); int (*atomic_open)(struct inode *, struct dentry *, struct file *, unsigned open_flag, umode_t create_mode); int (*tmpfile) (struct inode *, struct dentry *, umode_t); };
除非有額外說明,不然全部方法都不會持鎖調用。
open(2)
和creat(2)
調用。只有當須要支持常規文件時,才實現該函數。入參dentry必須沒有指向任何inode
的,最後須要調用d_instantiate()
將新建立的inode加入到這個dentry中。d_add()
將inode加入到dentry中,而且inode的引用計數要加1。若是inode不存在,則須要將NULL加入到dentry,表明dentry是無效的。create(2)
, mknod(2)
, mkdir(2)
去建立inode
,但這些函數仍是會由於lookup失敗的緣由而再次失敗。dentry
的操做函數,能夠給d_dop
賦值。這些函數被調用時,會使用目錄inode的信號量。d_instantiate()
。d_instantiate()
。d_instantiate()
-EINVAL
。當前如下flag已經實現-EEXIST
。可是目前VFS已經檢查了新文件名是否存在。-ECHILD
,此後會在ref-walk模式下再嘗試一次。chmod(2)
和相關的系統調用觸發。stat(2)
和相關的系統調用觸發。listxattr(2)
觸發。i_version
。若是該方法未定義,那麼VFS會自行更新inode,而後調用mark_inode_dirty_sync()
標記該inode
爲髒inode
。finish_no_open()
通知調用者。該方法只在最後一步是無效或者須要lookup時才調用。緩存有效的dentry須要在f_op->open()
中完成。若是文件建立成功了,則須要在file->f_mode
設置FMODE_CREATED
。若是指定了O_EXCL
,該方法須要在文件存在時返回失敗。返回成功就設置FMODE_CREATED
。open(O_TMPFILE)
的最後被調用。這是一個可選的方法,等同於在一個指定的目錄下,原子性地建立、打開、刪除一個文件。address space對象用於組織和管理page cache中的page。它能夠追蹤一個文件使用的page以及一個文件被映射到進程空間的page。它能夠根據地址查找page,追蹤被標記爲Dirty或Writeback的page。
VM能夠調用writepage()
來清理髒頁,或者設置PagePrivate
後調用releasepage()
來釋放乾淨的頁以便從新使用。若是乾淨的頁沒有設置PagePrivate
或者沒有外部引用,就會在沒有通知address space的狀況下被釋放掉。爲了實現這些功能,page須要經過lru_cache_add()
方法放到LRU中,當page被使用時須要調用mark_page_active()
。
page使用一個基數樹(radix tree)來保存。這棵樹維護了全部page的PG_Diry
和PG_Writeback
狀態,所以這些頁能被快速找到。
mpage_writepages()
是默認的writepages
,它使用Dirty
標記查找髒頁並寫回。若是沒有使用mpage_writepages()
(多是address space提供了本身的writepage
),那麼PAGECACHE_TAG_DIRTY
標記就不會被使用。write_inode_now()
和sync_inode()
使用PAGECACHE_TAG_DIRTY
來檢查writepages
是否將整個address space寫回到存儲中。
filemap*wait*
和sync_page*
會使用Writeback
標記,並經過filemap_fdatawait_range()
等待全部寫回操做完成。
address space的處理方法能夠經過page::private
附加一些額外的信息到page中。若是附加了信息,那麼必須設置PG_Private
。VM會調用address space的額外函數來處理這些數據。
address space做爲存儲和應用之間的中間層。每次從存儲讀取一頁的數據到address space中,供應用讀取。應用能夠將任意大小的數據寫入address space,但最後以頁爲單位寫入到存儲中。
讀進程只須要readpage
。寫進程則複雜一點,須要write_begin/write_end
將數據寫入到address space,還須要writepage
和writepages
將數據寫到存儲中。
address space增刪頁受inode::i_mutex
的保護。
當數據被寫入到page中時,須要設置PG_Dirty
(set_page_dirty()
)。當須要writepages
時會設置PG_Writeback
並清除PG_Dirty
。標記爲PG_Writeback
的page能夠在任意時間寫回到存儲,一旦寫回完成後,該標誌被清除。
寫回會使用struct writeback_control
給writepage
和writepages
提供寫回的請求和限制信息,該結構也用於返回寫回的結果。
大部分使用緩存IO的應用都會週期性地調用同步接口(如fsync()
,fdatasync()
,msync()
,sync_file_rage()
)來保證數據被寫回到存儲中。若是寫回時發生錯誤,這些接口應當返回錯誤,但僅在第一次返回錯誤,後續應當返回0,除非又有新的數據寫入且再次調用同步接口時又發生寫回錯誤。
理想狀況下,內核應當以文件爲單位,返回其寫回的錯誤。但實際上page cache並無以文件爲單位追蹤髒頁,所以當發生寫回錯誤時,內核沒法知道是哪一個文件發生的寫回錯誤。當前當發生寫回錯誤時,內核給全部打開的文件都返回錯誤,即便這個文件並無寫入或者已經寫回成功了。
想使用該框架的文件系統應當調用mapping_set_error()
來記錄錯誤。在寫回數據後,文件系統還應當調用file_check_and_advance_wb_err()
來確保struct file::f_wb_err
記錄的是正確的寫回錯誤。
VFS使用該結構操做文件的page cache。
struct address_space_operations { int (*writepage)(struct page *page, struct writeback_control *wbc); int (*readpage)(struct file *, 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 (*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); sector_t (*bmap)(struct address_space *, sector_t); void (*invalidatepage) (struct page *, unsigned int, unsigned int); int (*releasepage) (struct page *, int); void (*freepage)(struct page *); ssize_t (*direct_IO)(struct kiocb *, struct iov_iter *iter); /* isolate a page for migration */ bool (*isolate_page) (struct page *, isolate_mode_t); /* migrate the contents of a page to the specified target */ int (*migratepage) (struct page *, struct page *); /* put migration-failed page back to right list */ void (*putback_page) (struct page *); int (*launder_page) (struct page *); int (*is_partially_uptodate) (struct page *, unsigned long, unsigned long); void (*is_dirty_writeback) (struct page *, bool *, bool *); int (*error_remove_page) (struct mapping *mapping, struct page *page); int (*swap_activate)(struct file *); int (*swap_deactivate)(struct file *); };
PG_Dirty
,設置PageLocked
爲true
,而後writepage開始寫回,並設置PG_Writeback
,而後在完成寫回後解鎖page。wbc->sync_mode
是WB_SYNC_NONE
,那麼writepage在沒法完成寫回指定頁的狀況下,返回AOP_WRITEPAGE_ACTIVATE
,這樣VM就不會一直爲該頁調用writepage了。uptodate
並解鎖。若是readpage
須要解鎖,也能夠解鎖,並返回AOP_TRUNCATED_PAGE
。在這種狀況下,VM會從新安置和加鎖該頁,再次調用readpage
。wbc->sync_mode
是WBC_SYNC_ALL
,那麼writeback_control
就會指定一組必須寫回的頁。若是是WBC_SYNC_NONE
的話,則只要求儘量寫入nr_to_write頁。若是該方法未定義,那麼會用mpage_writepages
來替代。該方法會從address space中獲取全部標記爲DIRTY
的頁,而後傳遞給writepagePageDirty
標誌和基數樹中的PAGECACHE_TAG_DIRTY
標誌。generic_perform_write()
調用,用於告知文件系統準備在指定偏移處寫len字節的數據。address space應預留一些資源保證完成此次寫入操做。若是寫入要更新頁中的一部分,那麼須要將整頁塊讀取到內存中,即讀改寫。文件系統經過pagep返回page cache中已鎖定的page。調用者會將數據寫入到該page中。須要支持實際寫入的數據長度小於傳給write_begin
的長度的場景。可經過fsdata保存須要傳遞給write_end的數據。struct inode::i_size
。失敗返回小於0,不然返回實際拷貝到page cache的長度。ioctl(FIBMAP)
和swap文件。爲了swap一個文件,文件必須固定地映射到一個塊設備上。swap系統會經過bmap來找到文件所在的塊,而後直接存儲,而不經過文件系統。PagePrivate
,那麼當部分或所有的page從address space中被移除時,就會調用invalidatepage。PagePrivate
的page,該方法須要移除page的私有數據,並清除PagePrivate
。若是該方法失敗了則返回0。releasepage用於兩種場景fadvise(POSIX_FADV_DONTNEED)
觸發,或者文件系統明確請求(好比當nfs和9fs認爲cache的數據已經與存儲不一致時,會調用invalidate_inode_pages2()
)generic read/write
類函數調用來執行direct IO。__SetPageIsolated
將該頁標記爲PG_isolated
。PageDirty
和PageWriteback
,可是某些文件系統會有更復雜的狀態(好比NFS的unstable pages須要避免被回收),或者由於鎖的問題沒有設置這些標誌。該方法能夠向VM代表該頁是髒頁或正在寫回的頁,讓VM中止回收該頁。truncation
是正常的話,一般設置爲generic_error_remove_page()
。主要用於處理內存失敗。實現該方法,意味着你會處理那些頁,除非你已經鎖定或者增長了引用計數。一個file對象表明進程打開的一個文件。
VFS使用struct file_operations
操做一個打開的文件。
自v4.18,struct file_operations
定義以下
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 (*read_iter) (struct kiocb *, struct iov_iter *); ssize_t (*write_iter) (struct kiocb *, struct iov_iter *); int (*iterate) (struct file *, struct dir_context *); int (*iterate_shared) (struct file *, struct dir_context *); __poll_t (*poll) (struct file *, struct poll_table_struct *); 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 *, loff_t, loff_t, 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 **, void **); long (*fallocate)(struct file *file, int mode, loff_t offset, loff_t len); void (*show_fdinfo)(struct seq_file *m, struct file *f); #ifndef CONFIG_MMU unsigned (*mmap_capabilities)(struct file *); #endif ssize_t (*copy_file_range)(struct file *, loff_t, struct file *, loff_t, size_t, unsigned int); int (*clone_file_range)(struct file *, loff_t, struct file *, loff_t, u64); int (*dedupe_file_range)(struct file *, loff_t, struct file *, loff_t, u64); int (*fadvise)(struct file *, loff_t, loff_t, int); };
除非額外說明,不然這些函數都不會持鎖調用。
struct file
,並初始化strutc file::private_data
close(2)
調用fcntl(2)
調用fcntl(2)
調用,執行F_GETLK
,F_SETLK
,F_SETLKW
命令dentry屬於VFS和單個文件系統,與設備驅動無關。每一個dentry都有一個指向其父dentry的指針,以及一個子dentry的hash鏈表。
struct dentry_operations
是可選的,能夠設置爲NULL,或是使用VFS默認的函數。
v2.6.22中,其定義以下
struct dentry_operations { int (*d_revalidate)(struct dentry *, unsigned int); int (*d_weak_revalidate)(struct dentry *, unsigned int); int (*d_hash)(const struct dentry *, struct qstr *); int (*d_compare)(const struct dentry *, unsigned int, const char *, const struct qstr *); int (*d_delete)(const struct dentry *); int (*d_init)(struct dentry *); void (*d_release)(struct dentry *); void (*d_iput)(struct dentry *, struct inode *); char *(*d_dname)(struct dentry *, char *, int); struct vfsmount *(*d_automount)(struct path *); int (*d_manage)(const struct path *, bool); struct dentry *(*d_real)(struct dentry *, const struct inode *); };
iput()
,不然須要自行調用iput()
。如下是操做dentry的函數
dget()
:獲取一個已存在的dentry的引用dput()
:歸還一個dentry的引用。若是引用計數爲0,且該dentry還在其父dentry的hash表中,則調用d_delete()
檢查該dentry是否還應該緩存。若是須要緩存,則放入LRU鏈表中。d_drop()
:從父dentry的hash表中刪除dentryd_delete()
:刪除一個dentry,若是沒有其餘引用,則該dentry變爲一個無效的dentry(即指向NULL inode),d_iput()
就會被調用。若是還有引用,則調用d_drop()
d_add()
:將dentry加入到其父dentry的hash表中,而後調用d_instantiate()
d_instantiate()
:將dentry加入到inode的dentry鏈表中,並更新dentry指向的inode(即struct dentry::d_inode
)。inode的引用計數也會增長。d_lookup()
:查找dentry,若是找到了則增長引用計數後返回。