Linux 虛擬檔案系統概述

原始作者:Richard Gooch <rgooch@atnf.csiro.au>

  • 版權所有 (C) 1999 Richard Gooch

  • 版權所有 (C) 2005 Pekka Enberg

簡介

虛擬檔案系統(也稱為虛擬檔案系統開關)是核心中的軟體層,它為使用者空間程式提供檔案系統介面。它還在核心中提供了一種抽象,允許不同的檔案系統實現共存。

VFS 系統呼叫 open(2)、stat(2)、read(2)、write(2)、chmod(2) 等從程序上下文中呼叫。檔案系統鎖定在文件 鎖定 中描述。

目錄項快取 (dcache)

VFS 實現 open(2)、stat(2)、chmod(2) 和類似的系統呼叫。傳遞給它們的路徑名引數被 VFS 用於搜尋目錄項快取(也稱為 dentry 快取或 dcache)。這提供了一種非常快速的查詢機制,可以將路徑名(檔名)轉換為特定的 dentry。Dentries 存在於 RAM 中,永遠不會儲存到磁碟:它們僅為了效能而存在。

dentry 快取旨在檢視您的整個檔案空間。由於大多數計算機無法同時將所有 dentries 放入 RAM 中,因此快取中缺少一些位。為了將您的路徑名解析為 dentry,VFS 可能不得不沿途建立 dentries,然後載入 inode。這是透過查詢 inode 來完成的。

Inode 物件

單個 dentry 通常有一個指向 inode 的指標。Inode 是檔案系統物件,例如常規檔案、目錄、FIFO 和其他型別。它們要麼存在於磁碟上(對於塊裝置檔案系統),要麼存在於記憶體中(對於偽檔案系統)。磁碟上的 Inodes 在需要時被複制到記憶體中,並且對 inode 的更改被寫回磁碟。單個 inode 可以被多個 dentries 指向(例如,硬連結就是這樣做的)。

要查詢 inode,VFS 需要呼叫父目錄 inode 的 lookup() 方法。此方法由 inode 所在的特定檔案系統實現安裝。一旦 VFS 擁有所需的 dentry(以及 inode),我們就可以進行所有那些無聊的事情,例如 open(2) 檔案或 stat(2) 檔案以檢視 inode 資料。stat(2) 操作相當簡單:一旦 VFS 擁有 dentry,它就會檢視 inode 資料並將其中一些資料傳遞迴使用者空間。

File 物件

開啟檔案需要另一個操作:分配檔案結構(這是檔案描述符的核心端實現)。新分配的檔案結構用指向 dentry 的指標和一組檔案操作成員函式初始化。這些是從 inode 資料中獲取的。然後呼叫 open() 檔案方法,以便特定的檔案系統實現可以完成其工作。您可以看到這是 VFS 執行的另一個切換。檔案結構被放置到程序的檔案描述符表中。

讀取、寫入和關閉檔案(以及其他各種 VFS 操作)是透過使用使用者空間檔案描述符來獲取適當的檔案結構,然後呼叫所需的檔案結構方法來完成任何需要完成的操作。只要檔案是開啟的,它就會保持 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 將呼叫特定檔案系統的相應 mount() 方法。新的 vfsmount 引用由 ->mount() 返回的樹將被附加到掛載點,以便當路徑名解析到達掛載點時,它將跳轉到該 vfsmount 的根目錄。

您可以在檔案 /proc/filesystems 中檢視已註冊到核心的所有檔案系統。

struct file_system_type

這描述了檔案系統。定義了以下成員

struct file_system_type {
        const char *name;
        int fs_flags;
        int (*init_fs_context)(struct fs_context *);
        const struct fs_parameter_spec *parameters;
        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 hlist_head fs_supers;

        struct lock_class_key s_lock_key;
        struct lock_class_key s_umount_key;
        struct lock_class_key s_vfs_rename_key;
        struct lock_class_key s_writers_key[SB_FREEZE_LEVELS];

        struct lock_class_key i_lock_key;
        struct lock_class_key i_mutex_key;
        struct lock_class_key invalidate_lock_key;
        struct lock_class_key i_mutex_dir_key;
};
name

檔案系統型別的名稱,例如“ext2”、“iso9660”、“msdos”等

fs_flags

各種標誌(即 FS_REQUIRES_DEV、FS_NO_DCACHE 等)

init_fs_context

使用檔案系統特定的資料初始化 ‘struct fs_context’ ->ops 和 ->fs_private 欄位。

parameters

指向檔案系統引數描述符陣列 ‘struct fs_parameter_spec’ 的指標。更多資訊請參見 檔案系統掛載 API

mount

當應該掛載此檔案系統的新例項時要呼叫的方法

kill_sb

當應該關閉此檔案系統的例項時要呼叫的方法

owner

供 VFS 內部使用:在大多數情況下,您應該將其初始化為 THIS_MODULE。

next

供 VFS 內部使用:您應該將其初始化為 NULL

fs_supers

供 VFS 內部使用:檔案系統例項(超級塊)的 hlist

s_lock_key, s_umount_key, s_vfs_rename_key, s_writers_key, i_lock_key, i_mutex_key, invalidate_lock_key, i_mutex_dir_key: lockdep 特定

mount() 方法具有以下引數

struct file_system_type *fs_type

描述檔案系統,部分由特定檔案系統程式碼初始化

int flags

掛載標誌

const char *dev_name

我們要掛載的裝置名稱。

void *data

任意掛載選項,通常以 ASCII 字串的形式出現(參見“掛載選項”部分)

mount() 方法必須返回呼叫者請求的樹的根 dentry。必須獲取對其超級塊的活動引用,並且必須鎖定超級塊。失敗時應返回 ERR_PTR(error)。

引數與 mount(2) 的引數匹配,它們的解釋取決於檔案系統型別。例如,對於塊檔案系統,dev_name 被解釋為塊裝置名稱,開啟該裝置,如果它包含合適的檔案系統映像,則該方法相應地建立和初始化 struct super_block,並將它的根 dentry 返回給呼叫者。

->mount() 可以選擇返回現有檔案系統的子樹 - 它不必建立新的檔案系統。從呼叫者的角度來看,主要結果是對要附加的(子)樹的根目錄中的 dentry 的引用;建立新的超級塊是一個常見的副作用。

mount() 方法填充的超級塊結構中最有趣的成員是“s_op”欄位。這是指向 “struct super_operations” 的指標,它描述了檔案系統實現的下一級。

通常,檔案系統使用其中一個通用 mount() 實現,並提供 fill_super() 回撥函式作為替代。通用變體是

mount_bdev

掛載駐留在塊裝置上的檔案系統

mount_nodev

掛載不受裝置支援的檔案系統

mount_single

掛載在所有掛載之間共享例項的檔案系統

fill_super() 回撥實現具有以下引數

struct super_block *sb

超級塊結構。回撥必須正確初始化此結構。

void *data

任意掛載選項,通常以 ASCII 字串的形式出現(參見“掛載選項”部分)

int silent

是否對錯誤保持靜默

Superblock 物件

超級塊物件表示已掛載的檔案系統。

struct super_operations

這描述了 VFS 如何操作檔案系統的超級塊。定義了以下成員

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

        void (*dirty_inode) (struct inode *, int flags);
        int (*write_inode) (struct inode *, struct writeback_control *wbc);
        int (*drop_inode) (struct inode *);
        void (*evict_inode) (struct inode *);
        void (*put_super) (struct super_block *);
        int (*sync_fs)(struct super_block *sb, int wait);
        int (*freeze_super) (struct super_block *sb,
                                enum freeze_holder who);
        int (*freeze_fs) (struct super_block *);
        int (*thaw_super) (struct super_block *sb,
                                enum freeze_wholder who);
        int (*unfreeze_fs) (struct super_block *);
        int (*statfs) (struct dentry *, struct kstatfs *);
        int (*remount_fs) (struct super_block *, int *, char *);
        void (*umount_begin) (struct super_block *);

        int (*show_options)(struct seq_file *, struct dentry *);
        int (*show_devname)(struct seq_file *, struct dentry *);
        int (*show_path)(struct seq_file *, struct dentry *);
        int (*show_stats)(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);
        struct dquot **(*get_dquots)(struct inode *);

        long (*nr_cached_objects)(struct super_block *,
                                struct shrink_control *);
        long (*free_cached_objects)(struct super_block *,
                                struct shrink_control *);
};

除非另有說明,否則所有方法都在不持有任何鎖的情況下呼叫。這意味著大多數方法都可以安全地阻塞。所有方法都僅從程序上下文中呼叫(即,不是從中斷處理程式或下半部分呼叫)。

alloc_inode

alloc_inode() 呼叫此方法來為 struct inode 分配記憶體並初始化它。如果未定義此函式,則分配一個簡單的 “struct inode”。通常,alloc_inode 將用於分配一個更大的結構,其中包含嵌入其中的 “struct inode”。

destroy_inode

destroy_inode() 呼叫此方法來釋放為 struct inode 分配的資源。只有在定義了 ->alloc_inode 時才需要,並且只是撤消 ->alloc_inode 所做的任何事情。

free_inode

此方法從 RCU 回撥中呼叫。如果您在 ->destroy_inode 中使用 call_rcu() 釋放 “struct inode” 記憶體,那麼最好在此方法中釋放記憶體。

dirty_inode

當 inode 被標記為髒時,VFS 會呼叫此方法。這專門針對 inode 本身被標記為髒,而不是其資料。如果更新需要由 fdatasync() 持久化,則 I_DIRTY_DATASYNC 將在 flags 引數中設定。如果啟用了 lazytime 並且 struct inode 自上次 ->dirty_inode 呼叫以來已更新時間,則 I_DIRTY_TIME 將在 flags 中設定。

write_inode

當 VFS 需要將 inode 寫入磁碟時,將呼叫此方法。第二個引數指示寫入是否應該是同步的,並非所有檔案系統都檢查此標誌。

drop_inode

當對 inode 的最後一次訪問被刪除時呼叫,持有 inode->i_lock 自旋鎖。

此方法應為 NULL(正常的 UNIX 檔案系統語義)或“generic_delete_inode”(對於不想快取 inodes 的檔案系統 - 導致始終呼叫“delete_inode”,而不管 i_nlink 的值如何)

“generic_delete_inode()” 行為等效於在 put_inode() case 中使用 “force_delete” 的舊方法,但不具有 “force_delete()” 方法存在的競爭條件。

evict_inode

當 VFS 想要逐出 inode 時呼叫。呼叫者不會逐出 pagecache 或與 inode 關聯的元資料緩衝區;該方法必須使用 truncate_inode_pages_final() 來擺脫這些。呼叫者確保在呼叫 ->evict_inode() 時(或之後),非同步寫回不會為 inode 執行。可選。

put_super

當 VFS 希望釋放超級塊時(即解除安裝)呼叫。在持有超級塊鎖的情況下呼叫此函式

sync_fs

當 VFS 正在寫出與超級塊關聯的所有髒資料時呼叫。第二個引數指示該方法是否應等待寫出完成。可選。

freeze_super

如果提供,則呼叫此函式來代替 ->freeze_fs 回撥。主要區別在於,在沒有獲取 down_write(&sb->s_umount) 的情況下呼叫 ->freeze_super。如果檔案系統實現了它並且也希望呼叫 ->freeze_fs,那麼它必須從此回撥顯式呼叫 ->freeze_fs。可選。

freeze_fs

當 VFS 鎖定檔案系統並強制其進入一致狀態時呼叫。此方法當前由邏輯卷管理器 (LVM) 和 ioctl(FIFREEZE) 使用。可選。

thaw_super

在 ->freeze_super 之後,當 VFS 解鎖檔案系統並使其再次可寫時呼叫。可選。

unfreeze_fs

在 ->freeze_fs 之後,當 VFS 解鎖檔案系統並使其再次可寫時呼叫。可選。

statfs

當 VFS 需要獲取檔案系統統計資訊時呼叫。

remount_fs

當重新掛載檔案系統時呼叫。在持有核心鎖的情況下呼叫此函式

umount_begin

當 VFS 正在解除安裝檔案系統時呼叫。

show_options

由 VFS 呼叫以顯示 /proc/<pid>/mounts 和 /proc/<pid>/mountinfo 的掛載選項。(參見“掛載選項”部分)

show_devname

可選。由 VFS 呼叫以顯示 /proc/<pid>/{mounts,mountinfo,mountstats} 的裝置名稱。如果未提供,則將使用 '(struct mount).mnt_devname'。

show_path

可選。由 VFS(對於 /proc/<pid>/mountinfo)呼叫以顯示相對於檔案系統根目錄的掛載根目錄 dentry 路徑。

show_stats

可選。由 VFS(對於 /proc/<pid>/mountstats)呼叫以顯示檔案系統特定的掛載統計資訊。

quota_read

由 VFS 呼叫以從檔案系統配額檔案讀取。

quota_write

由 VFS 呼叫以寫入檔案系統配額檔案。

get_dquots

由配額呼叫以獲取特定 inode 的 “struct dquot” 陣列。可選。

nr_cached_objects

由檔案系統使用的 sb 快取縮小函式呼叫,以返回它包含的可釋放快取物件的數量。可選。

free_cache_objects

由檔案系統的 sb 快取縮小函式呼叫,以掃描指示的物件數量以嘗試釋放它們。可選,但是實現此方法的任何檔案系統也需要實現 ->nr_cached_objects,才能正確呼叫它。

我們無法對檔案系統可能遇到的任何錯誤執行任何操作,因此 void 返回型別。如果 VM 嘗試在 GFP_NOFS 條件下回收,則永遠不會呼叫此函式,因此此方法不需要自行處理該情況。

實現必須在完成的任何掃描迴圈中包含有條件地重新排程呼叫。這允許 VFS 確定適當的掃描批處理大小,而無需擔心由於較大的掃描批處理大小而導致實現會產生保持問題。

設定 inode 的人負責填充 “i_op” 欄位。這是指向 “struct inode_operations” 的指標,它描述了可以對單個 inode 執行的方法。

struct xattr_handler

在支援擴充套件屬性 (xattrs) 的檔案系統上,s_xattr 超級塊欄位指向一個以 NULL 結尾的 xattr 處理程式陣列。擴充套件屬性是 name:value 對。

name

表示處理程式匹配具有指定名稱的屬性(例如 “system.posix_acl_access”);prefix 欄位必須為 NULL。

prefix

表示處理程式匹配具有指定名稱字首的所有屬性(例如“user.”);name 欄位必須為 NULL。

list

確定是否應為特定 dentry 列出與此 xattr 處理程式匹配的屬性。由某些 listxattr 實現(如 generic_listxattr)使用。

get

由 VFS 呼叫以獲取特定擴充套件屬性的值。此方法由 getxattr(2) 系統呼叫呼叫。

set

由 VFS 呼叫以設定特定擴充套件屬性的值。當新值為 NULL 時,呼叫此函式以刪除特定的擴充套件屬性。此方法由 setxattr(2) 和 removexattr(2) 系統呼叫呼叫。

當檔案系統的 xattr 處理程式都不匹配指定的屬性名稱時,或者當檔案系統不支援擴充套件屬性時,各種 *xattr(2) 系統呼叫返回 -EOPNOTSUPP。

Inode 物件

Inode 物件表示檔案系統中的物件。

struct inode_operations

這描述了 VFS 如何在您的檔案系統中操作 inode。從核心 2.6.22 開始,定義了以下成員

struct inode_operations {
        int (*create) (struct mnt_idmap *, 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 mnt_idmap *, struct inode *,struct dentry *,const char *);
        struct dentry *(*mkdir) (struct mnt_idmap *, struct inode *,struct dentry *,umode_t);
        int (*rmdir) (struct inode *,struct dentry *);
        int (*mknod) (struct mnt_idmap *, struct inode *,struct dentry *,umode_t,dev_t);
        int (*rename) (struct mnt_idmap *, 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 mnt_idmap *, struct inode *, int);
        struct posix_acl * (*get_inode_acl)(struct inode *, int, bool);
        int (*setattr) (struct mnt_idmap *, struct dentry *, struct iattr *);
        int (*getattr) (struct mnt_idmap *, 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 mnt_idmap *, struct inode *, struct file *, umode_t);
        struct posix_acl * (*get_acl)(struct mnt_idmap *, struct dentry *, int);
        int (*set_acl)(struct mnt_idmap *, struct dentry *, struct posix_acl *, int);
        int (*fileattr_set)(struct mnt_idmap *idmap,
                            struct dentry *dentry, struct fileattr *fa);
        int (*fileattr_get)(struct dentry *dentry, struct fileattr *fa);
        struct offset_ctx *(*get_offset_ctx)(struct inode *inode);
};

同樣,除非另有說明,否則所有方法都在不持有任何鎖的情況下呼叫。

create

由 open(2) 和 creat(2) 系統呼叫呼叫。僅當您要支援常規檔案時才需要。您獲得的 dentry 不應具有 inode(即,它應該是負 dentry)。在這裡,您可能會使用 dentry 和新建立的 inode 呼叫 d_instantiate()

lookup

當 VFS 需要在父目錄中查詢 inode 時呼叫。要查詢的名稱在 dentry 中找到。此方法必須呼叫 d_add() 以將找到的 inode 插入到 dentry 中。應遞增 inode 結構中的 “i_count” 欄位。如果指定的 inode 不存在,則應將 NULL inode 插入到 dentry 中(這稱為負 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_new()。

如果未使用 d_instantiate_new() 並且提供了 fh_to_dentry() 匯出操作,或者儲存可能可以透過另一條路徑訪問(例如使用網路檔案系統),則可能需要更多注意。重要的是,如果 inode 不再是 I_NEW,並且 inode 可能已附加到 dentry,則不應使用 d_instantate()。這是因為 VFS 中有一個硬性規定,即目錄只能有一個 dentry。

例如,如果 NFS 檔案系統掛載兩次,則在第一次 mkdir 返回之前,新目錄可能在另一個掛載上可見,並且一對 name_to_handle_at()、open_by_handle_at() 呼叫可能會使用 IS_ROOT() dentry 例項化目錄 inode。

如果發生這種情況的可能性很大,則新的 inode 應使用 d_splice_alias() 進行 d_drop() ed 和附加。返回的 dentry(如果有)應由 ->mkdir() 返回。

rmdir

由 rmdir(2) 系統呼叫呼叫。僅當您要支援刪除子目錄時才需要

mknod

由 mknod(2) 系統呼叫呼叫以建立裝置(字元、塊)inode 或命名管道 (FIFO) 或套接字。僅當您要支援建立這些型別的 inode 時才需要。您可能需要像在 create() 方法中一樣呼叫 d_instantiate()

rename

由 rename(2) 系統呼叫呼叫,以將物件重新命名為具有第二個 inode 和 dentry 給出的父項和名稱。

對於任何不支援或未知的標誌,檔案系統必須返回 -EINVAL。當前實現了以下標誌:(1) RENAME_NOREPLACE:此標誌指示如果重新命名的目標存在,則重新命名應以 -EEXIST 失敗,而不是替換目標。VFS 已經檢查了是否存在,因此對於本地檔案系統,RENAME_NOREPLACE 實現等效於普通重新命名。(2) RENAME_EXCHANGE:交換源和目標。兩者都必須存在;這由 VFS 檢查。與普通重新命名不同,源和目標可能具有不同的型別。

get_link

由 VFS 呼叫以跟蹤指向它的符號連結到 inode。僅當您要支援符號連結時才需要。此方法返回要遍歷的符號連結正文(並可能使用 nd_jump_link() 重置當前位置)。如果正文在 inode 消失之前不會消失,則不需要任何其他操作;如果需要以其他方式固定它,則透過讓 get_link(..., ..., done) 執行 set_delayed_call(done, destructor, argument) 來安排其釋放。在這種情況下,一旦 VFS 完成您返回的正文,就會呼叫 destructor(argument)。可以在 RCU 模式下呼叫;這由 NULL dentry 引數指示。如果請求在不離開 RCU 模式的情況下無法處理,則使其返回 ERR_PTR(-ECHILD)。

如果檔案系統將符號連結目標儲存在 ->i_link 中,則 VFS 可以直接使用它,而無需呼叫 ->get_link();但是,仍必須提供 ->get_link()。在 RCU 寬限期之後,必須不釋放 ->i_link。在 iget() 後寫入 ->i_link 需要 “release” 記憶體屏障。

readlink

這現在只是 readlink(2) 的覆蓋,用於 ->get_link 使用 nd_jump_link() 或物件實際上不是符號連結的情況。通常,檔案系統應該只為符號連結實現 ->get_link,readlink(2) 將自動使用它。

permission

由 VFS 呼叫以檢查類 POSIX 檔案系統上的訪問許可權。

可以在 rcu-walk 模式下呼叫 (mask & MAY_NOT_BLOCK)。如果在 rcu-walk 模式下,檔案系統必須在不阻塞或儲存到 inode 的情況下檢查許可權。

如果遇到 rcu-walk 無法處理的情況,則返回 -ECHILD,它將在 ref-walk 模式下再次呼叫。

setattr

由 VFS 呼叫以設定檔案的屬性。此方法由 chmod(2) 和相關的系統呼叫呼叫。

getattr

由 VFS 呼叫以獲取檔案的屬性。此方法由 stat(2) 和相關的系統呼叫呼叫。

listxattr

由 VFS 呼叫以列出給定檔案的所有擴充套件屬性。此方法由 listxattr(2) 系統呼叫呼叫。

update_time

由 VFS 呼叫以更新特定時間或 inode 的 i_version。如果未定義此函式,VFS 將自行更新 inode 並呼叫 mark_inode_dirty_sync。

atomic_open

在開啟的最後一個元件上呼叫。使用此可選方法,檔案系統可以在一個原子操作中查詢、可能建立和開啟檔案。如果它想將實際開啟操作留給呼叫者(例如,如果該檔案變成符號連結、裝置或檔案系統無法進行原子開啟操作),則可以透過返回 finish_no_open(file, dentry) 來發出訊號。僅當最後一個元件為負數或需要查詢時才呼叫此方法。快取的肯定 dentries 仍由 f_op->open() 處理。如果建立了檔案,則應在 file->f_mode 中設定 FMODE_CREATED 標誌。在 O_EXCL 的情況下,僅當檔案不存在時該方法才能成功,因此成功時應始終設定 FMODE_CREATED。

tmpfile

在 O_TMPFILE open() 的末尾呼叫。可選,等效於原子地建立、開啟和取消連結給定目錄中的檔案。成功後需要返回檔案已經開啟;這可以透過在最後呼叫 finish_open_simple() 來完成。

fileattr_get

在 ioctl(FS_IOC_GETFLAGS) 和 ioctl(FS_IOC_FSGETXATTR) 上呼叫以檢索其他檔案標誌和屬性。也在相關 SET 操作之前呼叫,以檢查正在更改的內容(在這種情況下,i_rwsem 鎖定為獨佔)。如果未設定,則回退到 f_op->ioctl()。

fileattr_set

在 ioctl(FS_IOC_SETFLAGS) 和 ioctl(FS_IOC_FSSETXATTR) 上呼叫以更改其他檔案標誌和屬性。呼叫者持有 i_rwsem 獨佔鎖。如果未設定,則回退到 f_op->ioctl()。

get_offset_ctx

呼叫此函式以獲取目錄 inode 的偏移量上下文。檔案系統必須定義此操作才能使用 simple_offset_dir_operations。

地址空間物件

地址空間物件用於對頁面快取中的頁面進行分組和管理。它可用於跟蹤檔案(或任何其他內容)中的頁面,並跟蹤檔案各個部分到程序地址空間的對映。

地址空間可以提供許多不同的但相關的服務。這些服務包括傳遞記憶體壓力、按地址查詢頁面以及跟蹤標記為 Dirty 或 Writeback 的頁面。

第一個可以獨立於其他服務使用。VM 可以嘗試釋放乾淨的頁面以重新使用它們。為此,它可以呼叫具有設定了私有標誌的乾淨 folios 的 ->release_folio。沒有 PagePrivate 且沒有外部引用的乾淨頁面將在不通知地址空間的情況下釋放。

為了實現此功能,需要將頁面放置在帶有 lru_cache_add 的 LRU 上,並且每當使用頁面時都需要呼叫 mark_page_active。

頁面通常按 ->index 儲存在基數樹索引中。此樹維護有關每個頁面的 PG_Dirty 和 PG_Writeback 狀態的資訊,以便可以快速找到具有這些標誌的頁面。

Dirty 標記主要由 mpage_writepages 使用 - 預設的 ->writepages 方法。它使用該標記來查詢要寫回的髒頁面。如果不使用 mpage_writepages(即,地址提供自己的 ->writepages),則 PAGECACHE_TAG_DIRTY 標記幾乎未使用。write_inode_now 和 sync_inode 確實使用它(透過 __sync_single_inode)來檢查 ->writepages 是否已成功寫出整個 address_space。

Writeback 標記由 filemap*wait* 和 sync_page* 函式透過 filemap_fdatawait_range 使用,以等待所有寫回完成。

地址空間處理程式可以將額外資訊附加到頁面,通常使用 “struct page” 中的 “private” 欄位。如果附加了此類資訊,則應設定 PG_Private 標誌。這將導致各種 VM 例程對地址空間處理程式進行額外呼叫以處理該資料。

地址空間充當儲存和應用程式之間的中介。資料一次一個整頁地讀入地址空間,並透過複製頁面或透過記憶體對映頁面提供給應用程式。資料由應用程式寫入地址空間,然後通常以整頁的形式寫回儲存,但是地址空間可以更精細地控制寫入大小。

讀取過程本質上只需要 “read_folio”。寫入過程更加複雜,並使用 write_begin/write_end 或 dirty_folio 將資料寫入地址空間,並使用 writepages 將資料寫回儲存。

向地址空間新增和從中刪除頁面的操作受 inode 的 i_mutex 保護。

當資料寫入頁面時,應設定 PG_Dirty 標誌。它通常保持設定狀態,直到 writepages 請求寫入它。這應該清除 PG_Dirty 並設定 PG_Writeback。它可以在清除 PG_Dirty 之後的任何時間實際寫入。一旦知道安全,PG_Writeback 就會被清除。

Writeback 使用 writeback_control 結構來指導操作。這為 writepages 操作提供了一些有關寫回請求的性質和原因的資訊,以及正在執行寫回的約束。它還用於將有關 writepages 請求結果的資訊返回給呼叫者。

處理寫回期間的錯誤

大多數執行緩衝 I/O 的應用程式將定期呼叫檔案同步呼叫(fsync、fdatasync、msync 或 sync_file_range),以確保寫入的資料已寫入後備儲存。當寫回期間發生錯誤時,他們希望在發出檔案同步請求時報告該錯誤。在一個請求上報告錯誤之後,除非自上次檔案同步以來發生了進一步的寫回錯誤,否則同一檔案描述符上的後續請求應返回 0。

理想情況下,核心只會報告在寫入到後未能成功寫回的檔案描述上的錯誤。但是,通用頁面快取基礎結構不會跟蹤已弄髒每個頁面的檔案描述,因此無法確定哪些檔案描述應返回錯誤。

相反,核心中的通用寫回錯誤跟蹤基礎結構會選擇向在發生錯誤時開啟的所有檔案描述的 fsync 報告錯誤。在具有多個寫入器的情況下,所有寫入器都將在後續的 fsync 上返回錯誤,即使透過該特定檔案描述完成的所有寫入都成功了(甚至在該檔案描述上根本沒有寫入)。

希望使用此基礎結構的檔案系統應呼叫 mapping_set_error 以在發生錯誤時在 address_space 中記錄該錯誤。然後,在檔案 ->fsync 操作中從頁面快取寫回資料之後,他們應呼叫 file_check_and_advance_wb_err 以確保 struct file 的錯誤游標已前進到後備裝置發出的錯誤流中的正確位置。

struct address_space_operations

這描述了 VFS 如何操作您的檔案系統中檔案到頁面快取的對映。定義了以下成員

struct address_space_operations {
        int (*read_folio)(struct file *, struct folio *);
        int (*writepages)(struct address_space *, struct writeback_control *);
        bool (*dirty_folio)(struct address_space *, struct folio *);
        void (*readahead)(struct readahead_control *);
        int (*write_begin)(struct file *, struct address_space *mapping,
                           loff_t pos, unsigned len,
                        struct page **pagep, void **fsdata);
        int (*write_end)(struct file *, struct address_space *mapping,
                         loff_t pos, unsigned len, unsigned copied,
                         struct folio *folio, void *fsdata);
        sector_t (*bmap)(struct address_space *, sector_t);
        void (*invalidate_folio) (struct folio *, size_t start, size_t len);
        bool (*release_folio)(struct folio *, gfp_t);
        void (*free_folio)(struct folio *);
        ssize_t (*direct_IO)(struct kiocb *, struct iov_iter *iter);
        int (*migrate_folio)(struct mapping *, struct folio *dst,
                        struct folio *src, enum migrate_mode);
        int (*launder_folio) (struct folio *);

        bool (*is_partially_uptodate) (struct folio *, size_t from,
                                       size_t count);
        void (*is_dirty_writeback)(struct folio *, bool *, bool *);
        int (*error_remove_folio)(struct mapping *mapping, struct folio *);
        int (*swap_activate)(struct swap_info_struct *sis, struct file *f, sector_t *span)
        int (*swap_deactivate)(struct file *);
        int (*swap_rw)(struct kiocb *iocb, struct iov_iter *iter);
};
read_folio

由頁面快取呼叫以從後備儲存讀取 folio。“file” 引數為網路檔案系統提供身份驗證資訊,通常不被基於塊的檔案系統使用。如果呼叫者沒有開啟的檔案(例如,如果核心正在執行自己的讀取而不是代表具有開啟檔案的使用者空間程序執行讀取),則它可能為 NULL。

如果對映不支援大型 folios,則 folio 將包含單個頁面。呼叫 read_folio 時將鎖定 folio。如果讀取成功完成,則應將 folio 標記為 uptodate。檔案系統應在讀取完成後解鎖 folio,無論它是成功還是失敗。檔案系統不需要修改 folio 上的引用計數;頁面快取保留引用計數,並且在解鎖 folio 之前不會釋放該引用計數。

檔案系統可以同步實現 ->read_folio()。在正常操作中,透過 ->readahead() 方法讀取 folios。僅當此失敗,或者呼叫者需要等待讀取完成時,頁面快取才會呼叫 ->read_folio()。檔案系統不應嘗試在 ->read_folio() 操作中執行自己的預讀。

如果檔案系統此時無法執行讀取操作,它可以解鎖 folio,執行任何必要的動作以確保將來讀取成功,並返回 AOP_TRUNCATED_PAGE。在這種情況下,呼叫者應查詢 folio,鎖定它,並再次呼叫 ->read_folio。

呼叫者可以直接呼叫 ->read_folio() 方法,但使用 read_mapping_folio() 將處理鎖定、等待讀取完成以及 AOP_TRUNCATED_PAGE 等情況。

writepages

由 VM 呼叫以寫出與 address_space 物件關聯的頁面。如果 wbc->sync_mode 是 WB_SYNC_ALL,則 writeback_control 將指定必須寫出的頁面範圍。 如果它是 WB_SYNC_NONE,則會給出 nr_to_write,並且應儘可能多地寫入頁面。如果沒有給出 ->writepages,則使用 mpage_writepages 代替。這將從地址空間中選擇標記為 DIRTY 的頁面並將它們寫回。

dirty_folio

由 VM 呼叫以將 folio 標記為 dirty。 如果地址空間將私有資料附加到 folio,並且該資料需要在 folio 被標記為 dirty 時更新,則尤其需要這樣做。 例如,當修改記憶體對映頁面時,會呼叫此函式。如果已定義,它應設定 folio 的 dirty 標誌以及 i_pages 中的 PAGECACHE_TAG_DIRTY 搜尋標記。

readahead

由 VM 呼叫以讀取與 address_space 物件關聯的頁面。 這些頁面在頁面快取中是連續的,並且已鎖定。 在對每個頁面啟動 I/O 後,實現應減少頁面引用計數。 通常,該頁面將由 I/O 完成處理程式解鎖。 頁面集分為一些同步頁面和一些非同步頁面,rac->ra->async_size 給出了非同步頁面的數量。 檔案系統應嘗試讀取所有同步頁面,但可能會決定在到達非同步頁面後停止。 如果它確實決定停止嘗試 I/O,則可以簡單地返回。 呼叫者將從地址空間中刪除剩餘頁面,解鎖它們並減少頁面引用計數。 如果 I/O 成功完成,則設定 PageUptodate。

write_begin

由通用緩衝寫入程式碼呼叫,以要求檔案系統準備在檔案中給定的偏移量處寫入 len 個位元組。 address_space 應透過在必要時分配空間並執行任何其他內部管理來檢查寫入是否能夠完成。 如果寫入將更新儲存上的任何基本塊的部分,則應預先讀取這些塊(如果尚未讀取),以便可以正確寫出更新後的塊。

檔案系統必須在 *foliop 中返回指定偏移量的鎖定 pagecache folio,以供呼叫者寫入。

它必須能夠處理短寫入(其中傳遞給 write_begin 的長度大於複製到 folio 中的位元組數)。

可以在 fsdata 中返回一個 void *,然後將其傳遞到 write_end。

成功時返回 0;失敗時返回 < 0(這是錯誤程式碼),在這種情況下,不會呼叫 write_end。

write_end

在成功 write_begin 和資料複製之後,必須呼叫 write_end。 len 是傳遞給 write_begin 的原始 len,copied 是能夠複製的數量。

檔案系統必須負責解鎖 folio,減少其引用計數並更新 i_size。

失敗時返回 < 0,否則返回能夠複製到 pagecache 中的位元組數(<= 'copied')。

bmap

由 VFS 呼叫以將物件中的邏輯塊偏移量對映到物理塊號。 此方法由 FIBMAP ioctl 使用,並用於處理交換檔案。 為了能夠交換到檔案,該檔案必須具有到塊裝置的穩定對映。 交換系統不經過檔案系統,而是使用 bmap 找出檔案中塊的位置,並直接使用這些地址。

invalidate_folio

如果 folio 具有私有資料,則當要從地址空間中刪除部分或全部 folio 時,將呼叫 invalidate_folio。 這通常對應於截斷、打孔或地址空間的完全無效(在後一種情況下,“offset”將始終為 0,“length”將為 folio_size())。 應更新與 folio 關聯的任何私有資料以反映此截斷。 如果 offset 為 0 且 length 為 folio_size(),則應釋放私有資料,因為必須能夠完全丟棄 folio。 可以透過呼叫 ->release_folio 函式來完成,但在這種情況下,釋放必須成功。

release_folio

在具有私有資料的 folio 上呼叫 release_folio 以告知檔案系統 folio 即將被釋放。 ->release_folio 應從 folio 中刪除任何私有資料並清除私有標誌。 如果 release_folio() 失敗,則應返回 false。 release_folio() 用於兩個不同的但相關的案例。 第一個是 VM 想要釋放沒有活動使用者的乾淨 folio。 如果 ->release_folio 成功,則將從 address_space 中刪除該 folio 並釋放。

第二種情況是已請求使地址空間中的部分或全部 folio 無效。 這可以透過 fadvise(POSIX_FADV_DONTNEED) 系統呼叫或由檔案系統顯式請求,如 nfs 和 9p 所做的那樣(當它們認為快取可能與儲存過時時),透過呼叫 invalidate_inode_pages2()。 如果檔案系統進行這樣的呼叫,並且需要確保所有 folio 都無效,則其 release_folio 將需要確保這一點。 如果它還不能釋放私有資料,則可能會清除 uptodate 標誌。

free_folio

一旦 folio 在頁面快取中不再可見,就會呼叫 free_folio,以便清理任何私有資料。 由於它可能被記憶體回收器呼叫,因此它不應假設原始 address_space 對映仍然存在,並且不應阻塞。

direct_IO

由通用讀/寫例程呼叫以執行 direct_IO - 即繞過頁面快取並在儲存和應用程式的地址空間之間直接傳輸資料的 IO 請求。

migrate_folio

這用於壓縮物理記憶體使用。 如果 VM 想要重新定位 folio(可能來自發出迫在眉睫的故障訊號的記憶體裝置),它會將新的 folio 和舊的 folio 傳遞給此函式。 migrate_folio 應跨傳輸任何私有資料並更新它具有的對 folio 的任何引用。

launder_folio

在釋放 folio 之前呼叫 - 它寫回 dirty folio。 為了防止重新標記 folio 為 dirty,在整個操作過程中它保持鎖定狀態。

is_partially_uptodate

當透過頁面快取讀取檔案時,如果底層塊大小小於 folio 的大小,則由 VM 呼叫。 如果所需的塊是最新的,則讀取可以在不需要 I/O 來使整個頁面保持最新狀態的情況下完成。

is_dirty_writeback

當嘗試回收 folio 時,由 VM 呼叫。 VM 使用 dirty 和 writeback 資訊來確定是否需要停止以允許重新整理器有機會完成一些 IO。 通常,它可以使用 folio_test_dirty 和 folio_test_writeback,但某些檔案系統具有更復雜的狀態(NFS 中的不穩定 folio 會阻止回收),或者由於鎖定問題而未設定這些標誌。 此回撥允許檔案系統向 VM 指示是否應將 folio 視為 dirty 或 writeback 以用於停止的目的。

error_remove_folio

如果此地址空間的截斷是可以的,則通常設定為 generic_error_remove_folio。 用於記憶體故障處理。 設定此選項意味著您處理在您下面消失的頁面,除非您已鎖定它們或增加了引用計數。

swap_activate

呼叫以準備給定的檔案進行交換。 它應執行任何必要的驗證和準備,以確保可以以最小的記憶體分配執行寫入。 它應呼叫 add_swap_extent() 或輔助函式 iomap_swapfile_activate(),並返回新增的範圍數。 如果應透過 ->swap_rw() 提交 IO,則應設定 SWP_FS_OPS,否則 IO 將直接提交到塊裝置 sis->bdev

swap_deactivate

在 swapoff 時對 swap_activate 成功的檔案呼叫。

swap_rw

設定 SWP_FS_OPS 時呼叫以讀取或寫入交換頁面。

檔案物件

檔案物件表示程序開啟的檔案。 在 POSIX 術語中,這也稱為“開啟檔案描述”。

struct file_operations

這描述了 VFS 如何操作開啟的檔案。 截至核心 4.18,定義了以下成員

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 (*iopoll)(struct kiocb *kiocb, bool spin);
        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 *);
        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);
        loff_t (*remap_file_range)(struct file *file_in, loff_t pos_in,
                                   struct file *file_out, loff_t pos_out,
                                   loff_t len, unsigned int remap_flags);
        int (*fadvise)(struct file *, loff_t, loff_t, int);
};

同樣,除非另有說明,否則所有方法都在不持有任何鎖的情況下呼叫。

llseek

當 VFS 需要移動檔案位置索引時呼叫

read

由 read(2) 和相關的系統呼叫呼叫

read_iter

可能帶有 iov_iter 作為目標的非同步讀取

write

由 write(2) 和相關的系統呼叫呼叫

write_iter

可能帶有 iov_iter 作為源的非同步寫入

iopoll

當 aio 想要輪詢 HIPRI iocb 上的完成時呼叫

iterate_shared

當 VFS 需要讀取目錄內容時呼叫

poll

當程序想要檢查此檔案上是否有活動並且(可選)進入睡眠狀態直到有活動時,由 VFS 呼叫。 由 select(2) 和 poll(2) 系統呼叫呼叫

unlocked_ioctl

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

compat_ioctl
當 32 位系統呼叫

在 64 位核心上使用時,由 ioctl(2) 系統呼叫呼叫。

mmap

由 mmap(2) 系統呼叫呼叫

open

當應開啟 inode 時,由 VFS 呼叫。 當 VFS 開啟檔案時,它會建立一個新的“struct file”。 然後,它為新分配的檔案結構呼叫 open 方法。 您可能會認為 open 方法實際上屬於“struct inode_operations”,並且您可能是對的。 我認為這樣做是因為它使檔案系統更易於實現。 如果您想指向裝置結構,則 open() 方法是初始化檔案結構中的“private_data”成員的好地方

flush

由 close(2) 系統呼叫呼叫以重新整理檔案

release

當對開啟檔案的最後一個引用關閉時呼叫

fsync

由 fsync(2) 系統呼叫呼叫。 另請參見上面標題為“處理回寫期間的錯誤”的部分。

fasync

當為檔案啟用非同步(非阻塞)模式時,由 fcntl(2) 系統呼叫呼叫

lock

對於 F_GETLK、F_SETLK 和 F_SETLKW 命令,由 fcntl(2) 系統呼叫呼叫

get_unmapped_area

由 mmap(2) 系統呼叫呼叫

check_flags

對於 F_SETFL 命令,由 fcntl(2) 系統呼叫呼叫

flock

由 flock(2) 系統呼叫呼叫

splice_write

由 VFS 呼叫以將資料從管道拼接到檔案。 此方法由 splice(2) 系統呼叫使用

splice_read

由 VFS 呼叫以將資料從檔案拼接到管道。 此方法由 splice(2) 系統呼叫使用

setlease

由 VFS 呼叫以設定或釋放檔案鎖定租約。 setlease 實現應呼叫 generic_setlease 以在設定租約後記錄或刪除 inode 中的租約。

fallocate

由 VFS 呼叫以預先分配塊或打孔。

copy_file_range

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

remap_file_range

由 ioctl(2) 系統呼叫呼叫,用於 FICLONERANGE 和 FICLONE 以及 FIDEDUPERANGE 命令以重新對映檔案範圍。 實現應將原始檔的 pos_in 處的 len 位元組重新對映到 dest 檔案在 pos_out 處。 實現必須處理呼叫者傳入 len == 0;這意味著“重新對映到原始檔的末尾”。 返回值應為重新對映的位元組數,如果任何位元組重新對映之前發生錯誤,則為通常的負錯誤程式碼。 remap_flags 引數接受 REMAP_FILE_* 標誌。 如果設定了 REMAP_FILE_DEDUP,則實現必須僅在請求的檔案範圍具有相同內容時才重新對映。 如果設定了 REMAP_FILE_CAN_SHORTEN,則呼叫者可以接受實現縮短請求長度以滿足對齊或 EOF 要求(或任何其他原因)。

fadvise

可能由 fadvise64() 系統呼叫呼叫。

請注意,檔案操作由 inode 駐留的特定檔案系統實現。 當開啟裝置節點(字元或塊特殊檔案)時,大多數檔案系統將呼叫 VFS 中的特殊支援例程,這些例程將定位所需的裝置驅動程式資訊。 這些支援例程將檔案系統的檔案操作替換為裝置驅動程式的那些操作,然後繼續為檔案呼叫新的 open() 方法。 這就是在檔案系統中開啟裝置檔案最終如何呼叫裝置驅動程式的 open() 方法。

目錄項快取 (dcache)

struct dentry_operations

這描述了檔案系統如何過載標準 dentry 操作。 Dentries 和 dcache 是 VFS 和各個檔案系統實現的領域。 裝置驅動程式在這裡沒有任何業務。 這些方法可以設定為 NULL,因為它們是可選的,或者 VFS 使用預設值。 截至核心 2.6.22,定義了以下成員

struct dentry_operations {
        int (*d_revalidate)(struct inode *, const struct qstr *,
                            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 *, enum d_real_type type);
        bool (*d_unalias_trylock)(const struct dentry *);
        void (*d_unalias_unlock)(const struct dentry *);
};
d_revalidate

當 VFS 需要重新驗證 dentry 時呼叫。 每當名稱查詢在 dcache 中找到 dentry 時都會呼叫此函式。 大多數本地檔案系統將其保留為 NULL,因為它們在 dcache 中的所有 dentry 都是有效的。 網路檔案系統不同,因為伺服器上的內容可能會發生變化,而客戶端不一定知道這一點。

如果 dentry 仍然有效,此函式應返回正值,如果無效,則返回零或負錯誤程式碼。

d_revalidate 可以在 rcu-walk 模式下呼叫(flags & LOOKUP_RCU)。 如果處於 rcu-walk 模式,檔案系統必須在不阻塞或儲存到 dentry 的情況下重新驗證 dentry,d_parent 和 d_inode 不應在沒有注意的情況下使用(因為它們可能會發生變化,並且在 d_inode 情況下,甚至可能在我們下面變為 NULL)。

如果遇到 rcu-walk 無法處理的情況,則返回 -ECHILD,它將在 ref-walk 模式下再次呼叫。

d_weak_revalidate

當 VFS 需要重新驗證“跳轉”的 dentry 時呼叫。 當路徑遍歷在未透過在父目錄中查詢獲得的 dentry 處結束時,會呼叫此函式。 這包括“/”、“.”和“..”,以及 procfs 樣式的符號連結和掛載點遍歷。

在這種情況下,我們不太關心 dentry 是否仍然完全正確,而是關心 inode 是否仍然有效。 與 d_revalidate 一樣,大多數本地檔案系統會將此設定為 NULL,因為它們的 dcache 條目始終有效。

此函式具有與 d_revalidate 相同的返回程式碼語義。

d_weak_revalidate 僅在離開 rcu-walk 模式後呼叫。

d_hash

當 VFS 將 dentry 新增到雜湊表時呼叫。 傳遞給 d_hash 的第一個 dentry 是名稱要雜湊到的父目錄。

與 d_compare 關於取消引用等安全性的相同鎖定和同步規則。

d_compare

呼叫以將 dentry 名稱與給定名稱進行比較。 第一個 dentry 是要比較的 dentry 的父項,第二個是子 dentry。 len 和 name 字串是要比較的 dentry 的屬性。 qstr 是要與其比較的名稱。

必須是常量和冪等的,如果可能,不應採用鎖,也不應儲存到 dentry 中。 如果沒有特別注意,不應取消引用 dentry 之外的指標(例如,不應使用 d_parent、d_inode、d_name)。

但是,我們的 vfsmount 已固定,並且 RCU 已保持,因此 dentry 和 inode 不會消失,我們的 sb 或檔案系統模組也不會消失。 可以使用 ->d_sb。

這是一個棘手的呼叫約定,因為它需要在“rcu-walk”下呼叫,即沒有任何鎖定或引用。在事物上。

d_delete

當最後一個對 dentry 的引用被刪除並且 dcache 正在決定是否快取它時呼叫。 返回 1 以立即刪除,或返回 0 以快取 dentry。 預設為 NULL,這意味著始終快取可訪問的 dentry。 d_delete 必須是常量和冪等的。

d_init

在分配 dentry 時呼叫

d_release

在真正取消分配 dentry 時呼叫

d_iput

當 dentry 丟失其 inode 時呼叫(就在取消分配之前)。 此值為 NULL 時的預設值為 VFS 呼叫 iput()。 如果您定義此方法,則必須自己呼叫 iput()

d_dname

當應生成 dentry 的路徑名時呼叫。 對於某些偽檔案系統(sockfs、pipefs 等),延遲路徑名生成非常有用。(不應在建立 dentry 時執行,而僅在需要路徑時執行)。 真正的檔案系統可能不想使用它,因為它們的 dentry 存在於全域性 dcache 雜湊中,因此它們的雜湊應該是不可變的。 由於未保持鎖,因此 d_dname() 不應嘗試修改 dentry 本身,除非使用適當的 SMP 安全性。 注意:d_path() 邏輯非常棘手。 正確的返回方式(例如“Hello”)是將其放在緩衝區的末尾,並返回指向第一個字元的指標。 提供了 dynamic_dname() 輔助函式來處理此問題。

示例

static char *pipefs_dname(struct dentry *dent, char *buffer, int buflen)
{
        return dynamic_dname(dentry, buffer, buflen, "pipe:[%lu]",
                        dentry->d_inode->i_ino);
}
d_automount

在遍歷自動掛載 dentry 時呼叫(可選)。 這應建立一個新的 VFS 掛載記錄,並將該記錄返回給呼叫方。 呼叫方將收到一個路徑引數,該引數提供自動掛載目錄以描述自動掛載目標和父 VFS 掛載記錄以提供可繼承的掛載引數。 如果其他人設法首先進行自動掛載,則應返回 NULL。 如果 vfsmount 建立失敗,則應返回錯誤程式碼。 如果返回 -EISDIR,則目錄將被視為普通目錄,並返回給 pathwalk 以繼續遍歷。

如果返回 vfsmount,呼叫方將嘗試將其掛載在掛載點上,並且在失敗的情況下將從其過期列表中刪除 vfsmount。

僅當在 dentry 上設定 DCACHE_NEED_AUTOMOUNT 時才使用此函式。 如果在新增的 inode 上設定了 S_AUTOMOUNT,則 __d_instantiate() 會設定此標誌。

d_manage

呼叫以允許檔案系統管理從 dentry 的轉換(可選)。 例如,這允許 autofs 阻止等待探索“掛載點”背後的客戶端,同時讓守護程式過去並在那裡構造子樹。 應返回 0 以使呼叫程序繼續。 可以返回 -EISDIR 以告訴 pathwalk 將此目錄用作普通目錄,並忽略掛載在其上的任何內容,並且不檢查自動掛載標誌。 任何其他錯誤程式碼將完全中止 pathwalk。

如果 'rcu_walk' 引數為 true,則呼叫方正在 RCU-walk 模式下進行 pathwalk。 在此模式下不允許睡眠,並且可以透過返回 -ECHILD 來要求呼叫方離開並再次呼叫。 也可以返回 -EISDIR 以告訴 pathwalk 忽略 d_automount 或任何掛載。

僅當在正從中傳輸的 dentry 上設定 DCACHE_MANAGE_TRANSIT 時才使用此函式。

d_real

覆蓋/聯合型別檔案系統實現此方法以返回隱藏在覆蓋層後面的常規檔案的基礎 dentry 之一。

“type”引數採用 D_REAL_DATA 或 D_REAL_METADATA 值,以返回分別引用託管檔案資料或元資料的 inode 的實際基礎 dentry。

對於非常規檔案,將返回“dentry”引數。

d_unalias_trylock

如果存在,則將在移動預先存在的附加別名之前由 d_splice_alias() 呼叫。 返回 false 會阻止 __d_move(),從而導致 d_splice_alias() 以 -ESTALE 失敗。

基本原理:設定 FS_RENAME_DOES_D_MOVE 將阻止從檔案系統方法外部呼叫 d_move() 和 d_exchange(); 但是,它不能保證附加的 dentry 不會被 d_splice_alias() 重新命名或移動,因為 d_splice_alias() 找到了目錄 inode 的預先存在的別名。 通常我們不會在意; 但是,想要透過阻塞操作穩定到根目錄的整個路徑的東西可能需要它。 有關一個(並且希望只是一個)示例,請參見 9p。

d_unalias_unlock

應與 d_unalias_trylock 配對; 該呼叫是在 __d_unalias() 中的 __d_move() 呼叫之後呼叫的。

每個 dentry 都有一個指向其父 dentry 的指標,以及一個子 dentry 的雜湊列表。 子 dentry 基本上就像目錄中的檔案。

目錄項快取 API

定義了許多允許檔案系統操作 dentry 的函式

dget

為現有 dentry 開啟一個新控制代碼(這隻會增加使用計數)

dput

關閉 dentry 的控制代碼(減少使用計數)。 如果使用計數降至 0,並且 dentry 仍在父項的雜湊中,則會呼叫“d_delete”方法來檢查是否應快取它。 如果不應快取,或者 dentry 未雜湊,則將其刪除。 否則,快取的 dentry 將放入 LRU 列表,以便在記憶體短缺時回收。

d_drop

這會從其父雜湊列表中取消雜湊 dentry。 如果其使用計數降至 0,則隨後對 dput() 的呼叫將取消分配 dentry

d_delete

刪除 dentry。 如果沒有其他對 dentry 的開啟引用,則 dentry 將變為負 dentry(呼叫 d_iput() 方法)。 如果有其他引用,則會呼叫 d_drop()

d_add

將 dentry 新增到其父雜湊列表,然後呼叫 d_instantiate()

d_instantiate

將 dentry 新增到 inode 的別名雜湊列表並更新“d_inode”成員。 應設定/遞增 inode 結構中的“i_count”成員。 如果 inode 指標為 NULL,則 dentry 稱為“負 dentry”。 通常在為現有負 dentry 建立 inode 時呼叫此函式

d_lookup

給定其父項和路徑名元件,查詢 dentry。它從 dcache 雜湊表中查詢該給定名稱的子項。 如果找到,則引用計數將遞增並返回 dentry。 呼叫方必須在使用完 dentry 後使用 dput() 釋放 dentry。

掛載選項

解析選項

在掛載和重新掛載時,檔案系統將傳遞一個字串,其中包含以逗號分隔的掛載選項列表。 這些選項可以具有以下形式之一

選項 option=value

<linux/parser.h> 標頭定義了一個有助於解析這些選項的 API。 在現有檔案系統中有很多關於如何使用它的示例。

顯示選項

如果檔案系統接受掛載選項,則必須定義 show_options() 以顯示所有當前活動的選項。 規則是

  • 必須顯示非預設選項或其值與預設值不同的選項

  • 可以顯示預設啟用的選項或具有其預設值的選項

僅在掛載助手和核心之間內部使用的選項(例如檔案描述符)或僅在掛載期間生效的選項(例如控制日誌建立的選項)免於上述規則。

上述規則的基本原因是確保可以準確地複製掛載(例如,基於在 /proc/mounts 中找到的資訊再次解除安裝和掛載)。

資源

(請注意,某些資源不是最新的核心

版本。)

建立 Linux 虛擬檔案系統。 2002

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

Neil Brown 的 Linux 虛擬檔案系統層。 1999

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

Michael K. Johnson 的 Linux VFS 之旅。 1996

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

Andries Brouwer 的 Linux 核心小徑。 2001

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