翻譯自Linux文件中的vfs.txt
介紹
VFS(Virtual File System)是核心提供的檔案系統抽象層,其提供了檔案系統的操作介面,可以隱藏底層不同檔案系統的實現。
Directiry Entry Cache(dcache)
VFS通過open()
、stat()
這些介面傳入的檔名搜尋dcache,快速找到檔名對應的dentry。dentry的結構是struct dentry
,這只是一個記憶體結構,不會持久化到磁碟中,僅僅是為了提升效能而已。
dentry cache是整個檔案空間的檢視,但是大部分情況下並不能同時將所有dentry都載入到記憶體。VFS會在執行lookup()
時,建立還不在記憶體中的dentry。
The Inode Object
inode代表真實儲存在檔案系統中的物件,比如檔案、目錄、FIFO等等。inode的結構是struct inode
。一個dentry只能指向一個inode,但一個inode可以被多個dentry指向。
對於塊裝置的檔案系統,inode儲存在磁碟上,並在需要的時候拷貝到記憶體中,修改inode後又會寫回磁碟進行持久化。對於偽檔案系統,inode儲存在記憶體中。
VFS呼叫lookup()
從指定路徑的第一級目錄的dentry開始查詢對應的inode。lookup()
的真實實現是由inode所在的底層檔案系統提供的。
The File Object
開啟一個檔案實際就是建立一個file物件。file的結構是struct file
,其有指向一個dentry的指標,和一組操作file的函式指標。這些資訊是從inode中獲取的。在開啟檔案的最後,file會被加入當前程式的檔案描述符表中。
使用者的讀、寫、關閉檔案都通過fd
進行,核心會可以根據fd
獲取到對應的file物件,這些操作最終都會呼叫到底層檔案系統提供的函式。
註冊和掛載檔案系統
註冊和登出檔案系統的函式如下
#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中看到。
struct file_system_type
該結構用於描述檔案系統,自核心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;
};
- name:檔案系統的名字,比如"ext2"等。
- fs_flags:比如FS_REQUIRES_DEV、FS_NO_DCACHE等一些標誌。
- mount:掛載檔案系統時呼叫。
- kill_sb:解除安裝檔案系統時呼叫。
- owner:在大部分情況下,應當初始化為THIS_MODULE。
- next:應當初始化為NULL。
- s_lock_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
:確定是否輸出錯誤資訊。
The Superblock Object
一個superblock代表了一個已掛載的檔案系統。
struct super_operations
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不會加鎖呼叫這些函式。這意味著這些函式阻塞時,不會影響到其他執行緒。所有的函式只會在程式上下文呼叫,不會在中斷上下文中呼叫。
- alloc_inode:用於分配和初始化inode。如果未指定該方法,則使用純粹的
struct inode
。通常情況下,檔案系統會自定義自己的inode結構,除了包含struct inode
外,還會包含和底層檔案系統相關的欄位。 - destroy_inode:銷燬inode,必須和alloc_inode一起實現。
- dirty_inode:將inode標記為髒inode
- write_inode:將inode寫回到磁碟。該函式第二個引數用於指定是否同步寫入,並非所有檔案系統都檢查這個標誌
- drop_inode:用於判斷是否從記憶體中移除inode。當inode引用計數為0時呼叫該函式,呼叫前會鎖定
inode->i_lock
。drop_inode應該是NULL(正常UNIX檔案系統語義)或者是generic_delete_inode()
(不考慮引用計數,強制清除inode,適用於不想快取inode的檔案系統)。 - delete_inode:從記憶體中移除inode。v2.6.39的
struct super_operations
就沒有這個欄位,取而代之的是evict_inode - put_super:釋放
superblock
,呼叫前鎖定superblock lock
。 - sync_fs:將superblock關聯的髒資料寫回到儲存。第二個引數用於指明是否等待資料寫完後再返回。
- freeze_fs:鎖定檔案系統並迫使它進入一致狀態。目前被邏輯卷管理(LVM)會用到該函式。
- unfreeze_fs:解鎖檔案系統,使其可以重新寫入。
- statfs:獲取檔案系統統計資訊。
- remount_fs:重新掛載檔案系統,主要用於更新掛載引數。呼叫前鎖定**
kernel lock
。 - clear_inode:標記不再使用該inode。v2.6.39的
struct super_operations
就沒有這個欄位 - umount_begin:解除安裝檔案系統
- show_options:用於在/proc/
/mounts裡輸出掛載選項 - quota_read:讀quota file
- quota_write:寫quota file
- nr_cached_objects:返回可釋放的物件個數。
- free_cache_objects:清理物件。需要和nr_cached_objects一起定義
如果這些函式內會執行批量任務,那麼必須包含支援重新排程的函式,這使得VFS無需擔心這些函式長時間處理的問題。
設定inode時必須初始化struct inode
的i_op
欄位,其指向struct inode_operations
,該結構包含了一系列操作inode
的函式。
struct xattr_handler
當檔案系統需要支援擴充套件屬性時,可以指定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);
};
- name:該結構中的函式用於處理該名字代表的屬性,比如"system.posix_acl_access"。指定name,則prefix必須是NULL
- prefix:該結構中的函式用於處理該字首代表的屬性,比如"user."。指定了prefix,則name必須是NULL
- list:確定是否應為特定的dentry列出與此處理函式匹配的屬性。
- get:獲取擴充套件屬性的值。該方法在
getxattr(2)
流程中呼叫 - set:設定擴充套件屬性的值。如果新值為NULL,則移除擴充套件屬性。該函式在
setxattr(2)
和removexattr(2)
流程中呼叫
當檔案系統沒有xattr的處理方法或者沒有匹配的屬性時,會返回-EOPNOTSUPP
。
The Inode Object
一個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);
};
除非有額外說明,否則所有方法都不會持鎖呼叫。
- create:建立inode,由
open(2)
和creat(2)
呼叫。只有當需要支援常規檔案時,才實現該函式。入參dentry必須沒有指向任何inode
的,最後需要呼叫d_instantiate()
將新建立的inode加入到這個dentry中。 - lookup:查詢inode。
- 需要查詢的inode的名字記錄在dentry中。如果找到了則需要呼叫
d_add()
將inode加入到dentry中,並且inode的引用計數要加1。如果inode不存在,則需要將NULL加入到dentry,代表dentry是無效的。
- 需要查詢的inode的名字記錄在dentry中。如果找到了則需要呼叫
- 當錯誤發生時,必須返回錯誤。如果只是將dentry指向NULL,VFS會執行
create(2)
,mknod(2)
,mkdir(2)
去建立inode
,但這些函式還是會因為lookup失敗的原因而再次失敗。
- 當錯誤發生時,必須返回錯誤。如果只是將dentry指向NULL,VFS會執行
- 如果需要重新設定
dentry
的操作函式,可以給d_dop
賦值。這些函式被呼叫時,會使用目錄inode的訊號量。
- 如果需要重新設定
- link:建立硬連結。和create一樣,該函式中需要呼叫
d_instantiate()
。 - unlink:支援刪除inode。
- symlink:建立符號連結。
- mkdir:建立目錄。該函式中也需要呼叫
d_instantiate()
。 - rmdir:刪除目錄。
- mknod:建立裝置inode、命名管道或socket。該函式中也需要呼叫
d_instantiate()
- rename:重新命名。當檔案系統不支援某些方法和flag時,必須返回
-EINVAL
。當前以下flag已經實現 - RENAME_NOREPLACE:如果新檔名已經存在,則應當返回
-EEXIST
。但是目前VFS已經檢查了新檔名是否存在。
- RENAME_NOREPLACE:如果新檔名已經存在,則應當返回
- RENAME_EXCHANGE:兩個檔案必須都存在,只是交換一下檔名。
- get_link:獲取符號連結所指向的inode。
- readlink:讀取符號連結所指向的inode的檔名。正常情況下,檔案系統只需要實現get_link。
- permission:檢查類POSIX檔案系統的訪問許可權。可能會在rcu-walk模式下呼叫。如果在rcu-walk模式下,檔案系統必須在不阻塞或者不儲存inode的情況下檢查許可權。如果在rcu-walk模式下,發生一個無法處理的情況,則返回
-ECHILD
,此後會在ref-walk模式下再嘗試一次。 - setattr:設定檔案屬性,由
chmod(2)
和相關的系統呼叫觸發。 - getattr:獲取檔案屬性,由
stat(2)
和相關的系統呼叫觸發。 - listxattr:由VFS呼叫列出一個檔案的所有擴充套件屬性,由
listxattr(2)
觸發。 - update_time:更新inode的時間(atime、ctime、mtime)或
i_version
。如果該方法未定義,那麼VFS會自行更新inode,然後呼叫mark_inode_dirty_sync()
標記該inode
為髒inode
。 - atomic_open:在open操作的最後呼叫。使用這個可選的方法,檔案系統可以原子性地查詢、建立、開啟一個檔案。如果該方法想讓呼叫者去開啟檔案,則應當通過
finish_no_open()
通知呼叫者。該方法只在最後一步是無效或者需要lookup時才呼叫。快取有效的dentry需要在f_op->open()
中完成。如果檔案建立成功了,則需要在file->f_mode
設定FMODE_CREATED
。如果指定了O_EXCL
,該方法需要在檔案存在時返回失敗。返回成功就設定FMODE_CREATED
。 - tmpfile:在
open(O_TMPFILE)
的最後被呼叫。這是一個可選的方法,等同於在一個指定的目錄下,原子性地建立、開啟、刪除一個檔案。
The Address Space Object
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
記錄的是正確的寫回錯誤。
struct address_space_operations
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 *);
};
- writepage:基於資料完整性(sync)或者釋放記憶體(flush)的原因,VM會呼叫該方法將髒頁寫回到儲存。寫回過程:清除
PG_Dirty
,設定PageLocked
為true
,然後writepage開始寫回,並設定PG_Writeback
,然後在完成寫回後解鎖page。 - 如果
wbc->sync_mode
是WB_SYNC_NONE
,那麼writepage在無法完成寫回指定頁的情況下,返回AOP_WRITEPAGE_ACTIVATE
,這樣VM就不會一直為該頁呼叫writepage了。
- 如果
- readpage:VM呼叫該方法從儲存中讀取一頁的資料。該頁會被鎖定,但需要在讀取完成後,標記為
uptodate
並解鎖。如果readpage
需要解鎖,也可以解鎖,並返回AOP_TRUNCATED_PAGE
。在這種情況下,VM會重新安置和加鎖該頁,再次呼叫readpage
。 - writepages:VM呼叫該方法將address space相關聯的頁都寫回到儲存。如果如果
wbc->sync_mode
是WBC_SYNC_ALL
,那麼writeback_control
就會指定一組必須寫回的頁。如果是WBC_SYNC_NONE
的話,則只要求儘可能寫入nr_to_write頁。如果該方法未定義,那麼會用mpage_writepages
來替代。該方法會從address space中獲取所有標記為DIRTY
的頁,然後傳遞給writepage - set_page_dirty:VM呼叫該方法設定髒頁。只有在address space中有私有資料的page且在頁變髒時需要更新這些私有資料時,才需要該方法。如果定義了該方法,那麼它必須設定
PageDirty
標誌和基數樹中的PAGECACHE_TAG_DIRTY
標誌。 - readpages:VM呼叫該方法讀取address space相關聯的頁。這個方法本質上是多次呼叫readpage。readpages只用於預讀,因此讀取錯誤被忽略了。
- write_begin:由
generic_perform_write()
呼叫,用於告知檔案系統準備在指定偏移處寫len位元組的資料。address space應預留一些資源保證完成這次寫入操作。如果寫入要更新頁中的一部分,那麼需要將整頁塊讀取到記憶體中,即讀改寫。檔案系統通過pagep返回page cache中已鎖定的page。呼叫者會將資料寫入到該page中。需要支援實際寫入的資料長度小於傳給write_begin
的長度的場景。可通過fsdata儲存需要傳遞給write_end的資料。 - write_end:該函式必須在成功呼叫write_begin和拷貝資料後被呼叫。len是傳遞給write_begin的len,copied則是實際拷貝的長度。檔案系統必須釋放page的鎖和引用,並更新
struct inode::i_size
。失敗返回小於0,否則返回實際拷貝到page cache的長度。 - bmap:VFS呼叫該方法將邏輯塊偏移對映為物理塊序號。該方法用於
ioctl(FIBMAP)
和swap檔案。為了swap一個檔案,檔案必須固定地對映到一個塊裝置上。swap系統會通過bmap來找到檔案所在的塊,然後直接儲存,而不通過檔案系統。 - invalidatepage:如果page設定了
PagePrivate
,那麼當部分或全部的page從address space中被移除時,就會呼叫invalidatepage。 - releasepage:釋放標記為
PagePrivate
的page,該方法需要移除page的私有資料,並清除PagePrivate
。如果該方法失敗了則返回0。releasepage用於兩種場景 - VM發現一個沒有活動使用者的乾淨page,並向釋放該page。如果釋放成功,那麼該頁就會從address space中移除,變為自由的page。
- 需要讓address space中的部分或全部page失效。這種場景由
fadvise(POSIX_FADV_DONTNEED)
觸發,或者檔案系統明確請求(比如當nfs和9fs認為cache的資料已經與儲存不一致時,會呼叫invalidate_inode_pages2()
)
- 需要讓address space中的部分或全部page失效。這種場景由
- freepage:一旦page在page cache中不可見時,為了允許清除私有資料,就會呼叫該方法。因為該方法可能被記憶體回收者呼叫,所以該方法不能假設原始的address space還存在,也不應當阻塞。
- direct_IO:由
generic read/write
類函式呼叫來執行direct IO。 - isolate_page:VM在需要隔離一個可移動的非LRU的page時呼叫。如果成功了,VM會通過
__SetPageIsolated
將該頁標記為PG_isolated
。 - migrate_page:該方法用於壓縮實體記憶體使用量。如果VM想重新放置page(可能是記憶體卡的故障訊號觸發的),就會傳遞一個老page和一個新page給這個方法。該方法需要傳輸私有資料,並更新所有引用。
- putback_page:當已隔離的頁遷移失敗時,VM會呼叫該方法。
- launder_page:在釋放頁前呼叫。該方法將髒頁寫回儲存並避免重新弄髒該頁,在整個過程中,都會加鎖。
- is_partially_uptodate:塊大小不等於頁大小,一頁可能包含多個塊。如果VM讀取到所需的塊資料,那麼就無需等待整個頁讀取完畢。
- is_dirty_writeback:當VM想回收page時呼叫。VM會根據dirty和writeback的值決定是否需要停頓回收頁,以便能完成某些IO。通常情況下,VM可以使用
PageDirty
和PageWriteback
,但是某些檔案系統會有更復雜的狀態(比如NFS的unstable pages需要避免被回收),或者因為鎖的問題沒有設定這些標誌。該方法可以向VM表明該頁是髒頁或正在寫回的頁,讓VM停止回收該頁。 - error_remove_page:如果
truncation
是正常的話,通常設定為generic_error_remove_page()
。主要用於處理記憶體失敗。實現該方法,意味著你會處理那些頁,除非你已經鎖定或者增加了引用計數。 - swap_activate:當檔案使用了swapon時呼叫,用於分配空間並將塊的查詢資訊儲存在記憶體中。返回0代表成功,也意味著該檔案可以被當做備份的交換空間。
- swap_deactivate:當swap_activate成功後,呼叫該方法使得該檔案變為swapoff。
The File Object
一個file物件代表程式開啟的一個檔案。
struct file_operations
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);
};
除非額外說明,否則這些函式都不會持鎖呼叫。
- read_iter:支援將檔案資料讀取到非連續的記憶體中
- write_iter:支援將非連續記憶體中的資料寫入到檔案中。
- iterate:讀取目錄內容
- iterate_shared:當檔案系統支援併發的目錄迭代時,使用該函式讀取目錄內容
- compat_ioctl:在64位核心上相容32位的系統呼叫
- open:建立一個新的
struct file
,並初始化strutc file::private_data
- flush:由
close(2)
呼叫 - release:當檔案的引用計數為0時呼叫
- fasync:檔案為非阻塞模式時,由
fcntl(2)
呼叫 - lock:由
fcntl(2)
呼叫,執行F_GETLK
,F_SETLK
,F_SETLKW
命令 - fallocate:預分配block
Directory Entry Cache (dcache)
dentry屬於VFS和單個檔案系統,與裝置驅動無關。每個dentry都有一個指向其父dentry的指標,以及一個子dentry的hash連結串列。
struct dentry_operations
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 *);
};
- d_revalidate:當在cache中找到dentry時,判斷dentry是否有效,返回正數代表還有效,返回0或負數代表無效。大多數本地檔案系統將其設為NULL,因為它們的dentry總是有效的。網路檔案系統則不同,因為服務端的變更,客戶端可能感知不到。
- d_weak_revalidate
- d_hash:計算hash值,根據hash值加入父dentry的hash表中
- d_compare:比較dentry的名字,必須是常熟且冪等
- d_delete:判斷是否刪除dentry。返回1表示立即刪除,返回0代表快取。d_delete必須是常熟且冪等
- d_init:當分配dentry後呼叫,初始化dentry
- d_release:釋放dentry
- d_iput:歸還inode引用(在d_release前呼叫)。如果是NULL,VFS會呼叫
iput()
,否則需要自行呼叫iput()
。 - d_dname:當需要生成dentry的路徑名時呼叫。對於偽檔案系統(sockfs,pipefs)延遲生成路徑名來說很有用。因為沒有加鎖,所以d_dname不能修改dentry本身
Directory Entry Cache API
以下是操作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,如果找到了則增加引用計數後返回。